Install the package:
composer require ascetic-soft/rowcast-profiler
Wrap your Rowcast connection in a profiler (e.g., in a service provider or bootstrap file):
use AsceticSoft\Rowcast\Connection;
use AsceticSoft\RowcastProfiler\ConnectionProfiler;
use AsceticSoft\RowcastProfiler\InMemoryQueryProfileStore;
use AsceticSoft\RowcastProfiler\DefaultParameterSanitizer;
use AsceticSoft\RowcastProfiler\RowcastProfiler;
$innerConnection = Connection::create('sqlite::memory:');
$store = new InMemoryQueryProfileStore();
$sanitizer = new DefaultParameterSanitizer();
$profiler = new RowcastProfiler($store, $sanitizer, slowQueryThresholdMs: 50.0);
$profiledConnection = new ConnectionProfiler($innerConnection, $profiler);
Use the profiled connection as a drop-in replacement for your existing Rowcast connection.
$profiles = $store->getProfiles();
foreach ($profiles as $profile) {
if ($profile->durationMs > 200) {
echo "Slow query: " . $profile->sql . " (" . $profile->durationMs . "ms)\n";
print_r($profile->parameters); // Sanitized params
}
}
Register the profiler in a service provider:
public function register()
{
$this->app->singleton(AsceticSoft\Rowcast\ConnectionInterface::class, function ($app) {
$inner = $app->make(AsceticSoft\Rowcast\Connection::class);
$profiler = new RowcastProfiler(
new InMemoryQueryProfileStore(),
new DefaultParameterSanitizer(),
slowQueryThresholdMs: 100.0
);
return new ConnectionProfiler($inner, $profiler);
});
}
Access profiles via a command:
use Illuminate\Console\Command;
use AsceticSoft\RowcastProfiler\InMemoryQueryProfileStore;
class RowcastProfilerCommand extends Command
{
protected $signature = 'rowcast:profile';
protected $description = 'List slow Rowcast queries';
public function handle(InMemoryQueryProfileStore $store)
{
$profiles = $store->getProfiles();
foreach ($profiles as $profile) {
if ($profile->durationMs > 50) {
$this->line("<comment>Slow:</comment> {$profile->durationMs}ms - {$profile->sql}");
}
}
}
}
Enable profiling in config/app.php or a service provider:
$this->app->bind(
AsceticSoft\Rowcast\ConnectionInterface::class,
fn($app) => new ConnectionProfiler(
$app->make(AsceticSoft\Rowcast\Connection::class),
new RowcastProfiler(
new InMemoryQueryProfileStore(),
new DefaultParameterSanitizer(),
slowQueryThresholdMs: 20.0 // Catch slow queries early
)
)
);
Run a command to inspect profiles:
php artisan rowcast:profile
Optimize based on output (e.g., add indexes, denormalize data).
Add a CI step to fail builds with slow queries:
# .github/workflows/ci.yml
- name: Check Rowcast query performance
run: |
php artisan rowcast:profile --fail-if-slow
Create a custom command to enforce thresholds:
// app/Console/Commands/CheckRowcastQueries.php
public function handle()
{
$profiles = $store->getProfiles();
$slowQueries = collect($profiles)->filter(fn($p) => $p->durationMs > 100);
if ($slowQueries->count() > 0) {
$this->error("Slow queries detected:");
foreach ($slowQueries as $profile) {
$this->line("<error>• {$profile->durationMs}ms: {$profile->sql}</error>");
}
exit(1);
}
}
Extend Laravel Debugbar’s data collector:
use AsceticSoft\RowcastProfiler\InMemoryQueryProfileStore;
use LaravelDebugbar\ComponentCollector\Collector;
class RowcastProfilerCollector extends Collector
{
public function __construct(InMemoryQueryProfileStore $store)
{
$this->store = $store;
}
public function getData()
{
return [
'queries' => $this->store->getProfiles()->map(function ($profile) {
return [
'sql' => $profile->sql,
'duration' => $profile->durationMs,
'params' => $profile->parameters,
];
}),
];
}
}
Register the collector in a service provider:
Debugbar::collector(new RowcastProfilerCollector($store));
Implement a database-backed store:
use AsceticSoft\RowcastProfiler\QueryProfileStore;
use AsceticSoft\RowcastProfiler\QueryProfile;
class DatabaseQueryProfileStore implements QueryProfileStore
{
public function addProfile(QueryProfile $profile)
{
DB::table('query_profiles')->insert([
'sql' => $profile->sql,
'duration_ms' => $profile->durationMs,
'parameters' => json_encode($profile->parameters),
'created_at' => now(),
]);
}
public function getProfiles(): array
{
return DB::table('query_profiles')
->orderBy('duration_ms', 'desc')
->get()
->map(function ($record) {
return new QueryProfile(
$record->sql,
$record->duration_ms,
json_decode($record->parameters, true),
$record->created_at
);
})
->toArray();
}
}
Use it in the profiler:
$profiler = new RowcastProfiler(
new DatabaseQueryProfileStore(),
new DefaultParameterSanitizer(),
slowQueryThresholdMs: 50.0
);
Enable profiling only in specific environments:
$profiler = config('app.env') === 'local'
? new RowcastProfiler(
new InMemoryQueryProfileStore(),
new DefaultParameterSanitizer(),
slowQueryThresholdMs: 50.0
)
: null;
$connection = $profiler
? new ConnectionProfiler($inner, $profiler)
: $inner;
Toggle via config:
// config/rowcast.php
'profiler' => [
'enabled' => env('ROWCAST_PROFILER_ENABLED', false),
'slow_threshold_ms' => 50.0,
],
Parameter Sanitization Overhead
DefaultParameterSanitizer may add noticeable overhead if parameters are complex (e.g., nested arrays, objects).$profiler = new RowcastProfiler($store, null, slowQueryThresholdMs: 50.0);
InMemoryQueryProfileStore Limitations
Thread Safety
InMemoryQueryProfileStore is not thread-safe. Concurrent writes (e.g., in CLI + web) may corrupt data.if (app()->runningInConsole()) {
$connection = $inner; // Skip profiling in CLI
}
Error Handling Gaps
$profiler = new Row
How can I help you explore Laravel packages today?