diff --git a/src/Prometheus/CollectorRegistry.php b/src/Prometheus/CollectorRegistry.php index a90a4272..2cc52255 100644 --- a/src/Prometheus/CollectorRegistry.php +++ b/src/Prometheus/CollectorRegistry.php @@ -206,7 +206,8 @@ public function registerHistogram( string $name, string $help, array $labels = [], - array $buckets = null + array $buckets = null, + ?int $timestamp = null ): Histogram { $metricIdentifier = self::metricIdentifier($namespace, $name); if (isset($this->histograms[$metricIdentifier])) { @@ -218,7 +219,8 @@ public function registerHistogram( $name, $help, $labels, - $buckets + $buckets, + $timestamp ); return $this->histograms[$metricIdentifier]; } @@ -254,12 +256,13 @@ public function getOrRegisterHistogram( string $name, string $help, array $labels = [], - array $buckets = null + array $buckets = null, + ?int $timestamp = null ): Histogram { try { $histogram = $this->getHistogram($namespace, $name); } catch (MetricNotFoundException $e) { - $histogram = $this->registerHistogram($namespace, $name, $help, $labels, $buckets); + $histogram = $this->registerHistogram($namespace, $name, $help, $labels, $buckets, $timestamp); } return $histogram; } diff --git a/src/Prometheus/Counter.php b/src/Prometheus/Counter.php index 6fa20022..9795f3a3 100644 --- a/src/Prometheus/Counter.php +++ b/src/Prometheus/Counter.php @@ -21,16 +21,16 @@ public function getType(): string /** * @param string[] $labels e.g. ['status', 'opcode'] */ - public function inc(array $labels = []): void + public function inc(array $labels = [], ?int $timestamp = null): void { - $this->incBy(1, $labels); + $this->incBy(1, $labels, $timestamp); } /** * @param int|float $count e.g. 2 * @param mixed[] $labels e.g. ['status', 'opcode'] */ - public function incBy($count, array $labels = []): void + public function incBy($count, array $labels = [], ?int $timestamp = null): void { $this->assertLabelsAreDefinedCorrectly($labels); @@ -41,6 +41,7 @@ public function incBy($count, array $labels = []): void 'type' => $this->getType(), 'labelNames' => $this->getLabelNames(), 'labelValues' => $labels, + 'timestamp' => $timestamp, 'value' => $count, 'command' => is_float($count) ? Adapter::COMMAND_INCREMENT_FLOAT : Adapter::COMMAND_INCREMENT_INTEGER, ] diff --git a/src/Prometheus/Gauge.php b/src/Prometheus/Gauge.php index 527aed12..3a5f64b7 100644 --- a/src/Prometheus/Gauge.php +++ b/src/Prometheus/Gauge.php @@ -14,7 +14,7 @@ class Gauge extends Collector * @param double $value e.g. 123 * @param string[] $labels e.g. ['status', 'opcode'] */ - public function set(float $value, array $labels = []): void + public function set(float $value, array $labels = [], ?int $timestamp = null): void { $this->assertLabelsAreDefinedCorrectly($labels); @@ -26,6 +26,7 @@ public function set(float $value, array $labels = []): void 'labelNames' => $this->getLabelNames(), 'labelValues' => $labels, 'value' => $value, + 'timestamp' => $timestamp, 'command' => Adapter::COMMAND_SET, ] ); @@ -42,16 +43,16 @@ public function getType(): string /** * @param string[] $labels */ - public function inc(array $labels = []): void + public function inc(array $labels = [], ?int $timestamp = null): void { - $this->incBy(1, $labels); + $this->incBy(1, $labels, $timestamp); } /** * @param int|float $value * @param string[] $labels */ - public function incBy($value, array $labels = []): void + public function incBy($value, array $labels = [], ?int $timestamp = null): void { $this->assertLabelsAreDefinedCorrectly($labels); @@ -62,6 +63,7 @@ public function incBy($value, array $labels = []): void 'type' => $this->getType(), 'labelNames' => $this->getLabelNames(), 'labelValues' => $labels, + 'timestamp' => $timestamp, 'value' => $value, 'command' => Adapter::COMMAND_INCREMENT_FLOAT, ] diff --git a/src/Prometheus/RegistryInterface.php b/src/Prometheus/RegistryInterface.php index 4427fff2..ee4c38bc 100644 --- a/src/Prometheus/RegistryInterface.php +++ b/src/Prometheus/RegistryInterface.php @@ -111,5 +111,5 @@ public function getHistogram(string $namespace, string $name): Histogram; * @return Histogram * @throws MetricsRegistrationException */ - public function getOrRegisterHistogram(string $namespace, string $name, string $help, array $labels = [], array $buckets = null): Histogram; + public function getOrRegisterHistogram(string $namespace, string $name, string $help, array $labels = [], array $buckets = null, ?int $timestamp = null): Histogram; } diff --git a/src/Prometheus/RenderTextFormat.php b/src/Prometheus/RenderTextFormat.php index dfc5151a..263608c8 100644 --- a/src/Prometheus/RenderTextFormat.php +++ b/src/Prometheus/RenderTextFormat.php @@ -37,11 +37,13 @@ public function render(array $metrics): string private function renderSample(MetricFamilySamples $metric, Sample $sample): string { $labelNames = $metric->getLabelNames(); + $timestamp = $sample->getTimestamp(); + $timestampPart = $timestamp === null ? '' : ' ' . $timestamp; if ($metric->hasLabelNames() || $sample->hasLabelNames()) { $escapedLabels = $this->escapeAllLabels($labelNames, $sample); - return $sample->getName() . '{' . implode(',', $escapedLabels) . '} ' . $sample->getValue(); + return $sample->getName() . '{' . implode(',', $escapedLabels) . '} ' . $sample->getValue() . $timestampPart; } - return $sample->getName() . ' ' . $sample->getValue(); + return $sample->getName() . ' ' . $sample->getValue() . $timestampPart; } /** diff --git a/src/Prometheus/Sample.php b/src/Prometheus/Sample.php index efcba415..62e34606 100644 --- a/src/Prometheus/Sample.php +++ b/src/Prometheus/Sample.php @@ -26,6 +26,11 @@ class Sample */ private $value; + /** + * @var int|null + */ + private $timestamp; + /** * Sample constructor. * @param mixed[] $data @@ -36,6 +41,7 @@ public function __construct(array $data) $this->labelNames = (array) $data['labelNames']; $this->labelValues = (array) $data['labelValues']; $this->value = $data['value']; + $this->timestamp = $data['timestamp']; } /** @@ -70,6 +76,11 @@ public function getValue(): string return (string) $this->value; } + public function getTimestamp(): ?int + { + return $this->timestamp; + } + /** * @return bool */ diff --git a/src/Prometheus/Storage/InMemory.php b/src/Prometheus/Storage/InMemory.php index 46445f8c..8b39a59b 100644 --- a/src/Prometheus/Storage/InMemory.php +++ b/src/Prometheus/Storage/InMemory.php @@ -96,6 +96,7 @@ private function collectHistograms(): array 'labelNames' => ['le'], 'labelValues' => array_merge($decodedLabelValues, [$bucket]), 'value' => $acc, + 'timestamp' => null, ]; } else { $acc += $histogramBuckets[$labelValues][$bucket]; @@ -104,6 +105,7 @@ private function collectHistograms(): array 'labelNames' => ['le'], 'labelValues' => array_merge($decodedLabelValues, [$bucket]), 'value' => $acc, + 'timestamp' => null, ]; } } @@ -114,6 +116,7 @@ private function collectHistograms(): array 'labelNames' => [], 'labelValues' => $decodedLabelValues, 'value' => $acc, + 'timestamp' => null, ]; // Add the sum @@ -122,6 +125,7 @@ private function collectHistograms(): array 'labelNames' => [], 'labelValues' => $decodedLabelValues, 'value' => $histogramBuckets[$labelValues]['sum'], + 'timestamp' => null, ]; } $histograms[] = new MetricFamilySamples($data); @@ -145,7 +149,7 @@ private function internalCollect(array $metrics): array 'labelNames' => $metaData['labelNames'], 'samples' => [], ]; - foreach ($metric['samples'] as $key => $value) { + foreach ($metric['samples'] as $key => [$value, $timestamp]) { $parts = explode(':', $key); $labelValues = $parts[2]; $data['samples'][] = [ @@ -153,6 +157,7 @@ private function internalCollect(array $metrics): array 'labelNames' => [], 'labelValues' => $this->decodeLabelValues($labelValues), 'value' => $value, + 'timestamp' => $timestamp, ]; } $this->sortSamples($data['samples']); @@ -212,12 +217,15 @@ public function updateGauge(array $data): void ]; } if (array_key_exists($valueKey, $this->gauges[$metaKey]['samples']) === false) { - $this->gauges[$metaKey]['samples'][$valueKey] = 0; + $this->gauges[$metaKey]['samples'][$valueKey] = [0, $data['timestamp']]; } if ($data['command'] === Adapter::COMMAND_SET) { - $this->gauges[$metaKey]['samples'][$valueKey] = $data['value']; + $this->gauges[$metaKey]['samples'][$valueKey] = [$data['value'], $data['timestamp']]; } else { - $this->gauges[$metaKey]['samples'][$valueKey] += $data['value']; + $this->gauges[$metaKey]['samples'][$valueKey] = [ + $this->gauges[$metaKey]['samples'][$valueKey][0] + $data['value'], + $data['timestamp'] + ]; } } @@ -235,12 +243,15 @@ public function updateCounter(array $data): void ]; } if (array_key_exists($valueKey, $this->counters[$metaKey]['samples']) === false) { - $this->counters[$metaKey]['samples'][$valueKey] = 0; + $this->counters[$metaKey]['samples'][$valueKey] = [0, $data['timestamp']]; } if ($data['command'] === Adapter::COMMAND_SET) { - $this->counters[$metaKey]['samples'][$valueKey] = 0; + $this->counters[$metaKey]['samples'][$valueKey] = [0, $data['timestamp']]; } else { - $this->counters[$metaKey]['samples'][$valueKey] += $data['value']; + $this->counters[$metaKey]['samples'][$valueKey] = [ + $this->counters[$metaKey]['samples'][$valueKey][0] + $data['value'], + $data['timestamp'] + ]; } } diff --git a/tests/Test/Prometheus/RenderTextFormatTest.php b/tests/Test/Prometheus/RenderTextFormatTest.php index 5fa00048..f94561c9 100644 --- a/tests/Test/Prometheus/RenderTextFormatTest.php +++ b/tests/Test/Prometheus/RenderTextFormatTest.php @@ -34,9 +34,13 @@ private function buildSamples(): array $registry = new CollectorRegistry(new InMemory(), false); $registry->getOrRegisterCounter($namespace, 'counter', 'counter-help-text', ['label1', 'label2']) ->inc(['bob', 'al\ice']); - $registry->getOrRegisterGauge($namespace, 'gauge', 'counter-help-text', ['label1', 'label2']) + $registry->getOrRegisterCounter($namespace, 'counter_with_timestamp', 'counter-with-timestamp-help-text', ['label1', 'label2']) + ->inc(['bob', 'al\ice'], 1395066363000); + $registry->getOrRegisterGauge($namespace, 'gauge', 'gauge-help-text', ['label1', 'label2']) ->inc(["bo\nb", 'ali\"ce']); - $registry->getOrRegisterHistogram($namespace, 'histogram', 'counter-help-text', ['label1', 'label2'], [0, 10, 100]) + $registry->getOrRegisterGauge($namespace, 'gauge_with_timestamp', 'gauge-with-timestamp-help-text', ['label1', 'label2']) + ->inc(["bo\nb", 'ali\"ce'], 1395066363000); + $registry->getOrRegisterHistogram($namespace, 'histogram', 'histogram-help-text', ['label1', 'label2'], [0, 10, 100]) ->observe(5, ['bob', 'alice']); return $registry->getMetricFamilySamples(); @@ -48,10 +52,16 @@ private function getExpectedOutput(): string # HELP mynamespace_counter counter-help-text # TYPE mynamespace_counter counter mynamespace_counter{label1="bob",label2="al\\\\ice"} 1 -# HELP mynamespace_gauge counter-help-text +# HELP mynamespace_counter_with_timestamp counter-with-timestamp-help-text +# TYPE mynamespace_counter_with_timestamp counter +mynamespace_counter_with_timestamp{label1="bob",label2="al\\\\ice"} 1 1395066363000 +# HELP mynamespace_gauge gauge-help-text # TYPE mynamespace_gauge gauge mynamespace_gauge{label1="bo\\nb",label2="ali\\\\\"ce"} 1 -# HELP mynamespace_histogram counter-help-text +# HELP mynamespace_gauge_with_timestamp gauge-with-timestamp-help-text +# TYPE mynamespace_gauge_with_timestamp gauge +mynamespace_gauge_with_timestamp{label1="bo\\nb",label2="ali\\\\\"ce"} 1 1395066363000 +# HELP mynamespace_histogram histogram-help-text # TYPE mynamespace_histogram histogram mynamespace_histogram_bucket{label1="bob",label2="alice",le="0"} 0 mynamespace_histogram_bucket{label1="bob",label2="alice",le="10"} 1