PluginProbe
DecaLog / 4.4.0
DecaLog v4.4.0
3.0.2 3.1.0 3.10.0 3.2.0 3.3.0 3.4.0 3.4.1 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.6.3 3.7.0 3.7.1 3.8.0 3.9.0 3.9.1 4.0.0 4.1.0 4.2.0 4.3.0 4.3.1 4.4.0 4.5.0 All 75 releases
decalog / includes / libraries / prometheus / Storage / APC.php

APC.php in DecaLog 4.4.0, at includes/libraries/prometheus/Storage/APC.php

421 lines 13.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Prometheus\Storage;
6
7 use APCUIterator;
8 use Prometheus\Exception\StorageException;
9 use Prometheus\MetricFamilySamples;
10 use RuntimeException;
11
12 class APC implements Adapter
13 {
14 const PROMETHEUS_PREFIX = 'prom';
15
16 /**
17 * APC constructor.
18 *
19 * @throws StorageException
20 */
21 public function __construct()
22 {
23 if (!extension_loaded('apcu')) {
24 throw new StorageException('APCu extension is not loaded');
25 }
26 if (!apcu_enabled()) {
27 throw new StorageException('APCu is not enabled');
28 }
29 }
30
31 /**
32 * @return MetricFamilySamples[]
33 */
34 public function collect(): array
35 {
36 $metrics = $this->collectHistograms();
37 $metrics = array_merge($metrics, $this->collectGauges());
38 $metrics = array_merge($metrics, $this->collectCounters());
39 return $metrics;
40 }
41
42 /**
43 * @param mixed[] $data
44 */
45 public function updateHistogram(array $data): void
46 {
47 // Initialize the sum
48 $sumKey = $this->histogramBucketValueKey($data, 'sum');
49 $new = apcu_add($sumKey, $this->toBinaryRepresentationAsInteger(0));
50
51 // If sum does not exist, assume a new histogram and store the metadata
52 if ($new) {
53 apcu_store($this->metaKey($data), json_encode($this->metaData($data)));
54 }
55
56 // Atomically increment the sum
57 // Taken from https://github.com/prometheus/client_golang/blob/66058aac3a83021948e5fb12f1f408ff556b9037/prometheus/value.go#L91
58 $done = false;
59 while (!$done) {
60 $old = apcu_fetch($sumKey);
61 if ($old !== false) {
62 $done = apcu_cas($sumKey, $old, $this->toBinaryRepresentationAsInteger($this->fromBinaryRepresentationAsInteger($old) + $data['value']));
63 }
64 }
65
66 // Figure out in which bucket the observation belongs
67 $bucketToIncrease = '+Inf';
68 foreach ($data['buckets'] as $bucket) {
69 if ($data['value'] <= $bucket) {
70 $bucketToIncrease = $bucket;
71 break;
72 }
73 }
74
75 // Initialize and increment the bucket
76 apcu_add($this->histogramBucketValueKey($data, $bucketToIncrease), 0);
77 apcu_inc($this->histogramBucketValueKey($data, $bucketToIncrease));
78 }
79
80 /**
81 * @param mixed[] $data
82 */
83 public function updateGauge(array $data): void
84 {
85 $valueKey = $this->valueKey($data);
86 if ($data['command'] === Adapter::COMMAND_SET) {
87 apcu_store($valueKey, $this->toBinaryRepresentationAsInteger($data['value']));
88 apcu_store($this->metaKey($data), json_encode($this->metaData($data)));
89 } else {
90 $new = apcu_add($valueKey, $this->toBinaryRepresentationAsInteger(0));
91 if ($new) {
92 apcu_store($this->metaKey($data), json_encode($this->metaData($data)));
93 }
94 // Taken from https://github.com/prometheus/client_golang/blob/66058aac3a83021948e5fb12f1f408ff556b9037/prometheus/value.go#L91
95 $done = false;
96 while (!$done) {
97 $old = apcu_fetch($valueKey);
98 if ($old !== false) {
99 $done = apcu_cas($valueKey, $old, $this->toBinaryRepresentationAsInteger($this->fromBinaryRepresentationAsInteger($old) + $data['value']));
100 }
101 }
102 }
103 }
104
105 /**
106 * @param mixed[] $data
107 */
108 public function updateCounter(array $data): void
109 {
110 $valueKey = $this->valueKey($data);
111 // Check if value key already exists
112 if (apcu_exists($this->valueKey($data)) === false) {
113 apcu_add($this->valueKey($data), 0);
114 apcu_store($this->metaKey($data), json_encode($this->metaData($data)));
115 }
116
117 // Taken from https://github.com/prometheus/client_golang/blob/66058aac3a83021948e5fb12f1f408ff556b9037/prometheus/value.go#L91
118 $done = false;
119 while (!$done) {
120 $old = apcu_fetch($valueKey);
121 if ($old !== false) {
122 $done = apcu_cas($valueKey, $old, $this->toBinaryRepresentationAsInteger($this->fromBinaryRepresentationAsInteger($old) + $data['value']));
123 }
124 }
125 }
126
127 /**
128 * @deprecated use replacement method wipeStorage from Adapter interface
129 *
130 * @return void
131 */
132 public function flushAPC(): void
133 {
134 $this->wipeStorage();
135 }
136
137 /**
138 * Removes all previously stored data from apcu
139 *
140 * @return void
141 */
142 public function wipeStorage(): void
143 {
144 // / / | PCRE expresion boundary
145 // ^ | match from first character only
146 // %s: | common prefix substitute with colon suffix
147 // .+ | at least one additional character
148 $matchAll = sprintf('/^%s:.+/', self::PROMETHEUS_PREFIX);
149
150 foreach (new APCUIterator($matchAll) as $key => $value) {
151 apcu_delete($key);
152 }
153 }
154
155 /**
156 * @param mixed[] $data
157 * @return string
158 */
159 private function metaKey(array $data): string
160 {
161 return implode(':', [self::PROMETHEUS_PREFIX, $data['type'], $data['name'], 'meta']);
162 }
163
164 /**
165 * @param mixed[] $data
166 * @return string
167 */
168 private function valueKey(array $data): string
169 {
170 return implode(':', [
171 self::PROMETHEUS_PREFIX,
172 $data['type'],
173 $data['name'],
174 $this->encodeLabelValues($data['labelValues']),
175 'value',
176 ]);
177 }
178
179 /**
180 * @param mixed[] $data
181 * @param string|int $bucket
182 * @return string
183 */
184 private function histogramBucketValueKey(array $data, $bucket): string
185 {
186 return implode(':', [
187 self::PROMETHEUS_PREFIX,
188 $data['type'],
189 $data['name'],
190 $this->encodeLabelValues($data['labelValues']),
191 $bucket,
192 'value',
193 ]);
194 }
195
196 /**
197 * @param mixed[] $data
198 * @return mixed[]
199 */
200 private function metaData(array $data): array
201 {
202 $metricsMetaData = $data;
203 unset($metricsMetaData['value'], $metricsMetaData['command'], $metricsMetaData['labelValues']);
204 return $metricsMetaData;
205 }
206
207 /**
208 * @return MetricFamilySamples[]
209 */
210 private function collectCounters(): array
211 {
212 $counters = [];
213 foreach (new APCUIterator('/^prom:counter:.*:meta/') as $counter) {
214 $metaData = json_decode($counter['value'], true);
215 $data = [
216 'name' => $metaData['name'],
217 'help' => $metaData['help'],
218 'type' => $metaData['type'],
219 'labelNames' => $metaData['labelNames'],
220 'samples' => [],
221 ];
222 foreach (new APCUIterator('/^prom:counter:' . $metaData['name'] . ':.*:value/') as $value) {
223 $parts = explode(':', $value['key']);
224 $labelValues = $parts[3];
225 $data['samples'][] = [
226 'name' => $metaData['name'],
227 'labelNames' => [],
228 'labelValues' => $this->decodeLabelValues($labelValues),
229 'value' => $this->fromBinaryRepresentationAsInteger($value['value']),
230 ];
231 }
232 $this->sortSamples($data['samples']);
233 $counters[] = new MetricFamilySamples($data);
234 }
235 return $counters;
236 }
237
238 /**
239 * @return MetricFamilySamples[]
240 */
241 private function collectGauges(): array
242 {
243 $gauges = [];
244 foreach (new APCUIterator('/^prom:gauge:.*:meta/') as $gauge) {
245 $metaData = json_decode($gauge['value'], true);
246 $data = [
247 'name' => $metaData['name'],
248 'help' => $metaData['help'],
249 'type' => $metaData['type'],
250 'labelNames' => $metaData['labelNames'],
251 'samples' => [],
252 ];
253 foreach (new APCUIterator('/^prom:gauge:' . $metaData['name'] . ':.*:value/') as $value) {
254 $parts = explode(':', $value['key']);
255 $labelValues = $parts[3];
256 $data['samples'][] = [
257 'name' => $metaData['name'],
258 'labelNames' => [],
259 'labelValues' => $this->decodeLabelValues($labelValues),
260 'value' => $this->fromBinaryRepresentationAsInteger($value['value']),
261 ];
262 }
263
264 $this->sortSamples($data['samples']);
265 $gauges[] = new MetricFamilySamples($data);
266 }
267 return $gauges;
268 }
269
270 /**
271 * @return MetricFamilySamples[]
272 */
273 private function collectHistograms(): array
274 {
275 $histograms = [];
276 foreach (new APCUIterator('/^prom:histogram:.*:meta/') as $histogram) {
277 $metaData = json_decode($histogram['value'], true);
278 $data = [
279 'name' => $metaData['name'],
280 'help' => $metaData['help'],
281 'type' => $metaData['type'],
282 'labelNames' => $metaData['labelNames'],
283 'buckets' => $metaData['buckets'],
284 ];
285
286 // Add the Inf bucket so we can compute it later on
287 $data['buckets'][] = '+Inf';
288
289 $histogramBuckets = [];
290 foreach (new APCUIterator('/^prom:histogram:' . $metaData['name'] . ':.*:value/') as $value) {
291 $parts = explode(':', $value['key']);
292 $labelValues = $parts[3];
293 $bucket = $parts[4];
294 // Key by labelValues
295 $histogramBuckets[$labelValues][$bucket] = $value['value'];
296 }
297
298 // Compute all buckets
299 $labels = array_keys($histogramBuckets);
300 sort($labels);
301 foreach ($labels as $labelValues) {
302 $acc = 0;
303 $decodedLabelValues = $this->decodeLabelValues($labelValues);
304 foreach ($data['buckets'] as $bucket) {
305 $bucket = (string)$bucket;
306 if (!isset($histogramBuckets[$labelValues][$bucket])) {
307 $data['samples'][] = [
308 'name' => $metaData['name'] . '_bucket',
309 'labelNames' => ['le'],
310 'labelValues' => array_merge($decodedLabelValues, [$bucket]),
311 'value' => $acc,
312 ];
313 } else {
314 $acc += $histogramBuckets[$labelValues][$bucket];
315 $data['samples'][] = [
316 'name' => $metaData['name'] . '_' . 'bucket',
317 'labelNames' => ['le'],
318 'labelValues' => array_merge($decodedLabelValues, [$bucket]),
319 'value' => $acc,
320 ];
321 }
322 }
323
324 // Add the count
325 $data['samples'][] = [
326 'name' => $metaData['name'] . '_count',
327 'labelNames' => [],
328 'labelValues' => $decodedLabelValues,
329 'value' => $acc,
330 ];
331
332 // Add the sum
333 $data['samples'][] = [
334 'name' => $metaData['name'] . '_sum',
335 'labelNames' => [],
336 'labelValues' => $decodedLabelValues,
337 'value' => $this->fromBinaryRepresentationAsInteger($histogramBuckets[$labelValues]['sum']),
338 ];
339 }
340 $histograms[] = new MetricFamilySamples($data);
341 }
342 return $histograms;
343 }
344
345 /**
346 * @param mixed $val
347 * @return int
348 * @throws RuntimeException
349 */
350 private function toBinaryRepresentationAsInteger($val): int
351 {
352 $packedDouble = pack('d', $val);
353 if ((bool)$packedDouble !== false) {
354 $unpackedData = unpack("Q", $packedDouble);
355 if (is_array($unpackedData)) {
356 return $unpackedData[1];
357 }
358 }
359 throw new RuntimeException("Formatting from binary representation to integer did not work");
360 }
361
362 /**
363 * @param mixed $val
364 * @return float
365 * @throws RuntimeException
366 */
367 private function fromBinaryRepresentationAsInteger($val): float
368 {
369 $packedBinary = pack('Q', $val);
370 if ((bool)$packedBinary !== false) {
371 $unpackedData = unpack("d", $packedBinary);
372 if (is_array($unpackedData)) {
373 return $unpackedData[1];
374 }
375 }
376 throw new RuntimeException("Formatting from integer to binary representation did not work");
377 }
378
379 /**
380 * @param mixed[] $samples
381 */
382 private function sortSamples(array &$samples): void
383 {
384 usort($samples, function ($a, $b): int {
385 return strcmp(implode("", $a['labelValues']), implode("", $b['labelValues']));
386 });
387 }
388
389 /**
390 * @param mixed[] $values
391 * @return string
392 * @throws RuntimeException
393 */
394 private function encodeLabelValues(array $values): string
395 {
396 $json = json_encode($values);
397 if (false === $json) {
398 throw new RuntimeException(json_last_error_msg());
399 }
400 return base64_encode($json);
401 }
402
403 /**
404 * @param string $values
405 * @return mixed[]
406 * @throws RuntimeException
407 */
408 private function decodeLabelValues(string $values): array
409 {
410 $json = base64_decode($values, true);
411 if (false === $json) {
412 throw new RuntimeException('Cannot base64 decode label values');
413 }
414 $decodedValues = json_decode($json, true);
415 if (false === $decodedValues) {
416 throw new RuntimeException(json_last_error_msg());
417 }
418 return $decodedValues;
419 }
420 }
421