diff --git a/src/Prometheus/Storage/APC.php b/src/Prometheus/Storage/APC.php index b608e49..fb747ce 100644 --- a/src/Prometheus/Storage/APC.php +++ b/src/Prometheus/Storage/APC.php @@ -5,7 +5,6 @@ use Prometheus\MetricFamilySamples; -use RuntimeException; class APC implements Adapter { @@ -26,19 +25,19 @@ public function updateHistogram(array $data) { // Initialize the sum $sumKey = $this->histogramBucketValueKey($data, 'sum'); - $new = apcu_add($sumKey, $this->toInteger(0)); + $new = apc_add($sumKey, $this->toInteger(0)); // If sum does not exist, assume a new histogram and store the metadata if ($new) { - apcu_store($this->metaKey($data), json_encode($this->metaData($data))); + apc_store($this->metaKey($data), json_encode($this->metaData($data))); } // Atomically increment the sum // Taken from https://github.com/prometheus/client_golang/blob/66058aac3a83021948e5fb12f1f408ff556b9037/prometheus/value.go#L91 $done = false; while (!$done) { - $old = apcu_fetch($sumKey); - $done = apcu_cas($sumKey, $old, $this->toInteger($this->fromInteger($old) + $data['value'])); + $old = apc_fetch($sumKey); + $done = apc_cas($sumKey, $old, $this->toInteger($this->fromInteger($old) + $data['value'])); } // Figure out in which bucket the observation belongs @@ -51,42 +50,42 @@ public function updateHistogram(array $data) } // Initialize and increment the bucket - apcu_add($this->histogramBucketValueKey($data, $bucketToIncrease), 0); - apcu_inc($this->histogramBucketValueKey($data, $bucketToIncrease)); + apc_add($this->histogramBucketValueKey($data, $bucketToIncrease), 0); + apc_inc($this->histogramBucketValueKey($data, $bucketToIncrease)); } public function updateGauge(array $data) { $valueKey = $this->valueKey($data); if ($data['command'] == Adapter::COMMAND_SET) { - apcu_store($valueKey, $this->toInteger($data['value'])); - apcu_store($this->metaKey($data), json_encode($this->metaData($data))); + apc_store($valueKey, $this->toInteger($data['value'])); + apc_store($this->metaKey($data), json_encode($this->metaData($data))); } else { - $new = apcu_add($valueKey, $this->toInteger(0)); + $new = apc_add($valueKey, $this->toInteger(0)); if ($new) { - apcu_store($this->metaKey($data), json_encode($this->metaData($data))); + apc_store($this->metaKey($data), json_encode($this->metaData($data))); } // Taken from https://github.com/prometheus/client_golang/blob/66058aac3a83021948e5fb12f1f408ff556b9037/prometheus/value.go#L91 $done = false; while (!$done) { - $old = apcu_fetch($valueKey); - $done = apcu_cas($valueKey, $old, $this->toInteger($this->fromInteger($old) + $data['value'])); + $old = apc_fetch($valueKey); + $done = apc_cas($valueKey, $old, $this->toInteger($this->fromInteger($old) + $data['value'])); } } } public function updateCounter(array $data) { - $new = apcu_add($this->valueKey($data), 0); + $new = apc_add($this->valueKey($data), 0); if ($new) { - apcu_store($this->metaKey($data), json_encode($this->metaData($data))); + apc_store($this->metaKey($data), json_encode($this->metaData($data))); } - apcu_inc($this->valueKey($data), $data['value']); + apc_inc($this->valueKey($data), $data['value']); } public function flushAPC() { - apcu_clear_cache(); + apc_clear_cache('user'); } /** @@ -104,13 +103,7 @@ private function metaKey(array $data) */ private function valueKey(array $data) { - return implode(':', array( - self::PROMETHEUS_PREFIX, - $data['type'], - $data['name'], - $this->encodeLabelValues($data['labelValues']), - 'value' - )); + return implode(':', array(self::PROMETHEUS_PREFIX, $data['type'], $data['name'], json_encode($data['labelValues']), 'value')); } /** @@ -119,14 +112,7 @@ private function valueKey(array $data) */ private function histogramBucketValueKey(array $data, $bucket) { - return implode(':', array( - self::PROMETHEUS_PREFIX, - $data['type'], - $data['name'], - $this->encodeLabelValues($data['labelValues']), - $bucket, - 'value' - )); + return implode(':', array(self::PROMETHEUS_PREFIX, $data['type'], $data['name'], json_encode($data['labelValues']), $bucket, 'value')); } /** @@ -148,7 +134,7 @@ private function metaData(array $data) private function collectCounters() { $counters = array(); - foreach (new \APCUIterator('/^prom:counter:.*:meta/') as $counter) { + foreach (new \APCIterator('user', '/^prom:counter:.*:meta/') as $counter) { $metaData = json_decode($counter['value'], true); $data = array( 'name' => $metaData['name'], @@ -156,13 +142,13 @@ private function collectCounters() 'type' => $metaData['type'], 'labelNames' => $metaData['labelNames'], ); - foreach (new \APCUIterator('/^prom:counter:' . $metaData['name'] . ':.*:value/') as $value) { + foreach (new \APCIterator('user', '/^prom:counter:' . $metaData['name'] . ':.*:value/') as $value) { $parts = explode(':', $value['key']); $labelValues = $parts[3]; $data['samples'][] = array( 'name' => $metaData['name'], 'labelNames' => array(), - 'labelValues' => $this->decodeLabelValues($labelValues), + 'labelValues' => json_decode($labelValues), 'value' => $value['value'] ); } @@ -178,7 +164,7 @@ private function collectCounters() private function collectGauges() { $gauges = array(); - foreach (new \APCUIterator('/^prom:gauge:.*:meta/') as $gauge) { + foreach (new \APCIterator('user', '/^prom:gauge:.*:meta/') as $gauge) { $metaData = json_decode($gauge['value'], true); $data = array( 'name' => $metaData['name'], @@ -186,13 +172,13 @@ private function collectGauges() 'type' => $metaData['type'], 'labelNames' => $metaData['labelNames'], ); - foreach (new \APCUIterator('/^prom:gauge:' . $metaData['name'] . ':.*:value/') as $value) { + foreach (new \APCIterator('user', '/^prom:gauge:' . $metaData['name'] . ':.*:value/') as $value) { $parts = explode(':', $value['key']); $labelValues = $parts[3]; $data['samples'][] = array( 'name' => $metaData['name'], 'labelNames' => array(), - 'labelValues' => $this->decodeLabelValues($labelValues), + 'labelValues' => json_decode($labelValues), 'value' => $this->fromInteger($value['value']) ); } @@ -209,7 +195,7 @@ private function collectGauges() private function collectHistograms() { $histograms = array(); - foreach (new \APCUIterator('/^prom:histogram:.*:meta/') as $histogram) { + foreach (new \APCIterator('user', '/^prom:histogram:.*:meta/') as $histogram) { $metaData = json_decode($histogram['value'], true); $data = array( 'name' => $metaData['name'], @@ -223,7 +209,7 @@ private function collectHistograms() $data['buckets'][] = '+Inf'; $histogramBuckets = array(); - foreach (new \APCUIterator('/^prom:histogram:' . $metaData['name'] . ':.*:value/') as $value) { + foreach (new \APCIterator('user', '/^prom:histogram:' . $metaData['name'] . ':.*:value/') as $value) { $parts = explode(':', $value['key']); $labelValues = $parts[3]; $bucket = $parts[4]; @@ -236,7 +222,7 @@ private function collectHistograms() sort($labels); foreach ($labels as $labelValues) { $acc = 0; - $decodedLabelValues = $this->decodeLabelValues($labelValues); + $decodedLabelValues = json_decode($labelValues); foreach ($data['buckets'] as $bucket) { $bucket = (string) $bucket; if (!isset($histogramBuckets[$labelValues][$bucket])) { @@ -303,36 +289,4 @@ 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; - } } diff --git a/src/Prometheus/Storage/APCU.php b/src/Prometheus/Storage/APCU.php new file mode 100644 index 0000000..db45610 --- /dev/null +++ b/src/Prometheus/Storage/APCU.php @@ -0,0 +1,305 @@ +collectHistograms(); + $metrics = array_merge($metrics, $this->collectGauges()); + $metrics = array_merge($metrics, $this->collectCounters()); + return $metrics; + } + public function updateHistogram(array $data) + { + // Initialize the sum + $sumKey = $this->histogramBucketValueKey($data, 'sum'); + $new = apcu_add($sumKey, $this->toInteger(0)); + // If sum does not exist, assume a new histogram and store the metadata + if ($new) { + apcu_store($this->metaKey($data), json_encode($this->metaData($data))); + } + // Atomically increment the sum + // Taken from https://github.com/prometheus/client_golang/blob/66058aac3a83021948e5fb12f1f408ff556b9037/prometheus/value.go#L91 + $done = false; + while (!$done) { + $old = apcu_fetch($sumKey); + $done = apcu_cas($sumKey, $old, $this->toInteger($this->fromInteger($old) + $data['value'])); + } + // Figure out in which bucket the observation belongs + $bucketToIncrease = '+Inf'; + foreach ($data['buckets'] as $bucket) { + if ($data['value'] <= $bucket) { + $bucketToIncrease = $bucket; + break; + } + } + // Initialize and increment the bucket + apcu_add($this->histogramBucketValueKey($data, $bucketToIncrease), 0); + apcu_inc($this->histogramBucketValueKey($data, $bucketToIncrease)); + } + public function updateGauge(array $data) + { + $valueKey = $this->valueKey($data); + if ($data['command'] == Adapter::COMMAND_SET) { + apcu_store($valueKey, $this->toInteger($data['value'])); + apcu_store($this->metaKey($data), json_encode($this->metaData($data))); + } else { + $new = apcu_add($valueKey, $this->toInteger(0)); + if ($new) { + apcu_store($this->metaKey($data), json_encode($this->metaData($data))); + } + // Taken from https://github.com/prometheus/client_golang/blob/66058aac3a83021948e5fb12f1f408ff556b9037/prometheus/value.go#L91 + $done = false; + while (!$done) { + $old = apcu_fetch($valueKey); + $done = apcu_cas($valueKey, $old, $this->toInteger($this->fromInteger($old) + $data['value'])); + } + } + } + public function updateCounter(array $data) + { + $new = apcu_add($this->valueKey($data), 0); + if ($new) { + apcu_store($this->metaKey($data), json_encode($this->metaData($data))); + } + apcu_inc($this->valueKey($data), $data['value']); + } + public function flushAPCU() + { + apcu_clear_cache(); + } + /** + * @param array $data + * @return string + */ + private function metaKey(array $data) + { + return implode(':', array(self::PROMETHEUS_PREFIX, $data['type'], $data['name'], 'meta')); + } + /** + * @param array $data + * @return string + */ + private function valueKey(array $data) + { + return implode(':', array( + self::PROMETHEUS_PREFIX, + $data['type'], + $data['name'], + $this->encodeLabelValues($data['labelValues']), + 'value' + )); + } + /** + * @param array $data + * @return string + */ + private function histogramBucketValueKey(array $data, $bucket) + { + return implode(':', array( + self::PROMETHEUS_PREFIX, + $data['type'], + $data['name'], + $this->encodeLabelValues($data['labelValues']), + $bucket, + 'value' + )); + } + /** + * @param array $data + * @return array + */ + private function metaData(array $data) + { + $metricsMetaData = $data; + unset($metricsMetaData['value']); + unset($metricsMetaData['command']); + unset($metricsMetaData['labelValues']); + return $metricsMetaData; + } + /** + * @return array + */ + private function collectCounters() + { + $counters = array(); + foreach (new \APCUIterator('/^prom:counter:.*:meta/') as $counter) { + $metaData = json_decode($counter['value'], true); + $data = array( + 'name' => $metaData['name'], + 'help' => $metaData['help'], + 'type' => $metaData['type'], + 'labelNames' => $metaData['labelNames'], + ); + foreach (new \APCUIterator('/^prom:counter:' . $metaData['name'] . ':.*:value/') as $value) { + $parts = explode(':', $value['key']); + $labelValues = $parts[3]; + $data['samples'][] = array( + 'name' => $metaData['name'], + 'labelNames' => array(), + 'labelValues' => $this->decodeLabelValues($labelValues), + 'value' => $value['value'] + ); + } + $this->sortSamples($data['samples']); + $counters[] = new MetricFamilySamples($data); + } + return $counters; + } + /** + * @return array + */ + private function collectGauges() + { + $gauges = array(); + foreach (new \APCUIterator('/^prom:gauge:.*:meta/') as $gauge) { + $metaData = json_decode($gauge['value'], true); + $data = array( + 'name' => $metaData['name'], + 'help' => $metaData['help'], + 'type' => $metaData['type'], + 'labelNames' => $metaData['labelNames'], + ); + foreach (new \APCUIterator('/^prom:gauge:' . $metaData['name'] . ':.*:value/') as $value) { + $parts = explode(':', $value['key']); + $labelValues = $parts[3]; + $data['samples'][] = array( + 'name' => $metaData['name'], + 'labelNames' => array(), + 'labelValues' => $this->decodeLabelValues($labelValues), + 'value' => $this->fromInteger($value['value']) + ); + } + $this->sortSamples($data['samples']); + $gauges[] = new MetricFamilySamples($data); + } + return $gauges; + } + /** + * @return array + */ + private function collectHistograms() + { + $histograms = array(); + foreach (new \APCUIterator('/^prom:histogram:.*:meta/') as $histogram) { + $metaData = json_decode($histogram['value'], true); + $data = array( + 'name' => $metaData['name'], + 'help' => $metaData['help'], + 'type' => $metaData['type'], + 'labelNames' => $metaData['labelNames'], + 'buckets' => $metaData['buckets'] + ); + // Add the Inf bucket so we can compute it later on + $data['buckets'][] = '+Inf'; + $histogramBuckets = array(); + foreach (new \APCUIterator('/^prom:histogram:' . $metaData['name'] . ':.*:value/') as $value) { + $parts = explode(':', $value['key']); + $labelValues = $parts[3]; + $bucket = $parts[4]; + // Key by labelValues + $histogramBuckets[$labelValues][$bucket] = $value['value']; + } + // Compute all buckets + $labels = array_keys($histogramBuckets); + sort($labels); + foreach ($labels as $labelValues) { + $acc = 0; + $decodedLabelValues = $this->decodeLabelValues($labelValues); + foreach ($data['buckets'] as $bucket) { + $bucket = (string) $bucket; + if (!isset($histogramBuckets[$labelValues][$bucket])) { + $data['samples'][] = array( + 'name' => $metaData['name'] . '_bucket', + 'labelNames' => array('le'), + 'labelValues' => array_merge($decodedLabelValues, array($bucket)), + 'value' => $acc + ); + } else { + $acc += $histogramBuckets[$labelValues][$bucket]; + $data['samples'][] = array( + 'name' => $metaData['name'] . '_' . 'bucket', + 'labelNames' => array('le'), + 'labelValues' => array_merge($decodedLabelValues, array($bucket)), + 'value' => $acc + ); + } + } + // Add the count + $data['samples'][] = array( + 'name' => $metaData['name'] . '_count', + 'labelNames' => array(), + 'labelValues' => $decodedLabelValues, + 'value' => $acc + ); + // Add the sum + $data['samples'][] = array( + 'name' => $metaData['name'] . '_sum', + 'labelNames' => array(), + 'labelValues' => $decodedLabelValues, + 'value' => $this->fromInteger($histogramBuckets[$labelValues]['sum']) + ); + } + $histograms[] = new MetricFamilySamples($data); + } + return $histograms; + } + /** + * @param mixed $val + * @return int + */ + private function toInteger($val) + { + return unpack('Q', pack('d', $val))[1]; + } + /** + * @param mixed $val + * @return int + */ + private function fromInteger($val) + { + return unpack('d', pack('Q', $val))[1]; + } + private function sortSamples(array &$samples) + { + usort($samples, function($a, $b){ + 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; + } +} \ No newline at end of file diff --git a/tests/Test/Prometheus/APC/CollectorRegistryTest.php b/tests/Test/Prometheus/APCU/CollectorRegistryTest.php similarity index 53% rename from tests/Test/Prometheus/APC/CollectorRegistryTest.php rename to tests/Test/Prometheus/APCU/CollectorRegistryTest.php index 23f7b84..3e2f631 100644 --- a/tests/Test/Prometheus/APC/CollectorRegistryTest.php +++ b/tests/Test/Prometheus/APCU/CollectorRegistryTest.php @@ -1,20 +1,20 @@ adapter = new APC(); - $this->adapter->flushAPC(); + $this->adapter = new APCU(); + $this->adapter->flushAPCU(); } } diff --git a/tests/Test/Prometheus/APC/CounterTest.php b/tests/Test/Prometheus/APCU/CounterTest.php similarity index 58% rename from tests/Test/Prometheus/APC/CounterTest.php rename to tests/Test/Prometheus/APCU/CounterTest.php index 5ca3b5d..6bc4ef0 100644 --- a/tests/Test/Prometheus/APC/CounterTest.php +++ b/tests/Test/Prometheus/APCU/CounterTest.php @@ -1,20 +1,20 @@ adapter = new APC(); - $this->adapter->flushAPC(); + $this->adapter = new APCU(); + $this->adapter->flushAPCU(); } } diff --git a/tests/Test/Prometheus/APC/GaugeTest.php b/tests/Test/Prometheus/APCU/GaugeTest.php similarity index 57% rename from tests/Test/Prometheus/APC/GaugeTest.php rename to tests/Test/Prometheus/APCU/GaugeTest.php index f1b2c04..9572e9a 100644 --- a/tests/Test/Prometheus/APC/GaugeTest.php +++ b/tests/Test/Prometheus/APCU/GaugeTest.php @@ -1,20 +1,20 @@ adapter = new APC(); - $this->adapter->flushAPC(); + $this->adapter = new APCU(); + $this->adapter->flushAPCU(); } } diff --git a/tests/Test/Prometheus/APC/HistogramTest.php b/tests/Test/Prometheus/APCU/HistogramTest.php similarity index 59% rename from tests/Test/Prometheus/APC/HistogramTest.php rename to tests/Test/Prometheus/APCU/HistogramTest.php index 19d41eb..882de65 100644 --- a/tests/Test/Prometheus/APC/HistogramTest.php +++ b/tests/Test/Prometheus/APCU/HistogramTest.php @@ -1,21 +1,21 @@ adapter = new APC(); - $this->adapter->flushAPC(); + $this->adapter = new APCU(); + $this->adapter->flushAPCU(); } }