Skip to content

Commit

Permalink
Record the location of the slow query
Browse files Browse the repository at this point in the history
  • Loading branch information
jessarcher committed Oct 30, 2023
1 parent 941f16e commit b9c8ecf
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 17 deletions.
9 changes: 5 additions & 4 deletions database/migrations/2023_06_07_000001_create_pulse_tables.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ public function up(): void
$table->datetime('date');
$table->string('user_id')->nullable();
$table->text('sql');
$table->char('sql_hash', 16)->charset('binary')->virtualAs('UNHEX(MD5(`sql`))');
$table->text('location');
$table->char('sql_location_hash', 16)->charset('binary')->virtualAs('UNHEX(MD5(CONCAT(`sql`, `location`)))');
$table->unsignedInteger('duration');

$table->index(['sql_hash']); // slow_queries
$table->index(['sql_location_hash']); // slow_queries
$table->index([
'date', // slow_queries, trim
'sql_hash', // slow_queries
'date', // slow_queries, trim
'sql_location_hash', // slow_queries
'duration', // slow_queries
]);
});
Expand Down
2 changes: 1 addition & 1 deletion dist/pulse.css

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions resources/views/livewire/slow-queries.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,14 @@ class="min-h-full flex flex-col"
<tr wire:key="{{ md5($query->sql) }}">
<x-pulse::td class="!p-0 truncate max-w-[1px]">
<div class="relative">
<code class="bg-gray-700 dark:bg-gray-800 py-4 rounded-md text-gray-100 block text-xs whitespace-nowrap overflow-x-auto [scrollbar-color:theme(colors.gray.500)_transparent] [scrollbar-width:thin]">
<span class="px-3">{!! $sqlFormatter->highlight($query->sql) !!}</span>
</code>
<div class="bg-gray-700 dark:bg-gray-800 py-4 rounded-md text-gray-100 block text-xs whitespace-nowrap overflow-x-auto [scrollbar-color:theme(colors.gray.500)_transparent] [scrollbar-width:thin]">
<code class="px-3">{!! $sqlFormatter->highlight($query->sql) !!}</code>
@if ($query->location)
<p class="px-3 mt-3 text-xs leading-none text-gray-400 dark:text-gray-500">
{{ $query->location }}
</p>
@endif
</div>
<div class="absolute top-0 right-0 bottom-0 rounded-r-md w-3 bg-gradient-to-r from-transparent to-gray-700 dark:to-gray-800 pointer-events-none"></div>
</div>
</x-pulse::td>
Expand Down
12 changes: 8 additions & 4 deletions src/Queries/SlowQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,17 @@ public function __invoke(Interval $interval): Collection
'count',
'slowest',
'sql' => fn (Builder $query) => $query->select('sql')
->from('pulse_slow_queries', as: 'child')
->whereRaw('`child`.`sql_hash` = `parent`.`sql_hash`')
->from('pulse_slow_queries', as: 'child1')
->whereRaw('`child1`.`sql_location_hash` = `parent`.`sql_location_hash`')
->limit(1),
])->fromSub(fn (Builder $query) => $query->selectRaw('`sql_hash`, MAX(`duration`) as `slowest`, COUNT(*) as `count`')
'location' => fn (Builder $query) => $query->select('location')
->from('pulse_slow_queries', as: 'child2')
->whereRaw('`child2`.`sql_location_hash` = `parent`.`sql_location_hash`')
->limit(1),
])->fromSub(fn (Builder $query) => $query->selectRaw('`sql_location_hash`, MAX(`duration`) as `slowest`, COUNT(*) as `count`')
->from('pulse_slow_queries')
->where('date', '>', $now->subSeconds((int) $interval->totalSeconds)->toDateTimeString())
->groupBy('sql_hash')
->groupBy('sql_location_hash')
->orderByDesc('slowest')
->orderByDesc('count')
->limit(101), as: 'parent')
Expand Down
36 changes: 36 additions & 0 deletions src/Recorders/SlowQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Carbon\CarbonImmutable;
use Illuminate\Config\Repository;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\Str;
use Laravel\Pulse\Entry;
use Laravel\Pulse\Pulse;

Expand Down Expand Up @@ -56,8 +57,43 @@ public function record(QueryExecuted $event): ?Entry
return new Entry($this->table, [
'date' => $now->subMilliseconds((int) $event->time)->toDateTimeString(),
'sql' => $event->sql,
'location' => $this->getLocation(),
'duration' => (int) $event->time,
'user_id' => $this->pulse->authenticatedUserIdResolver(),
]);
}

/**
* Get the location of the query.
*/
protected function getLocation(): string
{
$backtrace = collect(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS));

$frame = $backtrace->firstWhere(fn (array $frame) => isset($frame['file']) && ! $this->isInternalFile($frame['file']));

if ($frame === null) {
return '';
}

return $this->formatLocation($frame['file'] ?? 'unknown', $frame['line'] ?? null);
}

/**
* Determine whether a file should be considered internal.
*/
protected function isInternalFile(string $file): bool
{
return Str::startsWith($file, base_path('vendor/laravel/pulse'))
|| Str::startsWith($file, base_path('vendor/laravel/framework'))
|| $file === public_path('index.php');
}

/**
* Format a file and line number and strip the base path.
*/
protected function formatLocation(string $file, ?int $line): string
{
return Str::replaceFirst(base_path('/'), '', $file).(is_int($line) ? (':'.$line) : '');
}
}
10 changes: 5 additions & 5 deletions tests/Feature/Livewire/SlowQueriesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@

it('renders slow queries', function () {
Pulse::ignore(fn () => DB::table('pulse_slow_queries')->insert([
['date' => '2000-01-02 03:04:05', 'sql' => 'select * from `users`', 'duration' => 1234],
['date' => '2000-01-02 03:04:05', 'sql' => 'select * from `users`', 'duration' => 2468],
['date' => '2000-01-02 03:04:05', 'sql' => 'select * from `users` where `id` = ?', 'duration' => 1234],
['date' => '2000-01-02 03:04:05', 'sql' => 'select * from `users`', 'location' => 'app/Foo.php:123', 'duration' => 1234],
['date' => '2000-01-02 03:04:05', 'sql' => 'select * from `users`', 'location' => 'app/Foo.php:123', 'duration' => 2468],
['date' => '2000-01-02 03:04:05', 'sql' => 'select * from `users` where `id` = ?', 'location' => 'app/Bar.php:456', 'duration' => 1234],
]));
Carbon::setTestNow('2000-01-02 03:04:15');

Livewire::test(SlowQueries::class, ['lazy' => false])
->assertViewHas('time')
->assertViewHas('runAt', '2000-01-02 03:04:15')
->assertViewHas('slowQueries', collect([
(object) ['sql' => 'select * from `users`', 'count' => 2, 'slowest' => 2468],
(object) ['sql' => 'select * from `users` where `id` = ?', 'count' => 1, 'slowest' => 1234],
(object) ['sql' => 'select * from `users`', 'location' => 'app/Foo.php:123', 'count' => 2, 'slowest' => 2468],
(object) ['sql' => 'select * from `users` where `id` = ?', 'location' => 'app/Bar.php:456', 'count' => 1, 'slowest' => 1234],
]));
});

0 comments on commit b9c8ecf

Please sign in to comment.