From fab34098ca7592abdcaf02f466d2c65a9593b09e Mon Sep 17 00:00:00 2001 From: Alex Bowers Date: Tue, 26 Jul 2022 18:16:37 +0100 Subject: [PATCH] Handle metrics for databases --- config/metrics.php | 9 +++ src/CloudNativeServiceProvider.php | 71 +++++++++++++++---- .../UnsupportedAdapterException.php | 8 +++ 3 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 src/Exceptions/UnsupportedAdapterException.php diff --git a/config/metrics.php b/config/metrics.php index 4c0a1e0..6f6f8e5 100644 --- a/config/metrics.php +++ b/config/metrics.php @@ -1,6 +1,15 @@ env('METRICS_ADAPTER', 'memory'), + + // What metrics do we want to report on? + 'handle' => [ + 'database' => env('METRICS_HANDLE_DATABASE', true), + 'http' => env('METRICS_HANDLE_HTTP', true), + ], + // The route configuration controls which path the metrics data is served on 'route' => [ 'enabled' => env('ENABLE_METRICS', true), diff --git a/src/CloudNativeServiceProvider.php b/src/CloudNativeServiceProvider.php index b0ab13b..c738613 100644 --- a/src/CloudNativeServiceProvider.php +++ b/src/CloudNativeServiceProvider.php @@ -4,35 +4,32 @@ use Illuminate\Contracts\Config\Repository; use Illuminate\Contracts\Http\Kernel; +use Illuminate\Database\Events\QueryExecuted; use Illuminate\Routing\Router; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; +use Jobilla\CloudNative\Laravel\Exceptions\UnsupportedAdapterException; use Jobilla\CloudNative\Laravel\Http\Controllers\ServeMetrics; use Jobilla\CloudNative\Laravel\Http\Middleware\RecordPrometheusMetrics; use Prometheus\CollectorRegistry; use Prometheus\Storage\APC; +use Prometheus\Storage\APCng; +use Prometheus\Storage\InMemory; +use Prometheus\Storage\Redis; class CloudNativeServiceProvider extends ServiceProvider { + protected CollectorRegistry $registry; + public function register() { $this->mergeConfigFrom(__DIR__.'/../config/metrics.php', 'metrics'); - - $this->app->singleton(CollectorRegistry::class, function () { - return new CollectorRegistry(new APC()); - }); } public function boot(Repository $config, Kernel $kernel) { - $this->publishes([ - __DIR__.'/../config/logging.php' => config_path('logging.php'), - __DIR__.'/../config/metrics.php' => config_path('metrics.php'), - ], 'cloud-native-config'); - - $this->publishes([ - __DIR__.'/../config/logging.php' => config_path('logging.php'), - ], 'cloud-native-logging'); - $this->publishes([ __DIR__.'/../config/metrics.php' => config_path('metrics.php'), ], 'cloud-native-metrics'); @@ -41,11 +38,55 @@ public function boot(Repository $config, Kernel $kernel) __DIR__.'/../assets/Dockerfile' => base_path('Dockerfile'), ], 'dockerfile'); - if ($config->get('metrics.route.enabled')) { + $this->app->singleton(CollectorRegistry::class, function () use ($config) { + $adapter = $config->get('metrics.adapter', 'memory'); + + switch ($adapter) { + case 'redis': + return new CollectorRegistry(new Redis); + break; + case 'apc': + if (filter_var(ini_get('apcu.enable_cli'), FILTER_VALIDATE_BOOLEAN) === false && App::runningInConsole()) { + Log::warning('Metrics adapter temporarily turned to "memory" because apc is disabled in CLI. See: https://www.php.net/manual/en/apcu.configuration.php'); + return new CollectorRegistry(new InMemory); + } + + return new CollectorRegistry(new APC); + break; + case 'apcng': + return new CollectorRegistry(new APCng); + break; + case 'memory': + return new CollectorRegistry(new InMemory); + break; + default: + throw new UnsupportedAdapterException("Adapter `{$adapter}` is not supported."); + break; + } + }); + + $this->registry = $this->app->make(CollectorRegistry::class); + + if ($this->registry && $config->get('metrics.handle.database')) { + DB::listen(function(QueryExecuted $query) use ($config) { + $query_labels = ['query' => $query->sql, 'mode' => strtok($query->sql, ' ')]; + + $query_counter = $this->registry->getOrRegisterCounter('db', 'query_total', 'Counter of total queries', array_keys($query_labels)); + $query_counter->inc(array_values($query_labels)); + + $query_duration = $this->registry->getOrRegisterHistogram('db', 'query_duration_ms', 'Duration of query', array_keys($query_labels), $config->get('metrics.buckets')); + $query_duration->observe($query->time, array_values($query_labels)); + }); + } + + if ($this->registry && $config->get('metrics.handle.http')) { + $kernel->prependMiddleware(RecordPrometheusMetrics::class); + } + + if ($this->registry && $config->get('metrics.route.enabled')) { /** @var Router $router */ $router = $this->app['router']; $router->get($config->get('metrics.route.path'), ServeMetrics::class); - $kernel->prependMiddleware(RecordPrometheusMetrics::class); } } } \ No newline at end of file diff --git a/src/Exceptions/UnsupportedAdapterException.php b/src/Exceptions/UnsupportedAdapterException.php new file mode 100644 index 0000000..65bb7b5 --- /dev/null +++ b/src/Exceptions/UnsupportedAdapterException.php @@ -0,0 +1,8 @@ +