diff --git a/src/Prometheus/Collector.php b/src/Prometheus/Collector.php index cbda884f..61de6f1c 100644 --- a/src/Prometheus/Collector.php +++ b/src/Prometheus/Collector.php @@ -99,6 +99,26 @@ protected function assertLabelsAreDefinedCorrectly(array $labels): void } } + /** + * @param array[] $labelValuesSet + */ + protected function assertValidInitLabelsValuesSet(array $labelValuesSet = []): void + { + $initLabelsKeys = array_keys($labelValuesSet); + + $forgottenLabels = array_diff($this->getLabelNames(), $initLabelsKeys); + if (count($forgottenLabels) > 0) { + throw new InvalidArgumentException("Missing label values for: " . implode(',', $forgottenLabels)); + } + + $unnecessaryLabels = array_diff($initLabelsKeys, $this->getLabelNames()); + if (count($unnecessaryLabels) > 0) { + throw new InvalidArgumentException( + "Some labels values are not mentioned on metric creation:" . implode(',', $unnecessaryLabels) + ); + } + } + /** * @param string $metricName */ diff --git a/src/Prometheus/Counter.php b/src/Prometheus/Counter.php index 6fa20022..18fb176e 100644 --- a/src/Prometheus/Counter.php +++ b/src/Prometheus/Counter.php @@ -46,4 +46,22 @@ public function incBy($count, array $labels = []): void ] ); } + + /** + * @param array[] $labelValuesSet + */ + public function init(array $labelValuesSet = []): void + { + $this->assertValidInitLabelsValuesSet($labelValuesSet); + + $this->storageAdapter->initCounter( + [ + 'name' => $this->getName(), + 'help' => $this->getHelp(), + 'type' => $this->getType(), + 'labelNames' => $this->getLabelNames(), + 'labelValuesSet' => $labelValuesSet, + ] + ); + } } diff --git a/src/Prometheus/Gauge.php b/src/Prometheus/Gauge.php index 527aed12..f30ad9cb 100644 --- a/src/Prometheus/Gauge.php +++ b/src/Prometheus/Gauge.php @@ -84,4 +84,22 @@ public function decBy($value, array $labels = []): void { $this->incBy(-$value, $labels); } + + /** + * @param array[] $labelValuesSet + */ + public function init(array $labelValuesSet = []): void + { + $this->assertValidInitLabelsValuesSet($labelValuesSet); + + $this->storageAdapter->initGauge( + [ + 'name' => $this->getName(), + 'help' => $this->getHelp(), + 'type' => $this->getType(), + 'labelNames' => $this->getLabelNames(), + 'labelValuesSet' => $labelValuesSet, + ] + ); + } } diff --git a/src/Prometheus/Histogram.php b/src/Prometheus/Histogram.php index 0e8fc926..e2621994 100644 --- a/src/Prometheus/Histogram.php +++ b/src/Prometheus/Histogram.php @@ -133,6 +133,25 @@ public function observe(float $value, array $labels = []): void ); } + /** + * @param array[] $labelValuesSet + */ + public function init(array $labelValuesSet = []): void + { + $this->assertValidInitLabelsValuesSet($labelValuesSet); + + $this->storageAdapter->initHistogram( + [ + 'name' => $this->getName(), + 'help' => $this->getHelp(), + 'type' => $this->getType(), + 'labelNames' => $this->getLabelNames(), + 'labelValuesSet' => $labelValuesSet, + 'buckets' => $this->buckets, + ] + ); + } + /** * @return string */ diff --git a/src/Prometheus/Storage/APC.php b/src/Prometheus/Storage/APC.php index 32be527b..a2550214 100644 --- a/src/Prometheus/Storage/APC.php +++ b/src/Prometheus/Storage/APC.php @@ -155,6 +155,21 @@ public function updateCounter(array $data): void } } + public function initHistogram(array $data): void + { + // TODO: Implement initHistogram() method. + } + + public function initGauge(array $data): void + { + // TODO: Implement initGauge() method. + } + + public function initCounter(array $data): void + { + // TODO: Implement initCounter() method. + } + /** * @deprecated use replacement method wipeStorage from Adapter interface * diff --git a/src/Prometheus/Storage/Adapter.php b/src/Prometheus/Storage/Adapter.php index fc39dd81..7e70d6f9 100644 --- a/src/Prometheus/Storage/Adapter.php +++ b/src/Prometheus/Storage/Adapter.php @@ -13,6 +13,14 @@ interface Adapter const COMMAND_INCREMENT_FLOAT = 2; const COMMAND_SET = 3; + /** + * Removes all previously stored metrics from underlying storage + * + * @throws StorageException + * @return void + */ + public function wipeStorage(): void; + /** * @return MetricFamilySamples[] */ @@ -43,10 +51,21 @@ public function updateGauge(array $data): void; public function updateCounter(array $data): void; /** - * Removes all previously stored metrics from underlying storage - * - * @throws StorageException + * @param mixed[] $data * @return void */ - public function wipeStorage(): void; + public function initHistogram(array $data): void; + + + /** + * @param mixed[] $data + * @return void + */ + public function initGauge(array $data): void; + + /** + * @param mixed[] $data + * @return void + */ + public function initCounter(array $data): void; } diff --git a/src/Prometheus/Storage/InMemory.php b/src/Prometheus/Storage/InMemory.php index 9045b020..0591f773 100644 --- a/src/Prometheus/Storage/InMemory.php +++ b/src/Prometheus/Storage/InMemory.php @@ -351,6 +351,21 @@ public function updateCounter(array $data): void } } + public function initHistogram(array $data): void + { + // TODO: Implement initHistogram() method. + } + + public function initGauge(array $data): void + { + // TODO: Implement initGauge() method. + } + + public function initCounter(array $data): void + { + // TODO: Implement initCounter() method. + } + /** * @param mixed[] $data * @param string|int $bucket diff --git a/src/Prometheus/Storage/Redis.php b/src/Prometheus/Storage/Redis.php index 8b626171..586e955d 100644 --- a/src/Prometheus/Storage/Redis.php +++ b/src/Prometheus/Storage/Redis.php @@ -374,6 +374,154 @@ public function updateCounter(array $data): void ); } + /** + * @param mixed[] $data + * @throws StorageException + */ + public function initHistogram(array $data): void + { + $this->ensureOpenConnection(); + + $metricKey = $this->toMetricKey($data); + + // If hash exists, we skip init TODO: new labels adding [checking __meta] + if ((bool) $this->redis->hLen($metricKey)) { + return; + } + + $bucketsToSet = $data['buckets']; + array_unshift($bucketsToSet, 'sum'); + $bucketsToSet[] = '+Inf'; + + $labelsCartesian = $this->cartesian($data['labelValuesSet']); + + $values = []; + foreach ($bucketsToSet as $bucket) { + foreach ($labelsCartesian as $labelValues) { + $values[] = json_encode(['b' => $bucket, 'labelValues' => array_values($labelValues)]); + } + } + + $valuesString = $this->makeLuaValuesString($values); + + // metadata + unset($data['labelValuesSet']); + + $this->redis->eval( + <<ensureOpenConnection(); + + $metricKey = $this->toMetricKey($data); + + // If hash exists, we skip init TODO: new labels adding [checking __meta] + if ((bool) $this->redis->hLen($metricKey)) { + return; + } + + $labelsCartesian = $this->cartesian($data['labelValuesSet']); + + $values = []; + foreach ($labelsCartesian as $labelValues) { + $values[] = json_encode(array_values($labelValues)); + } + + $valuesString = $this->makeLuaValuesString($values); + + // metadata + unset($data['labelValuesSet']); + + $this->redis->eval( + <<ensureOpenConnection(); + + $metricKey = $this->toMetricKey($data); + + // If hash exists, we skip init TODO: new labels adding [checking __meta] + if ((bool) $this->redis->hLen($metricKey)) { + return; + } + + $labelsCartesian = $this->cartesian($data['labelValuesSet']); + + $values = []; + foreach ($labelsCartesian as $labelValues) { + $values[] = json_encode(array_values($labelValues)); + } + + $valuesString = $this->makeLuaValuesString($values); + + // metadata + unset($data['labelValuesSet']); + + $this->redis->eval( + << $values) { + $append = []; + + foreach ($result as $product) { + foreach ($values as $item) { + $product[$key] = $item; + $append[] = $product; + } + } + + $result = $append; + } + + return $result; + } + + /** + * @param mixed[] $values + * @return string + */ + private function makeLuaValuesString(array $values): string + { + $values = array_map( + static function (string $value): string { + return '"' . addslashes($value) . '"'; + }, + $values + ); + + return implode(', ', $values); + } }