From b962f526e328450c5fa54542b8dd4e255f37e9e1 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Tue, 9 Dec 2025 09:49:44 +0800 Subject: [PATCH 01/19] Track whether to trace vendor commands --- src/Concerns/CapturesState.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index a078405c..5c3b4241 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -59,6 +59,8 @@ trait CapturesState private bool $paused = false; + private bool $captureDefaultVendorCommands = false; + /** * @var WeakMap */ @@ -131,6 +133,31 @@ public function configureScheduledTaskSampling(Event $event): void $this->sample(rate: $this->scheduledTasksSampleRates[$event] ?? $this->config['sampling']['scheduled_tasks']); } + /** + * @api + */ + public function captureDefaultVendorCommands(bool $capture = true): void + { + $this->captureDefaultVendorCommands = $capture; + } + + /** + * @api + * + * @return list + */ + public static function defaultVendorCommands(): array + { + return [ + 'auth:clear-resets', + 'horizon:snapshot', + 'horizon:status', + 'model:prune', + 'passport:purge', + 'sanctum:prune-expired', + ]; + } + /** * @api */ From e819db051c24b3c2e3d3c35a29d13c125f6c403b Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Tue, 9 Dec 2025 11:06:25 +0800 Subject: [PATCH 02/19] Ignore vendor commands by default --- src/Concerns/CapturesState.php | 7 ++++++- src/Hooks/CommandStartingListener.php | 2 +- src/Hooks/ScheduledTaskStartingListener.php | 8 +++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index 5c3b4241..d94c61fb 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -44,6 +44,7 @@ use function array_unshift; use function debug_backtrace; use function env; +use function in_array; use function memory_reset_peak_usage; use function preg_match; use function random_int; @@ -116,8 +117,12 @@ public function configureRequestSampling(): void /** * @internal */ - public function configureCommandSampling(): void + public function configureCommandSampling(string $command): void { + if (in_array($command, $this->defaultVendorCommands(), true)) { + $this->dontSample(); + } + $this->sample(match (Compatibility::getSamplingFromContext(null)) { true => 1.0, false => 0.0, diff --git a/src/Hooks/CommandStartingListener.php b/src/Hooks/CommandStartingListener.php index a41ba645..db0daf43 100644 --- a/src/Hooks/CommandStartingListener.php +++ b/src/Hooks/CommandStartingListener.php @@ -116,7 +116,7 @@ private function registerCommandHooks(CommandStarting $event): void return; } - $this->nightwatch->configureCommandSampling(); + $this->nightwatch->configureCommandSampling($event->command); $this->nightwatch->prepareForCommand($event->command); diff --git a/src/Hooks/ScheduledTaskStartingListener.php b/src/Hooks/ScheduledTaskStartingListener.php index bd699038..61cbfe87 100644 --- a/src/Hooks/ScheduledTaskStartingListener.php +++ b/src/Hooks/ScheduledTaskStartingListener.php @@ -7,6 +7,8 @@ use Laravel\Nightwatch\State\CommandState; use Throwable; +use function in_array; + /** * @internal */ @@ -24,7 +26,11 @@ public function __construct( public function __invoke(ScheduledTaskStarting $event): void { try { - $this->nightwatch->prepareForNextScheduledTask($event->task); + $this->nightwatch->prepareForNextScheduledTask(); + + if (in_array($event->task->command, $this->nightwatch->defaultVendorCommands(), true)) { + $this->nightwatch->dontSample(); + } } catch (Throwable $e) { $this->nightwatch->report($e, handled: true); } From 40dac6ae7600688a2a188407c7000af96f77f719 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Tue, 9 Dec 2025 11:08:51 +0800 Subject: [PATCH 03/19] Add facade methods --- src/Facades/Nightwatch.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Facades/Nightwatch.php b/src/Facades/Nightwatch.php index ed3bb561..eeb969a6 100644 --- a/src/Facades/Nightwatch.php +++ b/src/Facades/Nightwatch.php @@ -29,6 +29,8 @@ * @method static void rejectCacheKeys(array $keys) * @method static void captureDefaultVendorCacheKeys(bool $capture = true) * @method static array defaultVendorCacheKeys() + * @method static void captureDefaultVendorCommands(bool $capture = true) + * @method static array defaultVendorCommands() * @method static void rejectMail(callable $callback) * @method static void rejectNotifications(callable $callback) * @method static void rejectOutgoingRequests(callable $callback) From 24b7b4dbf9b32af6e1646e11fc3701f25ff97fd8 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Tue, 9 Dec 2025 12:43:20 +0800 Subject: [PATCH 04/19] Check flag --- src/Concerns/CapturesState.php | 8 +++++++- src/Hooks/ScheduledTaskStartingListener.php | 8 +------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index d94c61fb..d1a68970 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -119,8 +119,10 @@ public function configureRequestSampling(): void */ public function configureCommandSampling(string $command): void { - if (in_array($command, $this->defaultVendorCommands(), true)) { + if (! $this->captureDefaultVendorCommands && in_array($command, $this->defaultVendorCommands(), true)) { $this->dontSample(); + + return; } $this->sample(match (Compatibility::getSamplingFromContext(null)) { @@ -135,6 +137,10 @@ public function configureCommandSampling(string $command): void */ public function configureScheduledTaskSampling(Event $event): void { + if (! $this->captureDefaultVendorCommands && in_array($event->command, $this->defaultVendorCommands(), true)) { + $this->dontSample(); + } + $this->sample(rate: $this->scheduledTasksSampleRates[$event] ?? $this->config['sampling']['scheduled_tasks']); } diff --git a/src/Hooks/ScheduledTaskStartingListener.php b/src/Hooks/ScheduledTaskStartingListener.php index 61cbfe87..bd699038 100644 --- a/src/Hooks/ScheduledTaskStartingListener.php +++ b/src/Hooks/ScheduledTaskStartingListener.php @@ -7,8 +7,6 @@ use Laravel\Nightwatch\State\CommandState; use Throwable; -use function in_array; - /** * @internal */ @@ -26,11 +24,7 @@ public function __construct( public function __invoke(ScheduledTaskStarting $event): void { try { - $this->nightwatch->prepareForNextScheduledTask(); - - if (in_array($event->task->command, $this->nightwatch->defaultVendorCommands(), true)) { - $this->nightwatch->dontSample(); - } + $this->nightwatch->prepareForNextScheduledTask($event->task); } catch (Throwable $e) { $this->nightwatch->report($e, handled: true); } From b09d97126d9dc09a35464a7aecb2889f8f0e4817 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Tue, 9 Dec 2025 13:15:58 +0800 Subject: [PATCH 05/19] Add test --- src/Concerns/CapturesState.php | 15 +++++++-- tests/Unit/CliSamplingTest.php | 56 +++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index d1a68970..595f9352 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -48,6 +48,7 @@ use function memory_reset_peak_usage; use function preg_match; use function random_int; +use function str_contains; /** * @internal @@ -137,11 +138,19 @@ public function configureCommandSampling(string $command): void */ public function configureScheduledTaskSampling(Event $event): void { - if (! $this->captureDefaultVendorCommands && in_array($event->command, $this->defaultVendorCommands(), true)) { - $this->dontSample(); + $rate = $this->config['sampling']['scheduled_tasks']; + + if (! $this->captureDefaultVendorCommands) { + foreach ($this->defaultVendorCommands() as $vendorCommand) { + if (str_contains($event->command, $vendorCommand)) { + $rate = 0.0; + + break; + } + } } - $this->sample(rate: $this->scheduledTasksSampleRates[$event] ?? $this->config['sampling']['scheduled_tasks']); + $this->sample(rate: $this->scheduledTasksSampleRates[$event] ?? $rate); } /** diff --git a/tests/Unit/CliSamplingTest.php b/tests/Unit/CliSamplingTest.php index 38fdedda..72aaa903 100644 --- a/tests/Unit/CliSamplingTest.php +++ b/tests/Unit/CliSamplingTest.php @@ -12,6 +12,7 @@ use Laravel\Nightwatch\Compatibility; use Laravel\Nightwatch\Console\Sample; use Laravel\Nightwatch\Facades\Nightwatch; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\NullOutput; use Tests\TestCase; @@ -151,7 +152,7 @@ public function test_it_can_use_global_config_to_sample_scheduled_tasks(): void $ingest->forgetWrites(); } - $this->assertEqualsWithDelta(50, $writes, 10); + $this->assertEqualsWithDelta(50, $writes, 20); $this->assertCount(0, $this->core->ingest->buffer); } @@ -175,4 +176,57 @@ public function test_it_applies_individual_sample_rates_to_scheduled_tasks(): vo $this->assertSame(100, $writes); $this->assertCount(0, $this->core->ingest->buffer); } + + #[DataProvider('vendorCommands')] + public function test_it_samples_vendor_commands_separately(string $command): void + { + $ingest = $this->fakeIngest(); + Artisan::command($command, function () {}); + + $status = Artisan::handle($input = new StringInput($command), new NullOutput); + Artisan::terminate($input, $status); + + $ingest->assertWrittenTimes(0); + } + + #[DataProvider('vendorCommands')] + public function test_it_samples_vendor_scheduled_tasks_separately(string $command): void + { + $ingest = $this->fakeIngest(); + + $this->app[Schedule::class]->command($command)->everyMinute(); + + Artisan::call('schedule:run'); + + $this->assertCount(0, $ingest->writes()); + $this->assertCount(0, $this->core->ingest->buffer); + } + + #[DataProvider('vendorCommands')] + public function test_it_samples_vendor_scheduled_tasks_when_explicitly_sampled(string $command): void + { + $ingest = $this->fakeIngest(); + + $this->app[Schedule::class]->command($command)->everyMinute()->tap(Sample::rate(0.5)); + + $writes = 0; + for ($i = 0; $i < 100; $i++) { + Artisan::call('schedule:run'); + $writes += $ingest->writes()->count(); + $ingest->forgetWrites(); + } + + $this->assertEqualsWithDelta(50, $writes, 20); + $this->assertCount(0, $this->core->ingest->buffer); + } + + public static function vendorCommands(): iterable + { + yield ['model:prune']; + yield ['horizon:snapshot']; + yield ['horizon:status']; + yield ['passport:purge']; + yield ['sanctum:prune-expired']; + yield ['auth:clear-resets']; + } } From eef1781c2fe2ee8e93d81febba5b62d767f69136 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Tue, 9 Dec 2025 13:22:55 +0800 Subject: [PATCH 06/19] PHPStan --- src/Concerns/CapturesState.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index 595f9352..97a19979 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -141,8 +141,8 @@ public function configureScheduledTaskSampling(Event $event): void $rate = $this->config['sampling']['scheduled_tasks']; if (! $this->captureDefaultVendorCommands) { - foreach ($this->defaultVendorCommands() as $vendorCommand) { - if (str_contains($event->command, $vendorCommand)) { + foreach ($this->defaultVendorCommands() as $command) { + if (str_contains($event->command ?? '', $command)) { $rate = 0.0; break; From b955b3ea53651a56e8a1b31cb6d0bba288484c1d Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Tue, 9 Dec 2025 15:40:00 +0800 Subject: [PATCH 07/19] Change default commands --- src/Concerns/CapturesState.php | 6 ++---- tests/Unit/CliSamplingTest.php | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index 97a19979..d8e24916 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -169,12 +169,10 @@ public function captureDefaultVendorCommands(bool $capture = true): void public static function defaultVendorCommands(): array { return [ - 'auth:clear-resets', 'horizon:snapshot', 'horizon:status', - 'model:prune', - 'passport:purge', - 'sanctum:prune-expired', + 'queue:monitor', + 'schedule:list', ]; } diff --git a/tests/Unit/CliSamplingTest.php b/tests/Unit/CliSamplingTest.php index 72aaa903..16f26209 100644 --- a/tests/Unit/CliSamplingTest.php +++ b/tests/Unit/CliSamplingTest.php @@ -222,11 +222,9 @@ public function test_it_samples_vendor_scheduled_tasks_when_explicitly_sampled(s public static function vendorCommands(): iterable { - yield ['model:prune']; yield ['horizon:snapshot']; yield ['horizon:status']; - yield ['passport:purge']; - yield ['sanctum:prune-expired']; - yield ['auth:clear-resets']; + yield ['queue:monitor']; + yield ['schedule:list']; } } From 29293dcd8e213983e0e368b421a06afc1b5fb148 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Tue, 9 Dec 2025 15:58:31 +0800 Subject: [PATCH 08/19] Define command in tests --- tests/Unit/CliSamplingTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Unit/CliSamplingTest.php b/tests/Unit/CliSamplingTest.php index 16f26209..47507cf4 100644 --- a/tests/Unit/CliSamplingTest.php +++ b/tests/Unit/CliSamplingTest.php @@ -193,6 +193,7 @@ public function test_it_samples_vendor_commands_separately(string $command): voi public function test_it_samples_vendor_scheduled_tasks_separately(string $command): void { $ingest = $this->fakeIngest(); + Artisan::command($command, function () {}); $this->app[Schedule::class]->command($command)->everyMinute(); @@ -206,6 +207,7 @@ public function test_it_samples_vendor_scheduled_tasks_separately(string $comman public function test_it_samples_vendor_scheduled_tasks_when_explicitly_sampled(string $command): void { $ingest = $this->fakeIngest(); + Artisan::command($command, function () {}); $this->app[Schedule::class]->command($command)->everyMinute()->tap(Sample::rate(0.5)); From b8f5bb5f04e58841ec3d1a1b7bfa9e6bf2cf641e Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Wed, 10 Dec 2025 14:05:55 +0800 Subject: [PATCH 09/19] Test using different method --- tests/Unit/CliSamplingTest.php | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/tests/Unit/CliSamplingTest.php b/tests/Unit/CliSamplingTest.php index 47507cf4..557e22c3 100644 --- a/tests/Unit/CliSamplingTest.php +++ b/tests/Unit/CliSamplingTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Console\Output\NullOutput; use Tests\TestCase; +use function dirname; use function event; class CliSamplingTest extends TestCase @@ -28,6 +29,8 @@ protected function setUp(): void $this->forceCommandExecutionState(); parent::setUp(); + + $this->app->setBasePath(dirname($this->app->basePath())); } public function test_it_samples_job_attempts(): void @@ -132,7 +135,7 @@ public function test_it_resets_sampling_after_each_task(): void event(new CommandStarting('schedule:run', new StringInput(''), new NullOutput)); Nightwatch::dontSample(); - event(new ScheduledTaskStarting($this->app[Schedule::class]->call('php artisan inspire'))); + event(new ScheduledTaskStarting($this->app[Schedule::class]->command('inspire'))); $this->assertTrue(Nightwatch::sampling()); } @@ -181,7 +184,6 @@ public function test_it_applies_individual_sample_rates_to_scheduled_tasks(): vo public function test_it_samples_vendor_commands_separately(string $command): void { $ingest = $this->fakeIngest(); - Artisan::command($command, function () {}); $status = Artisan::handle($input = new StringInput($command), new NullOutput); Artisan::terminate($input, $status); @@ -192,34 +194,25 @@ public function test_it_samples_vendor_commands_separately(string $command): voi #[DataProvider('vendorCommands')] public function test_it_samples_vendor_scheduled_tasks_separately(string $command): void { - $ingest = $this->fakeIngest(); - Artisan::command($command, function () {}); - - $this->app[Schedule::class]->command($command)->everyMinute(); + event(new CommandStarting('schedule:run', new StringInput(''), new NullOutput)); - Artisan::call('schedule:run'); + event(new ScheduledTaskStarting($this->app[Schedule::class]->command($command)->everyMinute())); - $this->assertCount(0, $ingest->writes()); - $this->assertCount(0, $this->core->ingest->buffer); + $this->assertFalse(Nightwatch::sampling()); } #[DataProvider('vendorCommands')] public function test_it_samples_vendor_scheduled_tasks_when_explicitly_sampled(string $command): void { - $ingest = $this->fakeIngest(); - Artisan::command($command, function () {}); - - $this->app[Schedule::class]->command($command)->everyMinute()->tap(Sample::rate(0.5)); + event(new CommandStarting('schedule:run', new StringInput(''), new NullOutput)); - $writes = 0; + $samples = 0; for ($i = 0; $i < 100; $i++) { - Artisan::call('schedule:run'); - $writes += $ingest->writes()->count(); - $ingest->forgetWrites(); + event(new ScheduledTaskStarting($this->app[Schedule::class]->command($command)->everyMinute()->tap(Sample::rate(0.5)))); + $samples += (int) Nightwatch::sampling(); } - $this->assertEqualsWithDelta(50, $writes, 20); - $this->assertCount(0, $this->core->ingest->buffer); + $this->assertEqualsWithDelta(50, $samples, 20); } public static function vendorCommands(): iterable From bd837aabac1f80a04325f69b345d13aa0003c3d9 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Wed, 10 Dec 2025 14:25:04 +0800 Subject: [PATCH 10/19] Add queue --- tests/Unit/CliSamplingTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/CliSamplingTest.php b/tests/Unit/CliSamplingTest.php index 557e22c3..ba783bb1 100644 --- a/tests/Unit/CliSamplingTest.php +++ b/tests/Unit/CliSamplingTest.php @@ -219,7 +219,7 @@ public static function vendorCommands(): iterable { yield ['horizon:snapshot']; yield ['horizon:status']; - yield ['queue:monitor']; + yield ['queue:monitor default']; yield ['schedule:list']; } } From 997cc5524c5c81bec10c1343d932b301ee1959f4 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Fri, 12 Dec 2025 12:36:54 +0800 Subject: [PATCH 11/19] Ignore other commands --- src/Concerns/CapturesState.php | 9 ++++++++ src/Hooks/CommandStartingListener.php | 2 +- tests/Feature/Sensors/CommandSensorTest.php | 23 +++++++++++++++++++++ tests/Unit/CliSamplingTest.php | 20 ++++++++++++++++-- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index d8e24916..25509e35 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -169,9 +169,18 @@ public function captureDefaultVendorCommands(bool $capture = true): void public static function defaultVendorCommands(): array { return [ + 'auth:clear-resets', + 'config:cache', 'horizon:snapshot', 'horizon:status', + 'horizon:supervisor', + 'inertia:start-ssr', + 'invoke-serialized-closure', + 'model:prune', + 'nightwatch:agent', + 'nightwatch:status', 'queue:monitor', + 'reverb:start', 'schedule:list', ]; } diff --git a/src/Hooks/CommandStartingListener.php b/src/Hooks/CommandStartingListener.php index db0daf43..6ee65e77 100644 --- a/src/Hooks/CommandStartingListener.php +++ b/src/Hooks/CommandStartingListener.php @@ -53,7 +53,7 @@ public function __invoke(CommandStarting $event): void match ($event->command) { 'queue:work', 'queue:listen', 'horizon:work', 'vapor:work' => $this->registerJobHooks($event), 'schedule:run', 'schedule:work' => $this->registerScheduledTaskHooks(), - 'schedule:finish' => null, + 'help', 'inspire', 'schedule:finish' => null, default => $this->registerCommandHooks($event), }; } catch (Throwable $e) { diff --git a/tests/Feature/Sensors/CommandSensorTest.php b/tests/Feature/Sensors/CommandSensorTest.php index fc6c18a6..6766e0b2 100644 --- a/tests/Feature/Sensors/CommandSensorTest.php +++ b/tests/Feature/Sensors/CommandSensorTest.php @@ -13,6 +13,7 @@ use Illuminate\Support\Facades\DB; use Laravel\Nightwatch\Compatibility; use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\NullOutput; use Tests\TestCase; use function array_shift; @@ -285,6 +286,28 @@ public function test_it_ignores_schedule_finish_command(): void $this->assertSame(0, $status); $ingest->assertWrittenTimes(0); } + + public function test_it_ignores_inspire_command(): void + { + $ingest = $this->fakeIngest(); + + $status = Artisan::handle($input = new StringInput('inspire'), new NullOutput); + Artisan::terminate($input, $status); + + $this->assertSame(0, $status); + $ingest->assertWrittenTimes(0); + } + + public function test_it_ignores_help_command(): void + { + $ingest = $this->fakeIngest(); + + $status = Artisan::handle($input = new StringInput('help'), new NullOutput); + Artisan::terminate($input, $status); + + $this->assertSame(0, $status); + $ingest->assertWrittenTimes(0); + } } class ParentCommand extends Command diff --git a/tests/Unit/CliSamplingTest.php b/tests/Unit/CliSamplingTest.php index ba783bb1..5ce78201 100644 --- a/tests/Unit/CliSamplingTest.php +++ b/tests/Unit/CliSamplingTest.php @@ -19,6 +19,7 @@ use function dirname; use function event; +use function fake; class CliSamplingTest extends TestCase { @@ -133,9 +134,14 @@ public function test_it_pulls_sample_from_context_when_command_starting(): void public function test_it_resets_sampling_after_each_task(): void { event(new CommandStarting('schedule:run', new StringInput(''), new NullOutput)); + Artisan::command($command = fake()->name(), fn () => 0); Nightwatch::dontSample(); - event(new ScheduledTaskStarting($this->app[Schedule::class]->command('inspire'))); + event(new ScheduledTaskStarting($this->app[Schedule::class]->command($command))); + $this->assertTrue(Nightwatch::sampling()); + + Artisan::command($command = fake()->name(), fn () => 0); + event(new ScheduledTaskStarting($this->app[Schedule::class]->command($command))); $this->assertTrue(Nightwatch::sampling()); } @@ -184,6 +190,7 @@ public function test_it_applies_individual_sample_rates_to_scheduled_tasks(): vo public function test_it_samples_vendor_commands_separately(string $command): void { $ingest = $this->fakeIngest(); + Artisan::command($command, fn () => 0); $status = Artisan::handle($input = new StringInput($command), new NullOutput); Artisan::terminate($input, $status); @@ -217,9 +224,18 @@ public function test_it_samples_vendor_scheduled_tasks_when_explicitly_sampled(s public static function vendorCommands(): iterable { + yield ['auth:clear-resets']; + yield ['config:cache']; yield ['horizon:snapshot']; yield ['horizon:status']; - yield ['queue:monitor default']; + yield ['horizon:supervisor']; + yield ['inertia:start-ssr']; + yield ['invoke-serialized-closure']; + yield ['model:prune']; + yield ['nightwatch:agent']; + yield ['nightwatch:status']; + yield ['queue:monitor']; + yield ['reverb:start']; yield ['schedule:list']; } } From 08a79f27b60bebb6cbbabbe921d47df6f9fc33d1 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Fri, 12 Dec 2025 13:06:28 +0800 Subject: [PATCH 12/19] Test capturing enables sampling --- tests/Unit/CliSamplingTest.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/Unit/CliSamplingTest.php b/tests/Unit/CliSamplingTest.php index 5ce78201..7b4e3966 100644 --- a/tests/Unit/CliSamplingTest.php +++ b/tests/Unit/CliSamplingTest.php @@ -198,6 +198,20 @@ public function test_it_samples_vendor_commands_separately(string $command): voi $ingest->assertWrittenTimes(0); } + #[DataProvider('vendorCommands')] + public function test_it_samples_vendor_commands_when_enabled(string $command): void + { + $ingest = $this->fakeIngest(); + Artisan::command($command, fn () => 0); + + Nightwatch::captureDefaultVendorCommands(); + + $status = Artisan::handle($input = new StringInput($command), new NullOutput); + Artisan::terminate($input, $status); + + $ingest->assertWrittenTimes(1); + } + #[DataProvider('vendorCommands')] public function test_it_samples_vendor_scheduled_tasks_separately(string $command): void { @@ -208,6 +222,18 @@ public function test_it_samples_vendor_scheduled_tasks_separately(string $comman $this->assertFalse(Nightwatch::sampling()); } + #[DataProvider('vendorCommands')] + public function test_it_samples_vendor_scheduled_tasks_when_enabled(string $command): void + { + Nightwatch::captureDefaultVendorCommands(); + + event(new CommandStarting('schedule:run', new StringInput(''), new NullOutput)); + + event(new ScheduledTaskStarting($this->app[Schedule::class]->command($command)->everyMinute())); + + $this->assertTrue(Nightwatch::sampling()); + } + #[DataProvider('vendorCommands')] public function test_it_samples_vendor_scheduled_tasks_when_explicitly_sampled(string $command): void { From fc35d8ed75f2bac85289d6ad35795d1dc9ff8d38 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Mon, 15 Dec 2025 10:22:11 +0800 Subject: [PATCH 13/19] Use data provider --- tests/Feature/Sensors/CommandSensorTest.php | 29 ++++++--------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/tests/Feature/Sensors/CommandSensorTest.php b/tests/Feature/Sensors/CommandSensorTest.php index 6766e0b2..0fcf0574 100644 --- a/tests/Feature/Sensors/CommandSensorTest.php +++ b/tests/Feature/Sensors/CommandSensorTest.php @@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\DB; use Laravel\Nightwatch\Compatibility; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\NullOutput; use Tests\TestCase; @@ -276,37 +277,23 @@ public function test_it_child_commands_do_not_progress_the_modify_execution_stag $ingest->assertLatestWrite('cache-event:0.execution_stage', 'action'); } - public function test_it_ignores_schedule_finish_command(): void + #[DataProvider('vendorCommands')] + public function test_it_ignores_vendor_commands(string $command): void { $ingest = $this->fakeIngest(); - $status = Artisan::handle($input = new StringInput('schedule:finish 123')); + $status = Artisan::handle($input = new StringInput($command), new NullOutput); Artisan::terminate($input, $status); $this->assertSame(0, $status); $ingest->assertWrittenTimes(0); } - public function test_it_ignores_inspire_command(): void + public static function vendorCommands(): iterable { - $ingest = $this->fakeIngest(); - - $status = Artisan::handle($input = new StringInput('inspire'), new NullOutput); - Artisan::terminate($input, $status); - - $this->assertSame(0, $status); - $ingest->assertWrittenTimes(0); - } - - public function test_it_ignores_help_command(): void - { - $ingest = $this->fakeIngest(); - - $status = Artisan::handle($input = new StringInput('help'), new NullOutput); - Artisan::terminate($input, $status); - - $this->assertSame(0, $status); - $ingest->assertWrittenTimes(0); + yield ['help']; + yield ['inspire']; + yield ['schedule:finish 123']; } } From 9c6296260f6d8e36d4a88144791ea97ffbce3ba1 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Mon, 15 Dec 2025 10:29:42 +0800 Subject: [PATCH 14/19] Improve test naming --- tests/Unit/CliSamplingTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/CliSamplingTest.php b/tests/Unit/CliSamplingTest.php index 7b4e3966..cc93a69d 100644 --- a/tests/Unit/CliSamplingTest.php +++ b/tests/Unit/CliSamplingTest.php @@ -187,7 +187,7 @@ public function test_it_applies_individual_sample_rates_to_scheduled_tasks(): vo } #[DataProvider('vendorCommands')] - public function test_it_samples_vendor_commands_separately(string $command): void + public function test_it_does_not_sample_vendor_commands(string $command): void { $ingest = $this->fakeIngest(); Artisan::command($command, fn () => 0); @@ -213,7 +213,7 @@ public function test_it_samples_vendor_commands_when_enabled(string $command): v } #[DataProvider('vendorCommands')] - public function test_it_samples_vendor_scheduled_tasks_separately(string $command): void + public function test_it_does_not_sample_scheduled_vendor_commands(string $command): void { event(new CommandStarting('schedule:run', new StringInput(''), new NullOutput)); From 402709ab8db563b6f0ab63280d0aa54f2d9d8cbb Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Mon, 15 Dec 2025 10:29:51 +0800 Subject: [PATCH 15/19] Reduce false positive matching --- src/Concerns/CapturesState.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index 25509e35..e6d9d1c8 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -47,8 +47,10 @@ use function in_array; use function memory_reset_peak_usage; use function preg_match; +use function preg_split; use function random_int; -use function str_contains; +use function str_replace; +use function trim; /** * @internal @@ -140,14 +142,16 @@ public function configureScheduledTaskSampling(Event $event): void { $rate = $this->config['sampling']['scheduled_tasks']; - if (! $this->captureDefaultVendorCommands) { - foreach ($this->defaultVendorCommands() as $command) { - if (str_contains($event->command ?? '', $command)) { - $rate = 0.0; + $command = str_replace( + [Artisan::phpBinary(), Artisan::artisanBinary()], + '', + $event->command + ); - break; - } - } + $command = preg_split('/\s+/', trim($command), 2)[0] ?? ''; + + if (! $this->captureDefaultVendorCommands && in_array($command, $this->defaultVendorCommands(), true)) { + $rate = 0.0; } $this->sample(rate: $this->scheduledTasksSampleRates[$event] ?? $rate); From 9a70e05417b6b2e787b23f7cdf7687571b959c16 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Mon, 15 Dec 2025 10:32:39 +0800 Subject: [PATCH 16/19] Lint --- src/Concerns/CapturesState.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index e6d9d1c8..3609fc25 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -145,7 +145,7 @@ public function configureScheduledTaskSampling(Event $event): void $command = str_replace( [Artisan::phpBinary(), Artisan::artisanBinary()], '', - $event->command + $event->command ?? '' ); $command = preg_split('/\s+/', trim($command), 2)[0] ?? ''; From 963faf362ceb8eb2b505653c0cc9f62d9ab54728 Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Mon, 15 Dec 2025 11:57:55 +0800 Subject: [PATCH 17/19] Tweak default sample logic --- src/Concerns/CapturesState.php | 8 ++++---- tests/Unit/CliSamplingTest.php | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index 3609fc25..1cd4d3c8 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -140,8 +140,6 @@ public function configureCommandSampling(string $command): void */ public function configureScheduledTaskSampling(Event $event): void { - $rate = $this->config['sampling']['scheduled_tasks']; - $command = str_replace( [Artisan::phpBinary(), Artisan::artisanBinary()], '', @@ -151,10 +149,12 @@ public function configureScheduledTaskSampling(Event $event): void $command = preg_split('/\s+/', trim($command), 2)[0] ?? ''; if (! $this->captureDefaultVendorCommands && in_array($command, $this->defaultVendorCommands(), true)) { - $rate = 0.0; + $this->sample(0.0); + + return; } - $this->sample(rate: $this->scheduledTasksSampleRates[$event] ?? $rate); + $this->sample(rate: $this->scheduledTasksSampleRates[$event] ?? $this->config['sampling']['scheduled_tasks']); } /** diff --git a/tests/Unit/CliSamplingTest.php b/tests/Unit/CliSamplingTest.php index cc93a69d..d2b03382 100644 --- a/tests/Unit/CliSamplingTest.php +++ b/tests/Unit/CliSamplingTest.php @@ -237,6 +237,7 @@ public function test_it_samples_vendor_scheduled_tasks_when_enabled(string $comm #[DataProvider('vendorCommands')] public function test_it_samples_vendor_scheduled_tasks_when_explicitly_sampled(string $command): void { + Nightwatch::captureDefaultVendorCommands(); event(new CommandStarting('schedule:run', new StringInput(''), new NullOutput)); $samples = 0; From e4efb0b6b75219cd710275307f370dc23a72030e Mon Sep 17 00:00:00 2001 From: jradtilbrook Date: Mon, 15 Dec 2025 11:59:41 +0800 Subject: [PATCH 18/19] WIP --- src/Concerns/CapturesState.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index 1cd4d3c8..94c933be 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -140,18 +140,20 @@ public function configureCommandSampling(string $command): void */ public function configureScheduledTaskSampling(Event $event): void { - $command = str_replace( - [Artisan::phpBinary(), Artisan::artisanBinary()], - '', - $event->command ?? '' - ); + if (! $this->captureDefaultVendorCommands) { + $command = str_replace( + [Artisan::phpBinary(), Artisan::artisanBinary()], + '', + $event->command ?? '' + ); - $command = preg_split('/\s+/', trim($command), 2)[0] ?? ''; + $command = preg_split('/\s+/', trim($command), 2)[0] ?? ''; - if (! $this->captureDefaultVendorCommands && in_array($command, $this->defaultVendorCommands(), true)) { - $this->sample(0.0); + if (in_array($command, $this->defaultVendorCommands(), true)) { + $this->sample(0.0); - return; + return; + } } $this->sample(rate: $this->scheduledTasksSampleRates[$event] ?? $this->config['sampling']['scheduled_tasks']); From da63239a4a4f7c6bd01bb7d3d33d9087441ae987 Mon Sep 17 00:00:00 2001 From: Jarryd Tilbrook Date: Mon, 15 Dec 2025 12:26:55 +0800 Subject: [PATCH 19/19] Update src/Concerns/CapturesState.php Co-authored-by: Ryuta Hamasaki --- src/Concerns/CapturesState.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index 94c933be..9dcfc375 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -150,7 +150,7 @@ public function configureScheduledTaskSampling(Event $event): void $command = preg_split('/\s+/', trim($command), 2)[0] ?? ''; if (in_array($command, $this->defaultVendorCommands(), true)) { - $this->sample(0.0); + $this->dontSample(); return; }