Skip to content

Commit

Permalink
Merge pull request Jimdo#57 from motionpicturesolutions/bugfix/data-s…
Browse files Browse the repository at this point in the history
…anitization

Adds base64-encoding of label values to allow the usage of colons
  • Loading branch information
bracki authored Mar 19, 2018
2 parents f15cade + 7eb30b3 commit 01c8937
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 11 deletions.
60 changes: 53 additions & 7 deletions src/Prometheus/Storage/APC.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@


use Prometheus\MetricFamilySamples;
use RuntimeException;

class APC implements Adapter
{
Expand Down Expand Up @@ -103,7 +104,13 @@ private function metaKey(array $data)
*/
private function valueKey(array $data)
{
return implode(':', array(self::PROMETHEUS_PREFIX, $data['type'], $data['name'], json_encode($data['labelValues']), 'value'));
return implode(':', array(
self::PROMETHEUS_PREFIX,
$data['type'],
$data['name'],
$this->encodeLabelValues($data['labelValues']),
'value'
));
}

/**
Expand All @@ -112,7 +119,14 @@ private function valueKey(array $data)
*/
private function histogramBucketValueKey(array $data, $bucket)
{
return implode(':', array(self::PROMETHEUS_PREFIX, $data['type'], $data['name'], json_encode($data['labelValues']), $bucket, 'value'));
return implode(':', array(
self::PROMETHEUS_PREFIX,
$data['type'],
$data['name'],
$this->encodeLabelValues($data['labelValues']),
$bucket,
'value'
));
}

/**
Expand Down Expand Up @@ -148,7 +162,7 @@ private function collectCounters()
$data['samples'][] = array(
'name' => $metaData['name'],
'labelNames' => array(),
'labelValues' => json_decode($labelValues),
'labelValues' => $this->decodeLabelValues($labelValues),
'value' => $value['value']
);
}
Expand All @@ -159,8 +173,8 @@ private function collectCounters()
}

/**
* @return array
*/
* @return array
*/
private function collectGauges()
{
$gauges = array();
Expand All @@ -178,7 +192,7 @@ private function collectGauges()
$data['samples'][] = array(
'name' => $metaData['name'],
'labelNames' => array(),
'labelValues' => json_decode($labelValues),
'labelValues' => $this->decodeLabelValues($labelValues),
'value' => $this->fromInteger($value['value'])
);
}
Expand Down Expand Up @@ -222,7 +236,7 @@ private function collectHistograms()
sort($labels);
foreach ($labels as $labelValues) {
$acc = 0;
$decodedLabelValues = json_decode($labelValues);
$decodedLabelValues = $this->decodeLabelValues($labelValues);
foreach ($data['buckets'] as $bucket) {
$bucket = (string) $bucket;
if (!isset($histogramBuckets[$labelValues][$bucket])) {
Expand Down Expand Up @@ -289,4 +303,36 @@ private function sortSamples(array &$samples)
return strcmp(implode("", $a['labelValues']), implode("", $b['labelValues']));
});
}

/**
* @param array $values
* @return string
* @throws RuntimeException
*/
private function encodeLabelValues(array $values)
{
$json = json_encode($values);
if (false === $json) {
throw new RuntimeException(json_last_error_msg());
}
return base64_encode($json);
}

/**
* @param string $values
* @return array
* @throws RuntimeException
*/
private function decodeLabelValues($values)
{
$json = base64_decode($values, true);
if (false === $json) {
throw new RuntimeException('Cannot base64 decode label values');
}
$decodedValues = json_decode($json, true);
if (false === $decodedValues) {
throw new RuntimeException(json_last_error_msg());
}
return $decodedValues;
}
}
41 changes: 37 additions & 4 deletions src/Prometheus/Storage/InMemory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


use Prometheus\MetricFamilySamples;
use RuntimeException;

class InMemory implements Adapter
{
Expand Down Expand Up @@ -60,7 +61,7 @@ private function collectHistograms()
sort($labels);
foreach ($labels as $labelValues) {
$acc = 0;
$decodedLabelValues = json_decode($labelValues);
$decodedLabelValues = $this->decodeLabelValues($labelValues);
foreach ($data['buckets'] as $bucket) {
$bucket = (string)$bucket;
if (!isset($histogramBuckets[$labelValues][$bucket])) {
Expand Down Expand Up @@ -120,7 +121,7 @@ private function internalCollect(array $metrics)
$data['samples'][] = [
'name' => $metaData['name'],
'labelNames' => [],
'labelValues' => json_decode($labelValues),
'labelValues' => $this->decodeLabelValues($labelValues),
'value' => $value
];
}
Expand Down Expand Up @@ -215,7 +216,7 @@ private function histogramBucketValueKey(array $data, $bucket)
return implode(':', [
$data['type'],
$data['name'],
json_encode($data['labelValues']),
$this->encodeLabelValues($data['labelValues']),
$bucket
]);
}
Expand All @@ -238,7 +239,7 @@ private function metaKey(array $data)
private function valueKey(array $data)
{
return implode(':',
[$data['type'], $data['name'], json_encode($data['labelValues']), 'value']);
[$data['type'], $data['name'], $this->encodeLabelValues($data['labelValues']), 'value']);
}

/**
Expand All @@ -261,4 +262,36 @@ private function sortSamples(array &$samples)
return strcmp(implode("", $a['labelValues']), implode("", $b['labelValues']));
});
}

/**
* @param array $values
* @return string
* @throws RuntimeException
*/
private function encodeLabelValues(array $values)
{
$json = json_encode($values);
if (false === $json) {
throw new RuntimeException(json_last_error_msg());
}
return base64_encode($json);
}

/**
* @param string $values
* @return array
* @throws RuntimeException
*/
private function decodeLabelValues($values)
{
$json = base64_decode($values, true);
if (false === $json) {
throw new RuntimeException('Cannot base64 decode label values');
}
$decodedValues = json_decode($json, true);
if (false === $decodedValues) {
throw new RuntimeException(json_last_error_msg());
}
return $decodedValues;
}
}
47 changes: 47 additions & 0 deletions tests/Test/Prometheus/AbstractCounterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use PHPUnit_Framework_TestCase;
use Prometheus\Counter;
use Prometheus\MetricFamilySamples;
use Prometheus\Sample;
use Prometheus\Storage\Adapter;

