mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
feat(PHP): add aurweb Prometheus metrics
Along with this initial requests metric implementation, we also now serve the `/metrics` route, which grabs request metrics out of cache and renders them properly for Prometheus. **NOTE** Metrics are only enabled when the aurweb system admin has enabled caching by configuring `options.cache` correctly in `$AUR_CONFIG`. Otherwise, an error is logged about no cache being configured. New dependencies have been added which require the use of `composer`. See `INSTALL` for the dependency section in regards to composer dependencies and how to install them properly for aurweb. Metrics are in the following forms: aurweb_http_requests_count(method="GET",route="/some_route") aurweb_api_requests_count(method="GET",route="/rpc",type="search") This should allow us to search through the requests for specific routes and queries. Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
4e5b67f0a6
commit
986fa9ee30
5 changed files with 183 additions and 2 deletions
8
INSTALL
8
INSTALL
|
@ -49,9 +49,15 @@ read the instructions below.
|
|||
|
||||
# pacman -S python-mysql-connector python-pygit2 python-srcinfo python-sqlalchemy \
|
||||
python-bleach python-markdown python-alembic python-jinja \
|
||||
python-itsdangerous python-authlib python-httpx hypercorn
|
||||
python-itsdangerous python-authlib python-httpx hypercorn \
|
||||
composer
|
||||
# python3 setup.py install
|
||||
|
||||
4a) Install `composer` dependencies while inside of aurweb's root:
|
||||
|
||||
$ cd /path/to/aurweb
|
||||
/path/to/aurweb $ composer require promphp/prometheus_client_php
|
||||
|
||||
5) Create a new MySQL database and a user and import the aurweb SQL schema:
|
||||
|
||||
$ python -m aurweb.initdb
|
||||
|
|
|
@ -3,10 +3,39 @@ set_include_path(get_include_path() . PATH_SEPARATOR . '../lib');
|
|||
|
||||
include_once("aur.inc.php");
|
||||
include_once("pkgfuncs.inc.php");
|
||||
include_once("cachefuncs.inc.php");
|
||||
include_once("metricfuncs.inc.php");
|
||||
|
||||
$path = $_SERVER['PATH_INFO'];
|
||||
$tokens = explode('/', $path);
|
||||
|
||||
$query_string = $_SERVER['QUERY_STRING'];
|
||||
|
||||
// If no options.cache is configured, we no-op metric storage operations.
|
||||
$is_cached = defined('EXTENSION_LOADED_APC') || defined('EXTENSION_LOADED_MEMCACHE');
|
||||
if ($is_cached) {
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
// We'll always add +1 to our total request count to this $path,
|
||||
// unless this path == /metrics.
|
||||
if ($path !== "/metrics")
|
||||
add_metric("http_requests_count", $method, $path);
|
||||
|
||||
// Extract $type out of $query_string, if we can.
|
||||
$type = null;
|
||||
$query = array();
|
||||
if ($query_string)
|
||||
parse_str($query_string, $query);
|
||||
$type = $query['type'];
|
||||
|
||||
// Only store RPC metrics for valid types.
|
||||
$good_types = [
|
||||
"info", "multiinfo", "search", "msearch",
|
||||
"suggest", "suggest-pkgbase", "get-comment-form"
|
||||
];
|
||||
if ($path === "/rpc" && in_array($type, $good_types))
|
||||
add_metric("api_requests_count", $method, $path, $type);
|
||||
}
|
||||
|
||||
if (config_get_bool('options', 'enable-maintenance') && (empty($tokens[1]) || ($tokens[1] != "css" && $tokens[1] != "images"))) {
|
||||
if (!in_array($_SERVER['REMOTE_ADDR'], explode(" ", config_get('options', 'maintenance-exceptions')))) {
|
||||
header("HTTP/1.0 503 Service Unavailable");
|
||||
|
|
16
web/html/metrics.php
Normal file
16
web/html/metrics.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* Provide some variable Prometheus metrics. A new requests.route type
|
||||
* gets created for each request made and we keep a count using our
|
||||
* existing Memcached or APC configurable cache, with route
|
||||
* = {request_uri}?{query_string}.
|
||||
*
|
||||
* TL;DR -- The 'requests' counter is used to give variable requests
|
||||
* based on their request_uris and query_strings.
|
||||
**/
|
||||
include_once('metricfuncs.inc.php');
|
||||
|
||||
// Render metrics based on options.cache storage.
|
||||
render_metrics();
|
||||
|
||||
?>
|
129
web/lib/metricfuncs.inc.php
Normal file
129
web/lib/metricfuncs.inc.php
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
set_include_path(get_include_path() . PATH_SEPARATOR . '../lib');
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
include_once("cachefuncs.inc.php");
|
||||
|
||||
use \Prometheus\Storage\InMemory;
|
||||
use \Prometheus\CollectorRegistry;
|
||||
use \Prometheus\RenderTextFormat;
|
||||
|
||||
// We always pass through an InMemory cache. This means that
|
||||
// metrics are only good while the php-fpm service is running
|
||||
// and will start again at 0 if it's restarted.
|
||||
$registry = new CollectorRegistry(new InMemory());
|
||||
|
||||
function add_metric($anchor, $method, $path, $type = null) {
|
||||
|
||||
global $registry;
|
||||
// We keep track of which routes we're interested in by storing
|
||||
// a JSON-encoded list into the "prometheus_metrics" key,
|
||||
// with each item being a JSON-encoded associative array
|
||||
// in the form: {'path': <route>, 'query_string': <query_string>}.
|
||||
$metrics = get_cache_value("prometheus_metrics");
|
||||
$metrics = $metrics ? json_decode($metrics) : array();
|
||||
|
||||
$key = "$path:$type";
|
||||
|
||||
// If the current request $path isn't yet in $metrics create
|
||||
// a new assoc array for it and push it into $metrics.
|
||||
if (!in_array($key, $metrics)) {
|
||||
$data = array(
|
||||
'anchor' => $anchor,
|
||||
'method' => $method,
|
||||
'path' => $path,
|
||||
'type' => $type
|
||||
);
|
||||
array_push($metrics, json_encode($data));
|
||||
}
|
||||
|
||||
// Cache-wise, we also store the count values of each route
|
||||
// through the "prometheus:<route>" key. Grab the cache value
|
||||
// representing the current $path we're on (defaulted to 1).
|
||||
$count = get_cache_value("prometheus:$key");
|
||||
$count = $count ? $count + 1 : 1;
|
||||
|
||||
$labels = ["method", "route"];
|
||||
if ($type)
|
||||
array_push($labels, "type");
|
||||
|
||||
$gauge = $registry->getOrRegisterGauge(
|
||||
'aurweb',
|
||||
$anchor,
|
||||
'A metric count for the aurweb platform.',
|
||||
$labels
|
||||
);
|
||||
|
||||
$label_values = [$data['method'], $data['path']];
|
||||
if ($type)
|
||||
array_push($label_values, $type);
|
||||
|
||||
$gauge->set($count, $label_values);
|
||||
|
||||
// Update cache values.
|
||||
set_cache_value("prometheus:$key", $count, 0);
|
||||
set_cache_value("prometheus_metrics", json_encode($metrics), 0);
|
||||
|
||||
}
|
||||
|
||||
function render_metrics() {
|
||||
if (!defined('EXTENSION_LOADED_APC') && !defined('EXTENSION_LOADED_MEMCACHE')) {
|
||||
error_log("The /metrics route requires a valid 'options.cache' "
|
||||
. "configuration; no cache is configured.");
|
||||
return http_response_code(417); // EXPECTATION_FAILED
|
||||
}
|
||||
|
||||
global $registry;
|
||||
|
||||
// First, we grab the set of metrics we're interested in in the
|
||||
// form of a cached JSON list, if we can.
|
||||
$metrics = get_cache_value("prometheus_metrics");
|
||||
if (!$metrics)
|
||||
$metrics = array();
|
||||
else
|
||||
$metrics = json_decode($metrics);
|
||||
|
||||
// Now, we walk through each of those list values one by one,
|
||||
// which happen to be JSON-serialized associative arrays,
|
||||
// and process each metric via its associative array's contents:
|
||||
// The route path and the query string.
|
||||
// See web/html/index.php for the creation of such metrics.
|
||||
foreach ($metrics as $metric) {
|
||||
$data = json_decode($metric, true);
|
||||
|
||||
$anchor = $data['anchor'];
|
||||
$path = $data['path'];
|
||||
$type = $data['type'];
|
||||
$key = "$path:$type";
|
||||
|
||||
$labels = ["method", "route"];
|
||||
if ($type)
|
||||
array_push($labels, "type");
|
||||
|
||||
$count = get_cache_value("prometheus:$key");
|
||||
$gauge = $registry->getOrRegisterGauge(
|
||||
'aurweb',
|
||||
$anchor,
|
||||
'A metric count for the aurweb platform.',
|
||||
$labels
|
||||
);
|
||||
|
||||
$label_values = [$data['method'], $data['path']];
|
||||
if ($type)
|
||||
array_push($label_values, $type);
|
||||
|
||||
$gauge->set($count, $label_values);
|
||||
}
|
||||
|
||||
// Construct the results from RenderTextFormat renderer and
|
||||
// registry's samples.
|
||||
$renderer = new RenderTextFormat();
|
||||
$result = $renderer->render($registry->getMetricFamilySamples());
|
||||
|
||||
// Output the results with the right content type header.
|
||||
http_response_code(200); // OK
|
||||
header('Content-Type: ' . RenderTextFormat::MIME_TYPE);
|
||||
echo $result;
|
||||
}
|
||||
|
||||
?>
|
|
@ -19,7 +19,8 @@ $ROUTES = array(
|
|||
'/rss' => 'rss.php',
|
||||
'/tos' => 'tos.php',
|
||||
'/tu' => 'tu.php',
|
||||
'/addvote' => 'addvote.php',
|
||||
'/addvote' => 'addvote.php',
|
||||
'/metrics' => 'metrics.php' // Prometheus Metrics
|
||||
);
|
||||
|
||||
$PKG_PATH = '/packages';
|
||||
|
|
Loading…
Add table
Reference in a new issue