diff --git a/src/Commands/PruneCommand.php b/src/Commands/PruneCommand.php new file mode 100644 index 00000000..a15e56c8 --- /dev/null +++ b/src/Commands/PruneCommand.php @@ -0,0 +1,47 @@ +confirmToProceed()) { + return Command::FAILURE; + } + + $pulse->prune( + now()->subHours( + (int) $this->option('hours') + ) + ); + + return Command::SUCCESS; + } +} diff --git a/src/Contracts/Storage.php b/src/Contracts/Storage.php index 40b81f49..95a86347 100644 --- a/src/Contracts/Storage.php +++ b/src/Contracts/Storage.php @@ -3,6 +3,7 @@ namespace Laravel\Pulse\Contracts; use Carbon\CarbonInterval; +use DateTimeInterface; use Illuminate\Support\Collection; interface Storage @@ -19,6 +20,14 @@ public function store(Collection $items): void; */ public function trim(): void; + /** + * Prune the storage. + * + * @param \DateTimeInterface $before + * @return void + */ + public function prune(DateTimeInterface $before): void; + /** * Purge the storage. * diff --git a/src/PulseServiceProvider.php b/src/PulseServiceProvider.php index 8724c6ab..149f9ba9 100644 --- a/src/PulseServiceProvider.php +++ b/src/PulseServiceProvider.php @@ -234,6 +234,7 @@ protected function registerCommands(): void Commands\CheckCommand::class, Commands\RestartCommand::class, Commands\ClearCommand::class, + Commands\PruneCommand::class, ]); AboutCommand::add('Pulse', fn () => [ diff --git a/src/Storage/DatabaseStorage.php b/src/Storage/DatabaseStorage.php index 3cabc64f..e6311d2b 100644 --- a/src/Storage/DatabaseStorage.php +++ b/src/Storage/DatabaseStorage.php @@ -5,6 +5,7 @@ use Carbon\CarbonImmutable; use Carbon\CarbonInterval; use Closure; +use DateTimeInterface; use Illuminate\Contracts\Config\Repository; use Illuminate\Database\Connection; use Illuminate\Database\DatabaseManager; @@ -146,6 +147,37 @@ public function trim(): void ->delete()); } + /** + * Prune the storage. + * + * @param \DateTimeInterface $before + * @return void + */ + public function prune(DateTimeInterface $before): void + { + $before = CarbonImmutable::parse($before); + + $this->connection() + ->table('pulse_values') + ->where('timestamp', '<', $before->getTimestamp()) + ->delete(); + + $this->connection() + ->table('pulse_entries') + ->where('timestamp', '<', $before->getTimestamp()) + ->delete(); + + $this->connection() + ->table('pulse_aggregates') + ->distinct() + ->pluck('period') + ->each(fn (int $period) => $this->connection() + ->table('pulse_aggregates') + ->where('period', $period) + ->where('bucket', '<', $before->subMinutes($period)->getTimestamp()) + ->delete()); + } + /** * Purge the storage. * diff --git a/tests/Feature/Commands/PruneCommandTest.php b/tests/Feature/Commands/PruneCommandTest.php new file mode 100644 index 00000000..2f0018e3 --- /dev/null +++ b/tests/Feature/Commands/PruneCommandTest.php @@ -0,0 +1,49 @@ +count())->toBe(0); + expect(DB::table('pulse_entries')->count())->toBe(0); + expect(DB::table('pulse_aggregates')->count())->toBe(0); +}); + +it('prune entries from the suggested before hours', function () { + // Entries will be pruned + Date::setTestNow('2000-01-01 00:00:04'); + Pulse::record('foo', 'xxxx', 1); + Date::setTestNow('2000-01-01 00:00:05'); + Pulse::record('bar', 'xxxx', 1); + + // Entries will be kept + Date::setTestNow("2000-01-07 00:00:00"); + Pulse::record('baz', 'xxxx', 1); + Pulse::ingest(); + + Pulse::stopRecording(); + Date::setTestNow('2000-01-09 00:00:00'); + + # Act + Artisan::call("pulse:prune --hours=168"); + + expect(DB::table('pulse_entries')->pluck('type')->all())->toBe(['baz']); +}); diff --git a/tests/StorageFake.php b/tests/StorageFake.php index 5f9c815f..2c69f774 100644 --- a/tests/StorageFake.php +++ b/tests/StorageFake.php @@ -3,6 +3,7 @@ namespace Tests; use Carbon\CarbonInterval; +use DateTimeInterface; use Illuminate\Support\Collection; use Laravel\Pulse\Contracts\Storage; @@ -34,6 +35,17 @@ public function trim(): void $this->stored = $this->stored->reject(fn ($record) => $record->timestamp <= now()->subWeek()->timestamp); } + /** + * Prune the storage. + * + * @param \DateTimeInterface $before + * @return void + */ + public function prune(DateTimeInterface $before): void + { + $this->stored = $this->stored->reject(fn ($record) => $record->timestamp <= $before->getTimestamp()); + } + /** * Purge the storage. *