/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
Expand Down Expand Up @@ -139,5 +141,50 @@ public function itShouldRejectInvalidLabelNames()
new Counter($this->adapter, 'test', 'some_metric', 'help', array('invalid label'));
}

/**
* @test
* @dataProvider labelValuesDataProvider
*
* @param mixed $value The label value
*/
public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues($value)
{
$label = 'foo';
$histogram = new Counter($this->adapter, 'test', 'some_metric', 'help', array($label));
$histogram->inc(array($value));

$metrics = $this->adapter->collect();
self::assertInternalType('array', $metrics);
self::assertCount(1, $metrics);
self::assertContainsOnlyInstancesOf(MetricFamilySamples::class, $metrics);

$metric = reset($metrics);
$samples = $metric->getSamples();
self::assertContainsOnlyInstancesOf(Sample::class, $samples);

foreach ($samples as $sample) {
$labels = array_combine(
array_merge($metric->getLabelNames(), $sample->getLabelNames()),
$sample->getLabelValues()
);
self::assertEquals($value, $labels[$label]);
}
}

/**
* @see isShouldAcceptArbitraryLabelValues
* @return array
*/
public function labelValuesDataProvider()
{
$cases = [];
// Basic Latin
// See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin
for ($i = 32; $i <= 121; $i++) {
$cases['ASCII code ' . $i] = array(chr($i));
}
return $cases;
}

public abstract function configureAdapter();
}
46 changes: 46 additions & 0 deletions tests/Test/Prometheus/AbstractGaugeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PHPUnit_Framework_TestCase;
use Prometheus\Gauge;
use Prometheus\MetricFamilySamples;
use Prometheus\Sample;
use Prometheus\Storage\Adapter;

/**
Expand Down Expand Up @@ -308,5 +309,50 @@ public function itShouldRejectInvalidLabelNames()
new Gauge($this->adapter, 'test', 'some_metric', 'help', array('invalid label'));
}

/**
* @test
* @dataProvider labelValuesDataProvider
*
* @param mixed $value The label value
*/
public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues($value)
{
$label = 'foo';
$histogram = new Gauge($this->adapter, 'test', 'some_metric', 'help', array($label));
$histogram->inc(array($value));

$metrics = $this->adapter->collect();
self::assertInternalType('array', $metrics);
self::assertCount(1, $metrics);
self::assertContainsOnlyInstancesOf(MetricFamilySamples::class, $metrics);

$metric = reset($metrics);
$samples = $metric->getSamples();
self::assertContainsOnlyInstancesOf(Sample::class, $samples);

foreach ($samples as $sample) {
$labels = array_combine(
array_merge($metric->getLabelNames(), $sample->getLabelNames()),
$sample->getLabelValues()
);
self::assertEquals($value, $labels[$label]);
}
}

/**
* @see isShouldAcceptArbitraryLabelValues
* @return array
*/
public function labelValuesDataProvider()
{
$cases = [];
// Basic Latin
// See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin
for ($i = 32; $i <= 121; $i++) {
$cases['ASCII code ' . $i] = array(chr($i));
}
return $cases;
}

public abstract function configureAdapter();
}
46 changes: 46 additions & 0 deletions tests/Test/Prometheus/AbstractHistogramTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PHPUnit_Framework_TestCase;
use Prometheus\Histogram;
use Prometheus\MetricFamilySamples;
use Prometheus\Sample;
use Prometheus\Storage\Adapter;


Expand Down Expand Up @@ -421,5 +422,50 @@ public function itShouldRejectInvalidLabelNames()
new Histogram($this->adapter, 'test', 'some_metric', 'help', array('invalid label'), array(1));
}

/**
* @test
* @dataProvider labelValuesDataProvider
*
* @param mixed $value The label value
*/
public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues($value)
{
$label = 'foo';
$histogram = new Histogram($this->adapter, 'test', 'some_metric', 'help', array($label), array(1));
$histogram->observe(1, array($value));

$metrics = $this->adapter->collect();
self::assertInternalType('array', $metrics);
self::assertCount(1, $metrics);
self::assertContainsOnlyInstancesOf(MetricFamilySamples::class, $metrics);

$metric = reset($metrics);
$samples = $metric->getSamples();
self::assertContainsOnlyInstancesOf(Sample::class, $samples);

foreach ($samples as $sample) {
$labels = array_combine(
array_merge($metric->getLabelNames(), $sample->getLabelNames()),
$sample->getLabelValues()
);
self::assertEquals($value, $labels[$label]);
}
}

/**
* @see isShouldAcceptArbitraryLabelValues
* @return array
*/
public function labelValuesDataProvider()
{
$cases = [];
// Basic Latin
// See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin
for ($i = 32; $i <= 121; $i++) {
$cases['ASCII code ' . $i] = array(chr($i));
}
return $cases;
}

public abstract function configureAdapter();
}

0 comments on commit 01c8937

Please sign in to comment.