diff --git a/README.md b/README.md index 2a2a1ef61..9e0fe68e1 100644 --- a/README.md +++ b/README.md @@ -329,6 +329,13 @@ Once installed, you can use the following configuration data: extension="/path/to/tideways/tideways_xhprof.so" ``` +Monitoring +========== + +[Prometheus](https://prometheus.io) metrics suitable for monitoring service +health are exposed on `/metrics`. (This currently only works if using PDO for +storage.) + Releases / Changelog ==================== diff --git a/src/Xhgui/Controller/Metrics.php b/src/Xhgui/Controller/Metrics.php new file mode 100644 index 000000000..ac4f9f8a3 --- /dev/null +++ b/src/Xhgui/Controller/Metrics.php @@ -0,0 +1,40 @@ +searcher = $searcher; + } + + public function metrics() + { + $request = $this->app->request(); + $response = $this->app->response(); + + $stats = $this->searcher->stats(); + + $body = "# HELP xhgui_profiles_total Number of profiles collected.\n"; + $body .= "# TYPE xhgui_profiles_total gauge\n"; + $body .= sprintf("xhgui_profiles_total %0.1F\n\n", $stats['profiles']); + + $body .= "# HELP xhgui_profile_bytes_total Size of profiles collected.\n"; + $body .= "# TYPE xhgui_profile_bytes_total gauge\n"; + $body .= sprintf("xhgui_profile_bytes_total %0.1F\n\n", $stats['bytes']); + + $body .= "# HELP xhgui_latest_profile_seconds UNIX timestamp of most recent profile.\n"; + $body .= "# TYPE xhgui_latest_profile_seconds gauge\n"; + $body .= sprintf("xhgui_latest_profile_seconds %0.1F\n", $stats['latest']); + + $response->body($body); + $response['Content-Type'] = 'text/plain; version=0.0.4'; + } +} diff --git a/src/Xhgui/Searcher/Interface.php b/src/Xhgui/Searcher/Interface.php index f08ee06b9..012555deb 100644 --- a/src/Xhgui/Searcher/Interface.php +++ b/src/Xhgui/Searcher/Interface.php @@ -124,4 +124,12 @@ public function getAllWatches(); * @return void */ public function truncateWatches(); + + /** + * Return statistics about the size of all profiling data. + * + * @return array Array of stats. + */ + public function stats(); + } diff --git a/src/Xhgui/Searcher/Mongo.php b/src/Xhgui/Searcher/Mongo.php index 6a6a3b642..27246fabe 100644 --- a/src/Xhgui/Searcher/Mongo.php +++ b/src/Xhgui/Searcher/Mongo.php @@ -329,4 +329,16 @@ private function _wrap($data) } return $results; } + + /** + * {@inheritdoc} + */ + public function stats() + { + return [ + 'profiles' => 0, + 'latest' => 0, + 'bytes' => 0, + ]; + } } diff --git a/src/Xhgui/Searcher/Pdo.php b/src/Xhgui/Searcher/Pdo.php index 07bebce6a..f3e4857f2 100644 --- a/src/Xhgui/Searcher/Pdo.php +++ b/src/Xhgui/Searcher/Pdo.php @@ -250,4 +250,30 @@ public function getAllWatches() public function truncateWatches() { } + + /** + * {@inheritdoc} + */ + public function stats() + { + $stmt = $this->pdo->query(" + SELECT + COUNT(*) AS profiles, + MAX(request_ts) AS latest, + SUM(LENGTH(profile)) AS bytes + FROM {$this->table} + ", PDO::FETCH_ASSOC); + + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (false === $row) { + $row = array( + 'profiles' => 0, + 'latest' => 0, + 'bytes' => 0, + ); + } + + return $row; + } } diff --git a/src/Xhgui/ServiceContainer.php b/src/Xhgui/ServiceContainer.php index 7f44927ab..59b0da30e 100644 --- a/src/Xhgui/ServiceContainer.php +++ b/src/Xhgui/ServiceContainer.php @@ -147,6 +147,10 @@ protected function _controllers() $this['importController'] = function ($c) { return new Xhgui_Controller_Import($c['app'], $c['saver']); }; + + $this['metricsController'] = function ($c) { + return new Xhgui_Controller_Metrics($c['app'], $c['searcher']); + }; } } diff --git a/src/routes.php b/src/routes.php index 6c37abde8..58e8cfc93 100644 --- a/src/routes.php +++ b/src/routes.php @@ -124,3 +124,8 @@ $app->get('/waterfall/data', function () use ($di) { $di['waterfallController']->query(); })->name('waterfall.data'); + +// Metrics +$app->get('/metrics', function () use ($di, $app) { + $di['metricsController']->metrics(); +})->name('metrics');