From 7190f4ae2eda9287d25cd05491d4ac41cf54325f Mon Sep 17 00:00:00 2001
From: Matheus Carvalho <41878558+matheus-carvalho@users.noreply.github.com>
Date: Thu, 9 May 2024 14:26:48 -0300
Subject: [PATCH] [1.x] Allow thresholds of slow queries, jobs and requests to
be customised by regex pattern (#340)
* allow thresholds of slow queries, jobs and requests to be customised by regex pattern
* Fix lint and tests
* Prevent error when Livewire payload contains no components
* Improvements customised thresholds
* Format slow requests
* Revert default config
* slow requests UI
* format slow jobs recorder
* format slow jobs config
* format slow jobs ui
* formatting
* formatting
* format slow outgoing config
* format slow outgoing recorder
* format slow outgoing ui
* format slow queries config
* format slow queries recorder
* Format slow queries threshold
* Format usage UI
* use @js directive
* Remove comment
* Only resolve value once
* Fix CI
* Fix tests
* Formatting
* Formatting
---------
Co-authored-by: Matheus Carvalho
Co-authored-by: Jess Archer
Co-authored-by: Tim MacDonald
---
resources/views/livewire/cache.blade.php | 2 +-
resources/views/livewire/slow-jobs.blade.php | 7 ++-
.../livewire/slow-outgoing-requests.blade.php | 9 +++-
.../views/livewire/slow-queries.blade.php | 7 ++-
.../views/livewire/slow-requests.blade.php | 7 ++-
resources/views/livewire/usage.blade.php | 10 +++-
src/Livewire/SlowJobs.php | 4 ++
src/Livewire/SlowOutgoingRequests.php | 4 ++
src/Livewire/SlowQueries.php | 4 ++
src/Livewire/SlowRequests.php | 4 ++
src/Recorders/Concerns/LivewireRoutes.php | 4 +-
src/Recorders/Concerns/Thresholds.php | 27 +++++++++-
src/Recorders/SlowJobs.php | 4 +-
src/Recorders/SlowOutgoingRequests.php | 4 +-
src/Recorders/SlowQueries.php | 4 +-
src/Recorders/SlowRequests.php | 11 ++---
tests/Feature/Livewire/SlowJobsTest.php | 4 +-
.../Livewire/SlowOutgoingRequestsTest.php | 4 +-
tests/Feature/Livewire/SlowQueriesTest.php | 4 +-
tests/Feature/Livewire/SlowRequestsTest.php | 4 +-
tests/Feature/Recorders/SlowJobsTest.php | 43 +++++++++++++++-
.../Recorders/SlowOutgoingRequestsTest.php | 49 +++++++++++++++++++
tests/Feature/Recorders/SlowQueriesTest.php | 39 +++++++++++++++
tests/Feature/Recorders/SlowRequestsTest.php | 48 ++++++++++++++++++
24 files changed, 275 insertions(+), 32 deletions(-)
diff --git a/resources/views/livewire/cache.blade.php b/resources/views/livewire/cache.blade.php
index b5dfdb4f..ce5641e5 100644
--- a/resources/views/livewire/cache.blade.php
+++ b/resources/views/livewire/cache.blade.php
@@ -20,7 +20,7 @@
Str::plural('group', $count)
);
@endphp
-
@endif
+ @if (is_array($config['threshold']))
+
+ {{ $query->threshold }}ms threshold
+
+ @endif
diff --git a/resources/views/livewire/slow-requests.blade.php b/resources/views/livewire/slow-requests.blade.php
index 3a585bc6..c92763a5 100644
--- a/resources/views/livewire/slow-requests.blade.php
+++ b/resources/views/livewire/slow-requests.blade.php
@@ -2,7 +2,7 @@
@@ -55,6 +55,11 @@
{{ $slowRequest->action }}
@endif
+ @if (is_array($config['threshold']))
+
+ {{ $slowRequest->threshold }}ms threshold
+
+ @endif
@if ($config['sample_rate'] < 1)
diff --git a/resources/views/livewire/usage.blade.php b/resources/views/livewire/usage.blade.php
index 49609063..d8389ab0 100644
--- a/resources/views/livewire/usage.blade.php
+++ b/resources/views/livewire/usage.blade.php
@@ -7,7 +7,7 @@
default => 'Application Usage'
}"
title="Time: {{ number_format($time) }}ms; Run at: {{ $runAt }};"
- details="{{ $this->usage === 'slow_requests' ? ($slowRequestsConfig['threshold'].'ms threshold, ') : '' }}past {{ $this->periodForHumans() }}"
+ details="{{ $this->usage === 'slow_requests' ? (is_array($slowRequestsConfig['threshold']) ? '' : $slowRequestsConfig['threshold'].'ms threshold, ') : '' }}past {{ $this->periodForHumans() }}"
>
@endif
+ @if ($this->usage === 'slow_requests' && is_array($slowRequestsConfig['threshold']))
+ @php
+ $message = 'You have per-route thresholds configured.';
+ @endphp
+
+
+
+ @endif
diff --git a/src/Livewire/SlowJobs.php b/src/Livewire/SlowJobs.php
index 492ba399..a6614fc6 100644
--- a/src/Livewire/SlowJobs.php
+++ b/src/Livewire/SlowJobs.php
@@ -5,6 +5,7 @@
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\View;
+use Laravel\Pulse\Recorders\Concerns\Thresholds;
use Laravel\Pulse\Recorders\SlowJobs as SlowJobsRecorder;
use Livewire\Attributes\Lazy;
use Livewire\Attributes\Url;
@@ -15,6 +16,8 @@
#[Lazy]
class SlowJobs extends Card
{
+ use Thresholds;
+
/**
* Ordering.
*
@@ -40,6 +43,7 @@ public function render(): Renderable
'job' => $row->key,
'slowest' => $row->max,
'count' => $row->count,
+ 'threshold' => $this->threshold($row->key, SlowJobsRecorder::class),
]),
$this->orderBy,
);
diff --git a/src/Livewire/SlowOutgoingRequests.php b/src/Livewire/SlowOutgoingRequests.php
index da1cfbfc..52da83c7 100644
--- a/src/Livewire/SlowOutgoingRequests.php
+++ b/src/Livewire/SlowOutgoingRequests.php
@@ -5,6 +5,7 @@
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\View;
+use Laravel\Pulse\Recorders\Concerns\Thresholds;
use Laravel\Pulse\Recorders\SlowOutgoingRequests as SlowOutgoingRequestsRecorder;
use Livewire\Attributes\Lazy;
use Livewire\Attributes\Url;
@@ -15,6 +16,8 @@
#[Lazy]
class SlowOutgoingRequests extends Card
{
+ use Thresholds;
+
/**
* Ordering.
*
@@ -44,6 +47,7 @@ public function render(): Renderable
'uri' => $uri,
'slowest' => $row->max,
'count' => $row->count,
+ 'threshold' => $this->threshold($uri, SlowOutgoingRequestsRecorder::class),
];
}),
$this->orderBy,
diff --git a/src/Livewire/SlowQueries.php b/src/Livewire/SlowQueries.php
index e8ff3a5d..b4f1692f 100644
--- a/src/Livewire/SlowQueries.php
+++ b/src/Livewire/SlowQueries.php
@@ -5,6 +5,7 @@
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\View;
+use Laravel\Pulse\Recorders\Concerns\Thresholds;
use Laravel\Pulse\Recorders\SlowQueries as SlowQueriesRecorder;
use Livewire\Attributes\Lazy;
use Livewire\Attributes\Url;
@@ -15,6 +16,8 @@
#[Lazy]
class SlowQueries extends Card
{
+ use Thresholds;
+
/**
* Ordering.
*
@@ -56,6 +59,7 @@ public function render(): Renderable
'location' => $location,
'slowest' => $row->max,
'count' => $row->count,
+ 'threshold' => $this->threshold($sql, SlowQueriesRecorder::class),
];
}),
$this->orderBy,
diff --git a/src/Livewire/SlowRequests.php b/src/Livewire/SlowRequests.php
index 159e8813..9521f9c2 100644
--- a/src/Livewire/SlowRequests.php
+++ b/src/Livewire/SlowRequests.php
@@ -5,6 +5,7 @@
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\View;
+use Laravel\Pulse\Recorders\Concerns\Thresholds;
use Laravel\Pulse\Recorders\SlowRequests as SlowRequestsRecorder;
use Livewire\Attributes\Lazy;
use Livewire\Attributes\Url;
@@ -15,6 +16,8 @@
#[Lazy]
class SlowRequests extends Card
{
+ use Thresholds;
+
/**
* Ordering.
*
@@ -45,6 +48,7 @@ public function render(): Renderable
'action' => $action,
'count' => $row->count,
'slowest' => $row->max,
+ 'threshold' => $this->threshold($uri, SlowRequestsRecorder::class),
];
}),
$this->orderBy,
diff --git a/src/Recorders/Concerns/LivewireRoutes.php b/src/Recorders/Concerns/LivewireRoutes.php
index 118e2d93..3d5ff701 100644
--- a/src/Recorders/Concerns/LivewireRoutes.php
+++ b/src/Recorders/Concerns/LivewireRoutes.php
@@ -20,8 +20,8 @@ protected function resolveRoutePath(Request $request): array
$path = $route->getDomain().Str::start($route->uri(), '/');
$via = $route->getActionName();
- if ($route->named('*livewire.update')) {
- $snapshot = json_decode($request->input('components.0.snapshot'), flags: JSON_THROW_ON_ERROR);
+ if ($route->named('*livewire.update') && ($snapshot = $request->input('components.0.snapshot'))) {
+ $snapshot = json_decode($snapshot, flags: JSON_THROW_ON_ERROR);
if (isset($snapshot->memo->path)) {
$via = 'via '.$path;
diff --git a/src/Recorders/Concerns/Thresholds.php b/src/Recorders/Concerns/Thresholds.php
index 4ef2efaa..6f400f85 100644
--- a/src/Recorders/Concerns/Thresholds.php
+++ b/src/Recorders/Concerns/Thresholds.php
@@ -2,13 +2,36 @@
namespace Laravel\Pulse\Recorders\Concerns;
+use Illuminate\Support\Facades\Config;
+
trait Thresholds
{
/**
* Determine if the duration is under the configured threshold.
*/
- protected function underThreshold(int|float $duration): bool
+ protected function underThreshold(int|float $duration, string $key): bool
+ {
+ return $duration < $this->threshold($key);
+ }
+
+ /**
+ * Get the threshold for the given key.
+ */
+ protected function threshold(string $key, ?string $recorder = null): int
{
- return $duration < $this->config->get('pulse.recorders.'.static::class.'.threshold');
+ $recorder ??= static::class;
+
+ $config = Config::get("pulse.recorders.{$recorder}.threshold");
+
+ if (! is_array($config)) {
+ return $config;
+ }
+
+ // @phpstan-ignore argument.templateType, argument.templateType
+ $custom = collect($config)
+ ->except(['default'])
+ ->first(fn ($threshold, $pattern) => preg_match($pattern, $key) === 1);
+
+ return $custom ?? $config['default'] ?? 1_000;
}
}
diff --git a/src/Recorders/SlowJobs.php b/src/Recorders/SlowJobs.php
index e548bb93..e5deac30 100644
--- a/src/Recorders/SlowJobs.php
+++ b/src/Recorders/SlowJobs.php
@@ -74,9 +74,9 @@ public function record(JobReleasedAfterException|JobFailed|JobProcessed|JobProce
$this->pulse->lazy(function () use ($timestamp, $timestampMs, $name, $lastJobStartedProcessingAt) {
if (
- $this->underThreshold($duration = $timestampMs - $lastJobStartedProcessingAt) ||
! $this->shouldSample() ||
- $this->shouldIgnore($name)
+ $this->shouldIgnore($name) ||
+ $this->underThreshold($duration = $timestampMs - $lastJobStartedProcessingAt, $name)
) {
return;
}
diff --git a/src/Recorders/SlowOutgoingRequests.php b/src/Recorders/SlowOutgoingRequests.php
index 10d27869..a3b150bc 100644
--- a/src/Recorders/SlowOutgoingRequests.php
+++ b/src/Recorders/SlowOutgoingRequests.php
@@ -56,9 +56,9 @@ public function record(RequestInterface $request, int $startedAt): void
$this->pulse->lazy(function () use ($startedAt, $timestamp, $endedAt, $method, $uri) {
if (
- $this->underThreshold($duration = $endedAt - $startedAt) ||
! $this->shouldSample() ||
- $this->shouldIgnore($uri)
+ $this->shouldIgnore($uri) ||
+ $this->underThreshold($duration = $endedAt - $startedAt, $uri)
) {
return;
}
diff --git a/src/Recorders/SlowQueries.php b/src/Recorders/SlowQueries.php
index 6f0b5791..a5950df9 100644
--- a/src/Recorders/SlowQueries.php
+++ b/src/Recorders/SlowQueries.php
@@ -48,9 +48,9 @@ public function record(QueryExecuted $event): void
$this->pulse->lazy(function () use ($timestampMs, $duration, $sql, $location) {
if (
- $this->underThreshold($duration) ||
! $this->shouldSample() ||
- $this->shouldIgnore($sql)
+ $this->shouldIgnore($sql) ||
+ $this->underThreshold($duration, $sql)
) {
return;
}
diff --git a/src/Recorders/SlowRequests.php b/src/Recorders/SlowRequests.php
index 44cbbad6..9c4c1473 100644
--- a/src/Recorders/SlowRequests.php
+++ b/src/Recorders/SlowRequests.php
@@ -50,17 +50,16 @@ public function register(callable $record, Application $app): void
*/
public function record(Carbon $startedAt, Request $request, Response $response): void
{
- if (
- ! $request->route() instanceof Route ||
- $this->underThreshold($duration = ((int) $startedAt->diffInMilliseconds())) ||
- ! $this->shouldSample()
- ) {
+ if (! $request->route() instanceof Route || ! $this->shouldSample()) {
return;
}
[$path, $via] = $this->resolveRoutePath($request);
- if ($this->shouldIgnore($path)) {
+ if (
+ $this->shouldIgnore($path) ||
+ $this->underThreshold($duration = ((int) $startedAt->diffInMilliseconds()), $path)
+ ) {
return;
}
diff --git a/tests/Feature/Livewire/SlowJobsTest.php b/tests/Feature/Livewire/SlowJobsTest.php
index d924a657..00452cab 100644
--- a/tests/Feature/Livewire/SlowJobsTest.php
+++ b/tests/Feature/Livewire/SlowJobsTest.php
@@ -33,7 +33,7 @@
Livewire::test(SlowJobs::class, ['lazy' => false])
->assertViewHas('slowJobs', collect([
- (object) ['job' => 'App\Jobs\MyJob', 'count' => 4, 'slowest' => 2468],
- (object) ['job' => 'App\Jobs\MyOtherJob', 'count' => 2, 'slowest' => 1234],
+ (object) ['job' => 'App\Jobs\MyJob', 'count' => 4, 'slowest' => 2468, 'threshold' => 1_000],
+ (object) ['job' => 'App\Jobs\MyOtherJob', 'count' => 2, 'slowest' => 1234, 'threshold' => 1_000],
]));
});
diff --git a/tests/Feature/Livewire/SlowOutgoingRequestsTest.php b/tests/Feature/Livewire/SlowOutgoingRequestsTest.php
index 05bfdf51..937ecc0a 100644
--- a/tests/Feature/Livewire/SlowOutgoingRequestsTest.php
+++ b/tests/Feature/Livewire/SlowOutgoingRequestsTest.php
@@ -33,7 +33,7 @@
Livewire::test(SlowOutgoingRequests::class, ['lazy' => false])
->assertViewHas('slowOutgoingRequests', collect([
- (object) ['method' => 'GET', 'uri' => 'http://example.com', 'count' => 4, 'slowest' => 2468],
- (object) ['method' => 'GET', 'uri' => 'http://example.org', 'count' => 2, 'slowest' => 1234],
+ (object) ['method' => 'GET', 'uri' => 'http://example.com', 'count' => 4, 'slowest' => 2468, 'threshold' => 1_000],
+ (object) ['method' => 'GET', 'uri' => 'http://example.org', 'count' => 2, 'slowest' => 1234, 'threshold' => 1_000],
]));
});
diff --git a/tests/Feature/Livewire/SlowQueriesTest.php b/tests/Feature/Livewire/SlowQueriesTest.php
index faf981c5..73a0b8f0 100644
--- a/tests/Feature/Livewire/SlowQueriesTest.php
+++ b/tests/Feature/Livewire/SlowQueriesTest.php
@@ -36,8 +36,8 @@
Livewire::test(SlowQueries::class, ['lazy' => false])
->assertViewHas('slowQueries', collect([
- (object) ['sql' => 'select * from `users`', 'location' => 'app/Foo.php:123', 'count' => 4, 'slowest' => 2468],
- (object) ['sql' => 'select * from `users` where `id` = ?', 'location' => 'app/Bar.php:456', 'count' => 2, 'slowest' => 1234],
+ (object) ['sql' => 'select * from `users`', 'location' => 'app/Foo.php:123', 'count' => 4, 'slowest' => 2468, 'threshold' => 1_000],
+ (object) ['sql' => 'select * from `users` where `id` = ?', 'location' => 'app/Bar.php:456', 'count' => 2, 'slowest' => 1234, 'threshold' => 1_000],
]));
});
diff --git a/tests/Feature/Livewire/SlowRequestsTest.php b/tests/Feature/Livewire/SlowRequestsTest.php
index b1d808ef..ee303286 100644
--- a/tests/Feature/Livewire/SlowRequestsTest.php
+++ b/tests/Feature/Livewire/SlowRequestsTest.php
@@ -36,7 +36,7 @@
Livewire::test(SlowRequests::class, ['lazy' => false])
->assertViewHas('slowRequests', collect([
- (object) ['method' => 'GET', 'uri' => '/users', 'action' => 'FooController@index', 'count' => 4, 'slowest' => 2468],
- (object) ['method' => 'GET', 'uri' => '/users/{user}', 'action' => 'Closure', 'count' => 2, 'slowest' => 1234],
+ (object) ['method' => 'GET', 'uri' => '/users', 'action' => 'FooController@index', 'count' => 4, 'slowest' => 2468, 'threshold' => 1_000],
+ (object) ['method' => 'GET', 'uri' => '/users/{user}', 'action' => 'Closure', 'count' => 2, 'slowest' => 1234, 'threshold' => 1_000],
]));
});
diff --git a/tests/Feature/Recorders/SlowJobsTest.php b/tests/Feature/Recorders/SlowJobsTest.php
index d792baab..3ef36720 100644
--- a/tests/Feature/Recorders/SlowJobsTest.php
+++ b/tests/Feature/Recorders/SlowJobsTest.php
@@ -85,6 +85,37 @@
expect(Pulse::ignore(fn () => DB::table('pulse_aggregates')->where('type', 'slow_job')->get()))->toHaveCount(0);
});
+it('can configure threshold per job', function () {
+ Carbon::setTestNow('2000-01-02 03:04:05');
+ Config::set('queue.default', 'database');
+ Config::set('pulse.recorders.'.SlowJobs::class.'.threshold', [
+ '#MySlowJob#' => 1_000,
+ '#AnotherSlowJob#' => 2_000,
+ ]);
+
+ Bus::dispatchToQueue(new MySlowJob(1_000));
+ Bus::dispatchToQueue(new AnotherSlowJob(1_000));
+ Artisan::call('queue:work', ['--max-jobs' => 2, '--stop-when-empty' => true, '--sleep' => 0]);
+
+ $entries = Pulse::ignore(fn () => DB::table('pulse_entries')->where('type', 'slow_job')->get());
+ expect($entries)->toHaveCount(1);
+ expect($entries[0]->key)->toBe('MySlowJob');
+ expect($entries[0]->value)->toBe(1_000);
+
+ DB::table('pulse_entries')->delete();
+
+ Bus::dispatchToQueue(new MySlowJob(2_000));
+ Bus::dispatchToQueue(new AnotherSlowJob(2_000));
+ Artisan::call('queue:work', ['--max-jobs' => 2, '--stop-when-empty' => true, '--sleep' => 0]);
+
+ $entries = Pulse::ignore(fn () => DB::table('pulse_entries')->where('type', 'slow_job')->get());
+ expect($entries)->toHaveCount(2);
+ expect($entries[0]->key)->toBe('MySlowJob');
+ expect($entries[0]->value)->toBe(2_000);
+ expect($entries[1]->key)->toBe('AnotherSlowJob');
+ expect($entries[1]->value)->toBe(2_000);
+});
+
it('can ignore jobs', function () {
Config::set('queue.default', 'database');
Config::set('pulse.recorders.'.SlowJobs::class.'.threshold', 0);
@@ -220,8 +251,18 @@
class MySlowJob implements ShouldQueue
{
+ public function __construct(public $duration = 100)
+ {
+ //
+ }
+
public function handle()
{
- Carbon::setTestNow(Carbon::now()->addMilliseconds(100));
+ Carbon::setTestNow(Carbon::now()->addMilliseconds($this->duration));
}
}
+
+class AnotherSlowJob extends MySlowJob
+{
+ //
+}
diff --git a/tests/Feature/Recorders/SlowOutgoingRequestsTest.php b/tests/Feature/Recorders/SlowOutgoingRequestsTest.php
index 77fe4f6a..925bcb06 100644
--- a/tests/Feature/Recorders/SlowOutgoingRequestsTest.php
+++ b/tests/Feature/Recorders/SlowOutgoingRequestsTest.php
@@ -4,9 +4,12 @@
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Sleep;
use Laravel\Pulse\Facades\Pulse;
use Laravel\Pulse\Recorders\SlowOutgoingRequests;
+use function Pest\Laravel\freezeTime;
+
it('ingests slow outgoing http requests', function () {
Config::set('pulse.recorders.'.SlowOutgoingRequests::class.'.threshold', 0);
Carbon::setTestNow('2000-01-02 03:04:05');
@@ -219,3 +222,49 @@
expect(Pulse::ingest())->toBe(10);
});
+
+it('can configure threshold per url', function () {
+ freezeTime();
+ Sleep::fake(syncWithCarbon: true);
+ Config::set('pulse.recorders.'.SlowOutgoingRequests::class.'.threshold', [
+ '#one-second-threshold#' => 1_000,
+ '#two-second-threshold#' => 2_000,
+ ]);
+ $sleepSeconds = null;
+ Http::fake([
+ 'one-second-threshold' => function () use (&$sleepSeconds) {
+ Sleep::for($sleepSeconds)->seconds();
+
+ return Http::response('ok');
+ },
+ 'two-second-threshold' => function () use (&$sleepSeconds) {
+ Sleep::for($sleepSeconds)->seconds();
+
+ return Http::response('ok');
+ },
+ ]);
+
+ $sleepSeconds = 1;
+ Http::get('one-second-threshold')->throw();
+ Http::get('two-second-threshold')->throw();
+ Pulse::ingest();
+
+ $entries = Pulse::ignore(fn () => DB::table('pulse_entries')->get());
+ expect($entries)->toHaveCount(1);
+ expect($entries[0]->key)->toBe('["GET","one-second-threshold"]');
+ expect($entries[0]->value)->toBe(1000);
+
+ DB::table('pulse_entries')->delete();
+
+ $sleepSeconds = 2;
+ Http::get('one-second-threshold')->throw();
+ Http::get('two-second-threshold')->throw();
+ Pulse::ingest();
+
+ $entries = Pulse::ignore(fn () => DB::table('pulse_entries')->orderBy('key')->get());
+ expect($entries)->toHaveCount(2);
+ expect($entries[0]->key)->toBe('["GET","one-second-threshold"]');
+ expect($entries[0]->value)->toBe(2_000);
+ expect($entries[1]->key)->toBe('["GET","two-second-threshold"]');
+ expect($entries[1]->value)->toBe(2_000);
+});
diff --git a/tests/Feature/Recorders/SlowQueriesTest.php b/tests/Feature/Recorders/SlowQueriesTest.php
index 14f06dbe..705cca3a 100644
--- a/tests/Feature/Recorders/SlowQueriesTest.php
+++ b/tests/Feature/Recorders/SlowQueriesTest.php
@@ -108,6 +108,45 @@
Pulse::ignore(fn () => expect(DB::table('pulse_entries')->count())->toBe(0));
});
+it('can configure threshold per query', function () {
+ Config::set('pulse.recorders.'.SlowQueries::class.'.threshold', [
+ '#one_second_threshold#' => 1_000,
+ '#two_second_threshold#' => 2_000,
+ ]);
+ $queryDuration = null;
+ prependListener(QueryExecuted::class, function (QueryExecuted $event) use (&$queryDuration) {
+ $event->time = $queryDuration;
+ });
+
+ $queryDuration = 1_000;
+ DB::pretend(function () {
+ DB::table('one_second_threshold')->count();
+ DB::table('two_second_threshold')->count();
+ });
+ Pulse::ingest();
+
+ $entries = Pulse::ignore(fn () => DB::table('pulse_entries')->get());
+ expect($entries)->toHaveCount(1);
+ expect($entries[0]->key)->toContain('one_second_threshold');
+ expect($entries[0]->value)->toBe(1_000);
+
+ DB::table('pulse_entries')->delete();
+
+ $queryDuration = 2_000;
+ DB::pretend(function () {
+ DB::table('one_second_threshold')->count();
+ DB::table('two_second_threshold')->count();
+ });
+ Pulse::ingest();
+
+ $entries = Pulse::ignore(fn () => DB::table('pulse_entries')->orderBy('key')->get());
+ expect($entries)->toHaveCount(2);
+ expect($entries[0]->key)->toContain('one_second_threshold');
+ expect($entries[0]->value)->toBe(2_000);
+ expect($entries[1]->key)->toContain('two_second_threshold');
+ expect($entries[1]->value)->toBe(2_000);
+});
+
it('ingests queries equal to the slow query threshold', function () {
Config::set('pulse.recorders.'.SlowQueries::class.'.threshold', 5000);
prependListener(QueryExecuted::class, function (QueryExecuted $event) {
diff --git a/tests/Feature/Recorders/SlowRequestsTest.php b/tests/Feature/Recorders/SlowRequestsTest.php
index e686822d..978d06ba 100644
--- a/tests/Feature/Recorders/SlowRequestsTest.php
+++ b/tests/Feature/Recorders/SlowRequestsTest.php
@@ -6,6 +6,7 @@
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
+use Illuminate\Support\Sleep;
use Laravel\Pulse\Facades\Pulse;
use Laravel\Pulse\Recorders\SlowRequests;
use Tests\User;
@@ -99,6 +100,53 @@
Pulse::ignore(fn () => expect(DB::table('pulse_values')->count())->toBe(0));
});
+it('can configure threshold per route', function () {
+ Date::setTestNow('2000-01-02 03:04:05');
+ Sleep::fake(syncWithCarbon: true);
+ Config::set('pulse.recorders.'.SlowRequests::class.'.threshold', [
+ '#^/one-second-threshold#' => 1_000,
+ '#^/two-second-threshold#' => 2_000,
+ ]);
+ $sleepSeconds = null;
+ Route::get('one-second-threshold', function () use (&$sleepSeconds) {
+ Sleep::for($sleepSeconds)->seconds();
+ });
+ Route::get('two-second-threshold', function () use (&$sleepSeconds) {
+ Sleep::for($sleepSeconds)->seconds();
+ });
+ Route::get('default-threshold', fn () => 'ok');
+
+ $sleepSeconds = 1;
+ get('one-second-threshold')->assertOk();
+ get('two-second-threshold')->assertOk();
+ get('default-threshold')->assertOk();
+
+ $entries = Pulse::ignore(fn () => DB::table('pulse_entries')->get());
+ expect($entries)->toHaveCount(1);
+ expect($entries[0]->type)->toBe('slow_request');
+ expect($entries[0]->key)->toBe(json_encode(['GET', '/one-second-threshold', 'Closure']));
+ expect($entries[0]->key_hash)->toBe(keyHash(json_encode(['GET', '/one-second-threshold', 'Closure'])));
+ expect($entries[0]->value)->toBe(1000);
+
+ DB::table('pulse_entries')->delete();
+
+ $sleepSeconds = 2;
+ get('one-second-threshold')->assertOk();
+ get('two-second-threshold')->assertOk();
+ get('default-threshold')->assertOk();
+
+ $entries = Pulse::ignore(fn () => DB::table('pulse_entries')->orderBy('key')->get());
+ expect($entries)->toHaveCount(2);
+ expect($entries[0]->type)->toBe('slow_request');
+ expect($entries[0]->key)->toBe(json_encode(['GET', '/one-second-threshold', 'Closure']));
+ expect($entries[0]->key_hash)->toBe(keyHash(json_encode(['GET', '/one-second-threshold', 'Closure'])));
+ expect($entries[0]->value)->toBe(2000);
+ expect($entries[1]->type)->toBe('slow_request');
+ expect($entries[1]->key)->toBe(json_encode(['GET', '/two-second-threshold', 'Closure']));
+ expect($entries[1]->key_hash)->toBe(keyHash(json_encode(['GET', '/two-second-threshold', 'Closure'])));
+ expect($entries[1]->value)->toBe(2000);
+});
+
it('captures slow requests per user', function () {
Date::setTestNow('2000-01-02 03:04:05');
Config::set('pulse.recorders.'.SlowRequests::class.'.threshold', 0);