Metrics: Add support for histogram
This commit is contained in:
parent
a50dd9cae0
commit
138ee996b0
|
@ -6,11 +6,15 @@ use Engelsystem\Renderer\EngineInterface;
|
||||||
|
|
||||||
class MetricsEngine implements EngineInterface
|
class MetricsEngine implements EngineInterface
|
||||||
{
|
{
|
||||||
|
/** @var string */
|
||||||
|
protected $prefix = 'engelsystem_';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render metrics
|
* Render metrics
|
||||||
*
|
*
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* @param mixed[] $data
|
* @param mixed[] $data
|
||||||
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*
|
*
|
||||||
* @example $data = ['foo' => [['labels' => ['foo'=>'bar'], 'value'=>42]], 'bar'=>123]
|
* @example $data = ['foo' => [['labels' => ['foo'=>'bar'], 'value'=>42]], 'bar'=>123]
|
||||||
|
@ -25,21 +29,29 @@ class MetricsEngine implements EngineInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
$list = is_array($list) ? $list : [$list];
|
$list = is_array($list) ? $list : [$list];
|
||||||
$name = 'engelsystem_' . $name;
|
$name = $this->prefix . $name;
|
||||||
|
|
||||||
if (isset($list['help'])) {
|
if (isset($list['help'])) {
|
||||||
$return[] = sprintf('# HELP %s %s', $name, $this->escape($list['help']));
|
$return[] = sprintf('# HELP %s %s', $name, $this->escape($list['help']));
|
||||||
unset($list['help']);
|
unset($list['help']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$type = null;
|
||||||
if (isset($list['type'])) {
|
if (isset($list['type'])) {
|
||||||
|
$type = $list['type'];
|
||||||
$return[] = sprintf('# TYPE %s %s', $name, $list['type']);
|
$return[] = sprintf('# TYPE %s %s', $name, $list['type']);
|
||||||
unset($list['type']);
|
unset($list['type']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$list = (!isset($list['value']) || !isset($list['labels'])) ? $list : [$list];
|
$list = (!isset($list['value']) || !isset($list['labels'])) ? $list : [$list];
|
||||||
foreach ($list as $row) {
|
foreach ($list as $row) {
|
||||||
$row = is_array($row) ? $row : [$row];
|
$row = $this->expandData($row);
|
||||||
|
|
||||||
|
if ($type == 'histogram') {
|
||||||
|
$return = array_merge($return, $this->formatHistogram($row, $name));
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$return[] = $this->formatData($name, $row);
|
$return[] = $this->formatData($name, $row);
|
||||||
}
|
}
|
||||||
|
@ -49,17 +61,72 @@ class MetricsEngine implements EngineInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $path
|
* @param array $row
|
||||||
* @return bool
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return array[]
|
||||||
*/
|
*/
|
||||||
public function canRender(string $path): bool
|
protected function formatHistogram(array $row, string $name): array
|
||||||
{
|
{
|
||||||
return $path == '/metrics';
|
$return = [];
|
||||||
|
$data = ['labels' => $row['labels']];
|
||||||
|
|
||||||
|
if (!isset($row['value']['+Inf'])) {
|
||||||
|
$row['value']['+Inf'] = !empty($row['value']) ? max($row['value']) : 'NaN';
|
||||||
|
}
|
||||||
|
asort($row['value']);
|
||||||
|
|
||||||
|
foreach ($row['value'] as $le => $value) {
|
||||||
|
$return[] = $this->formatData(
|
||||||
|
$name . '_bucket',
|
||||||
|
array_merge_recursive($data, ['value' => $value, 'labels' => ['le' => $le]])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum = isset($row['sum']) ? $row['sum'] : 'NaN';
|
||||||
|
$count = $row['value']['+Inf'];
|
||||||
|
$return[] = $this->formatData($name . '_sum', $data + ['value' => $sum]);
|
||||||
|
$return[] = $this->formatData($name . '_count', $data + ['value' => $count]);
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand the value to be an array
|
||||||
|
*
|
||||||
|
* @param $data
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function expandData($data): array
|
||||||
|
{
|
||||||
|
$data = is_array($data) ? $data : [$data];
|
||||||
|
$return = ['labels' => [], 'value' => null];
|
||||||
|
|
||||||
|
if (isset($data['labels'])) {
|
||||||
|
$return['labels'] = $data['labels'];
|
||||||
|
unset($data['labels']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data['sum'])) {
|
||||||
|
$return['sum'] = $data['sum'];
|
||||||
|
unset($data['sum']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data['value'])) {
|
||||||
|
$return['value'] = $data['value'];
|
||||||
|
unset($data['value']);
|
||||||
|
} else {
|
||||||
|
$return['value'] = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param array|mixed $row
|
* @param array|mixed $row
|
||||||
|
*
|
||||||
* @return string
|
* @return string
|
||||||
* @see https://prometheus.io/docs/instrumenting/exposition_formats/
|
* @see https://prometheus.io/docs/instrumenting/exposition_formats/
|
||||||
*/
|
*/
|
||||||
|
@ -68,23 +135,23 @@ class MetricsEngine implements EngineInterface
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'%s%s %s',
|
'%s%s %s',
|
||||||
$name,
|
$name,
|
||||||
$this->renderLabels($row),
|
$this->renderLabels($row['labels']),
|
||||||
$this->renderValue($row)
|
$this->renderValue($row['value'])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array|mixed $row
|
* @param array $labels
|
||||||
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
protected function renderLabels($row): string
|
protected function renderLabels(array $labels): string
|
||||||
{
|
{
|
||||||
$labels = [];
|
if (empty($labels)) {
|
||||||
if (!is_array($row) || empty($row['labels'])) {
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($row['labels'] as $type => $value) {
|
foreach ($labels as $type => $value) {
|
||||||
$labels[$type] = $type . '="' . $this->formatValue($value) . '"';
|
$labels[$type] = $type . '="' . $this->formatValue($value) . '"';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,19 +160,21 @@ class MetricsEngine implements EngineInterface
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array|mixed $row
|
* @param array|mixed $row
|
||||||
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
protected function renderValue($row)
|
protected function renderValue($row)
|
||||||
{
|
{
|
||||||
if (isset($row['value'])) {
|
if (is_array($row)) {
|
||||||
return $this->formatValue($row['value']);
|
$row = array_pop($row);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->formatValue(array_pop($row));
|
return $this->formatValue($row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
protected function formatValue($value)
|
protected function formatValue($value)
|
||||||
|
@ -119,6 +188,7 @@ class MetricsEngine implements EngineInterface
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
protected function escape($value)
|
protected function escape($value)
|
||||||
|
@ -136,6 +206,16 @@ class MetricsEngine implements EngineInterface
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function canRender(string $path): bool
|
||||||
|
{
|
||||||
|
return $path == '/metrics';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does nothing as shared data will only result in unexpected behaviour
|
* Does nothing as shared data will only result in unexpected behaviour
|
||||||
*
|
*
|
||||||
|
|
|
@ -55,6 +55,85 @@ class MetricsEngineTest extends TestCase
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Metrics\MetricsEngine::expandData
|
||||||
|
* @covers \Engelsystem\Controllers\Metrics\MetricsEngine::formatHistogram
|
||||||
|
* @covers \Engelsystem\Controllers\Metrics\MetricsEngine::get
|
||||||
|
*/
|
||||||
|
public function testGetHistogram()
|
||||||
|
{
|
||||||
|
$engine = new MetricsEngine();
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
<<<'EOD'
|
||||||
|
# TYPE engelsystem_test_minimum_histogram histogram
|
||||||
|
engelsystem_test_minimum_histogram_bucket{le="3"} 4
|
||||||
|
engelsystem_test_minimum_histogram_bucket{le="+Inf"} 4
|
||||||
|
engelsystem_test_minimum_histogram_sum 1.337
|
||||||
|
engelsystem_test_minimum_histogram_count 4
|
||||||
|
EOD,
|
||||||
|
$engine->get('/metrics', [
|
||||||
|
'test_minimum_histogram' => [
|
||||||
|
'type' => 'histogram', [3 => 4, 'sum' => 1.337]
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
<<<'EOD'
|
||||||
|
# TYPE engelsystem_test_short_histogram histogram
|
||||||
|
engelsystem_test_short_histogram_bucket{le="0"} 0
|
||||||
|
engelsystem_test_short_histogram_bucket{le="60"} 10
|
||||||
|
engelsystem_test_short_histogram_bucket{le="120"} 19
|
||||||
|
engelsystem_test_short_histogram_bucket{le="+Inf"} 300
|
||||||
|
engelsystem_test_short_histogram_sum 123.456
|
||||||
|
engelsystem_test_short_histogram_count 300
|
||||||
|
EOD,
|
||||||
|
$engine->get('/metrics', [
|
||||||
|
'test_short_histogram' => [
|
||||||
|
'type' => 'histogram',
|
||||||
|
'value' => [120 => 19, '+Inf' => 300, 60 => 10, 0 => 0, 'sum' => 123.456]
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
<<<'EOD'
|
||||||
|
# TYPE engelsystem_test_multiple_histogram histogram
|
||||||
|
engelsystem_test_multiple_histogram_bucket{handler="foo",le="0.1"} 32
|
||||||
|
engelsystem_test_multiple_histogram_bucket{handler="foo",le="3"} 99
|
||||||
|
engelsystem_test_multiple_histogram_bucket{handler="foo",le="+Inf"} 99
|
||||||
|
engelsystem_test_multiple_histogram_sum{handler="foo"} 42
|
||||||
|
engelsystem_test_multiple_histogram_count{handler="foo"} 99
|
||||||
|
engelsystem_test_multiple_histogram_bucket{handler="bar",le="0.2"} 0
|
||||||
|
engelsystem_test_multiple_histogram_bucket{handler="bar",le="+Inf"} 3
|
||||||
|
engelsystem_test_multiple_histogram_sum{handler="bar"} 3
|
||||||
|
engelsystem_test_multiple_histogram_count{handler="bar"} 3
|
||||||
|
EOD,
|
||||||
|
$engine->get('/metrics', [
|
||||||
|
'test_multiple_histogram' => [
|
||||||
|
'type' => 'histogram',
|
||||||
|
['labels' => ['handler' => 'foo'], 'sum' => '42', 'value' => ['0.1' => 32, 3 => 99]],
|
||||||
|
['labels' => ['handler' => 'bar'], 'sum' => '3', 'value' => ['0.2' => 0, '+Inf' => 3]],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
<<<'EOD'
|
||||||
|
# TYPE engelsystem_test_minimum_histogram histogram
|
||||||
|
engelsystem_test_minimum_histogram_bucket{le="+Inf"} NaN
|
||||||
|
engelsystem_test_minimum_histogram_sum NaN
|
||||||
|
engelsystem_test_minimum_histogram_count NaN
|
||||||
|
EOD,
|
||||||
|
$engine->get('/metrics', [
|
||||||
|
'test_minimum_histogram' => [
|
||||||
|
'type' => 'histogram', []
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \Engelsystem\Controllers\Metrics\MetricsEngine::canRender
|
* @covers \Engelsystem\Controllers\Metrics\MetricsEngine::canRender
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue