11
11
use Prometheus \Histogram ;
12
12
use Prometheus \MetricFamilySamples ;
13
13
use Prometheus \Storage \RedisTxn \Metadata ;
14
+ use Prometheus \Storage \RedisTxn \MetadataBuilder ;
14
15
use Prometheus \Storage \RedisTxn \Metric ;
15
16
use Prometheus \Summary ;
16
17
use RedisException ;
@@ -39,6 +40,21 @@ class RedisTxn implements Adapter
39
40
40
41
const PROMETHEUS_METRIC_META_SUFFIX = '_METRIC_META ' ;
41
42
43
+ const TTL_FIELD = 'ttl ' ;
44
+
45
+ const DEFAULT_TTL_SECONDS = 600 ;
46
+
47
+ const HGETALL_FUNCTION = <<<LUA
48
+ local function hgetall(key)
49
+ local flat_map = redis.call('hgetall', key)
50
+ local result = {}
51
+ for i = 1, #flat_map, 2 do
52
+ result[flat_map[i]] = flat_map[i + 1]
53
+ end
54
+ return result
55
+ end
56
+ LUA ;
57
+
42
58
/**
43
59
* @var mixed[]
44
60
*/
@@ -303,15 +319,14 @@ public function updateSummary(array $data): void
303
319
{
304
320
$ this ->ensureOpenConnection ();
305
321
306
- // Prepare summary metadata
307
- $ metaHashKey = self ::$ prefix . self ::PROMETHEUS_METRIC_META_SUFFIX ;
308
- $ summaryMetadata = $ this ->toMetadata ($ data );
309
- $ ttl = $ summaryMetadata ->getMaxAgeSeconds ();
322
+ // Prepare metadata
323
+ $ metadata = $ this ->toMetadata ($ data );
324
+ $ ttl = $ metadata ->getMaxAgeSeconds ();
310
325
311
- // Create summary key
312
- $ keyPrefix = self :: $ prefix . Summary:: TYPE . self :: PROMETHEUS_METRIC_KEYS_SUFFIX ;
313
- $ summaryKey = implode ( ' : ' , [ $ keyPrefix , $ data [ ' name ' ], $ summaryMetadata -> getLabelValuesEncoded ()] );
314
- $ summaryRegistryKey = implode ( ' : ' , [ $ keyPrefix , ' keys ' ] );
326
+ // Create Redis keys
327
+ $ metricKey = $ this -> getMetricKey ( $ metadata ) ;
328
+ $ registryKey = $ this -> getMetricRegistryKey ( $ metadata -> getType () );
329
+ $ metadataKey = $ this -> getMetadataKey ( $ metadata -> getType () );
315
330
316
331
// Get summary sample
317
332
//
@@ -350,10 +365,10 @@ public function updateSummary(array $data): void
350
365
LUA
351
366
,
352
367
[
353
- $ summaryRegistryKey ,
354
- $ metaHashKey ,
355
- $ summaryKey ,
356
- $ summaryMetadata ->toJson (),
368
+ $ registryKey ,
369
+ $ metadataKey ,
370
+ $ metricKey ,
371
+ $ metadata ->toJson (),
357
372
$ value ,
358
373
$ currentTime ,
359
374
$ ttl ,
@@ -407,27 +422,50 @@ public function updateGauge(array $data): void
407
422
public function updateCounter (array $ data ): void
408
423
{
409
424
$ this ->ensureOpenConnection ();
410
- $ metaData = $ data ;
411
- unset($ metaData ['value ' ], $ metaData ['labelValues ' ], $ metaData ['command ' ]);
412
- $ this ->redis ->eval (
413
- <<<LUA
414
- local result = redis.call(ARGV[1], KEYS[1], ARGV[3], ARGV[2])
415
- local added = redis.call('sAdd', KEYS[2], KEYS[1])
416
- if added == 1 then
417
- redis.call('hMSet', KEYS[1], '__meta', ARGV[4])
418
- end
419
- return result
425
+
426
+ // Prepare metadata
427
+ $ metadata = $ this ->toMetadata ($ data );
428
+
429
+ // Create Redis keys
430
+ $ metricKey = $ this ->getMetricKey ($ metadata );
431
+ $ registryKey = $ this ->getMetricRegistryKey ($ metadata ->getType ());
432
+ $ metadataKey = $ this ->getMetadataKey ($ metadata ->getType ());
433
+
434
+ // Prepare script input
435
+ $ command = $ metadata ->getCommand () === Adapter::COMMAND_INCREMENT_INTEGER ? 'incrby ' : 'incrbyfloat ' ;
436
+ $ value = $ data ['value ' ];
437
+ $ ttl = time () + ($ metadata ->getMaxAgeSeconds () ?? self ::DEFAULT_TTL_SECONDS );
438
+
439
+ $ this ->redis ->eval (<<<LUA
440
+ -- Parse script input
441
+ local registryKey = KEYS[1]
442
+ local metadataKey = KEYS[2]
443
+ local metricKey = KEYS[3]
444
+ local metadata = ARGV[1]
445
+ local observeCommand = ARGV[2]
446
+ local value = ARGV[3]
447
+ local ttl = ARGV[4]
448
+
449
+ -- Register metric value
450
+ redis.call('sadd', registryKey, metricKey)
451
+
452
+ -- Register metric metadata
453
+ redis.call('hset', metadataKey, metricKey, metadata)
454
+
455
+ -- Update metric value
456
+ redis.call(observeCommand, metricKey, value)
457
+ redis.call('expire', metricKey, ttl)
420
458
LUA
421
- ,
422
- [
423
- $ this -> toMetricKey ( $ data ) ,
424
- self :: $ prefix . Counter:: TYPE . self :: PROMETHEUS_METRIC_KEYS_SUFFIX ,
425
- $ this -> getRedisCommand ( $ data [ ' command ' ] ),
426
- $ data [ ' value ' ] ,
427
- json_encode ( $ data [ ' labelValues ' ]) ,
428
- json_encode ( $ metaData ),
429
- ],
430
- 2
459
+ , [
460
+ $ registryKey ,
461
+ $ metadataKey ,
462
+ $ metricKey ,
463
+ $ metadata -> toJson ( ),
464
+ $ command ,
465
+ $ value ,
466
+ $ ttl
467
+ ],
468
+ 3
431
469
);
432
470
}
433
471
@@ -440,11 +478,13 @@ private function toMetadata(array $data): Metadata
440
478
{
441
479
return Metadata::newBuilder ()
442
480
->withName ($ data ['name ' ])
481
+ ->withType ($ data ['type ' ])
443
482
->withHelp ($ data ['help ' ])
444
483
->withLabelNames ($ data ['labelNames ' ])
445
484
->withLabelValues ($ data ['labelValues ' ])
446
- ->withQuantiles ($ data ['quantiles ' ])
447
- ->withMaxAgeSeconds ($ data ['maxAgeSeconds ' ])
485
+ ->withQuantiles ($ data ['quantiles ' ] ?? null )
486
+ ->withMaxAgeSeconds ($ data ['maxAgeSeconds ' ] ?? null )
487
+ ->withCommand ($ data ['command ' ] ?? null )
448
488
->build ();
449
489
}
450
490
@@ -548,23 +588,23 @@ private function collectSummaries(): array
548
588
// Register summary key
549
589
$ keyPrefix = self ::$ prefix . Summary::TYPE . self ::PROMETHEUS_METRIC_KEYS_SUFFIX ;
550
590
$ summaryRegistryKey = implode (': ' , [$ keyPrefix , 'keys ' ]);
551
- $ metaHashKey = self :: $ prefix . self :: PROMETHEUS_METRIC_META_SUFFIX ;
591
+ $ metadataKey = $ this -> getMetadataKey (Summary:: TYPE ) ;
552
592
$ currentTime = time ();
553
593
554
594
$ result = $ this ->redis ->eval (<<<LUA
555
595
-- Parse script input
556
596
local summaryRegistryKey = KEYS[1]
557
- local metaHashKey = KEYS[2]
597
+ local metadataKey = KEYS[2]
558
598
local currentTime = tonumber(ARGV[1])
559
- local result = {}
560
599
561
600
-- Process each registered summary metric
601
+ local result = {}
562
602
local summaryKeys = redis.call('smembers', summaryRegistryKey)
563
603
for i, summaryKey in ipairs(summaryKeys) do
564
604
-- Get metric sample TTL
565
605
local ttlFieldName = summaryKey .. ":ttl"
566
606
redis.call('set', 'foo', ttlFieldName)
567
- local summaryTtl = redis.call("hget", metaHashKey , ttlFieldName)
607
+ local summaryTtl = redis.call("hget", metadataKey , ttlFieldName)
568
608
if summaryTtl ~= nil then
569
609
summaryTtl = tonumber(summaryTtl)
570
610
end
@@ -582,13 +622,13 @@ private function collectSummaries(): array
582
622
local summarySamples = {}
583
623
if numSamples > 0 then
584
624
-- Configure results
585
- summaryMetadata = redis.call("hget", metaHashKey , summaryKey)
625
+ summaryMetadata = redis.call("hget", metadataKey , summaryKey)
586
626
summarySamples = redis.call("zrange", summaryKey, startScore, "+inf", "byscore")
587
627
else
588
628
-- Remove the metric's associated metadata if there are no associated samples remaining
589
629
redis.call('srem', summaryRegistryKey, summaryKey)
590
- redis.call('hdel', metaHashKey , summaryKey)
591
- redis.call('hdel', metaHashKey , ttlFieldName)
630
+ redis.call('hdel', metadataKey , summaryKey)
631
+ redis.call('hdel', metadataKey , ttlFieldName)
592
632
end
593
633
594
634
-- Add the processed metric to the set of results
@@ -603,17 +643,17 @@ private function collectSummaries(): array
603
643
,
604
644
[
605
645
$ summaryRegistryKey ,
606
- $ metaHashKey ,
646
+ $ metadataKey ,
607
647
$ currentTime ,
608
648
],
609
649
2
610
650
);
611
651
612
- // Format summary metrics and hand them off to the calling collector
652
+ // Format metrics and hand them off to the calling collector
613
653
$ summaries = [];
614
654
$ redisSummaries = json_decode ($ result , true );
615
655
foreach ($ redisSummaries as $ summary ) {
616
- $ serializedSummary = Metric::newBuilder ()
656
+ $ serializedSummary = Metric::newSummaryMetricBuilder ()
617
657
->withMetadata ($ summary ['metadata ' ])
618
658
->withSamples ($ summary ['samples ' ])
619
659
->build ()
@@ -657,26 +697,71 @@ private function collectGauges(): array
657
697
*/
658
698
private function collectCounters (): array
659
699
{
660
- $ keys = $ this ->redis ->sMembers (self ::$ prefix . Counter::TYPE . self ::PROMETHEUS_METRIC_KEYS_SUFFIX );
661
- sort ($ keys );
700
+ // Create Redis keys
701
+ $ registryKey = $ this ->getMetricRegistryKey (Counter::TYPE );
702
+ $ metadataKey = $ this ->getMetadataKey (Counter::TYPE );
703
+
704
+ // Execute transaction to collect metrics
705
+ $ hgetallFunction = self ::HGETALL_FUNCTION ;
706
+ $ result = $ this ->redis ->eval (<<<LUA
707
+ $ hgetallFunction
708
+
709
+ -- Parse script input
710
+ local registryKey = KEYS[1]
711
+ local metadataKey = KEYS[2]
712
+
713
+ -- Process each registered counter metric
714
+ local result = {}
715
+ local counterKeys = redis.call('smembers', registryKey)
716
+ for i, counterKey in ipairs(counterKeys) do
717
+ local doesExist = redis.call('exists', counterKey)
718
+ if doesExist then
719
+ -- Get counter metadata
720
+ local metadata = redis.call('hget', metadataKey, counterKey)
721
+
722
+ -- Get counter sample
723
+ local sample = redis.call('get', counterKey)
724
+
725
+ -- Add the processed metric to the set of results
726
+ result[counterKey] = {}
727
+ result[counterKey]["metadata"] = metadata
728
+ result[counterKey]["samples"] = sample
729
+ else
730
+ -- Remove metadata for expired key
731
+ redis.call('srem', registryKey, counterKey)
732
+ redis.call('hdel', metadataKey, counterKey)
733
+ end
734
+ end
735
+
736
+ -- Return the set of collected metrics
737
+ return cjson.encode(result)
738
+ LUA
739
+ , [
740
+ $ registryKey ,
741
+ $ metadataKey ,
742
+ ],
743
+ 2
744
+ );
745
+
746
+ // Collate counter metrics by metric name
747
+ $ metrics = [];
748
+ $ redisCounters = json_decode ($ result , true );
749
+ foreach ($ redisCounters as $ counter ) {
750
+ // Get metadata
751
+ $ phpMetadata = json_decode ($ counter ['metadata ' ], true );
752
+ $ metadata = MetadataBuilder::fromArray ($ phpMetadata )->build ();
753
+
754
+ // Create or update metric
755
+ $ metricName = $ metadata ->getName ();
756
+ $ builder = $ metrics [$ metricName ] ?? Metric::newScalarMetricBuilder ()->withMetadata ($ metadata );
757
+ $ builder ->withSample ($ counter ['samples ' ], $ metadata ->getLabelValues ());
758
+ $ metrics [$ metricName ] = $ builder ;
759
+ }
760
+
761
+ // Format metrics and hand them off to the calling collector
662
762
$ counters = [];
663
- foreach ($ keys as $ key ) {
664
- $ raw = $ this ->redis ->hGetAll (str_replace ($ this ->redis ->_prefix ('' ), '' , $ key ));
665
- $ counter = json_decode ($ raw ['__meta ' ], true );
666
- unset($ raw ['__meta ' ]);
667
- $ counter ['samples ' ] = [];
668
- foreach ($ raw as $ k => $ value ) {
669
- $ counter ['samples ' ][] = [
670
- 'name ' => $ counter ['name ' ],
671
- 'labelNames ' => [],
672
- 'labelValues ' => json_decode ($ k , true ),
673
- 'value ' => $ value ,
674
- ];
675
- }
676
- usort ($ counter ['samples ' ], function ($ a , $ b ): int {
677
- return strcmp (implode ("" , $ a ['labelValues ' ]), implode ("" , $ b ['labelValues ' ]));
678
- });
679
- $ counters [] = $ counter ;
763
+ foreach ($ metrics as $ _ => $ metric ) {
764
+ $ counters [] = $ metric ->build ()->toArray ();
680
765
}
681
766
return $ counters ;
682
767
}
@@ -739,4 +824,36 @@ private function decodeLabelValues(string $values): array
739
824
}
740
825
return $ decodedValues ;
741
826
}
827
+
828
+ /**
829
+ * @param string $metricType
830
+ * @return string
831
+ */
832
+ private function getMetricRegistryKey (string $ metricType ): string
833
+ {
834
+ $ keyPrefix = self ::$ prefix . $ metricType . self ::PROMETHEUS_METRIC_KEYS_SUFFIX ;
835
+ return implode (': ' , [$ keyPrefix , 'keys ' ]);
836
+ }
837
+
838
+ /**
839
+ * @param string $metricType
840
+ * @return string
841
+ */
842
+ private function getMetadataKey (string $ metricType ): string
843
+ {
844
+ return self ::$ prefix . $ metricType . self ::PROMETHEUS_METRIC_META_SUFFIX ;
845
+ }
846
+
847
+ /**
848
+ * @param Metadata $metadata
849
+ * @return string
850
+ */
851
+ private function getMetricKey (Metadata $ metadata ): string
852
+ {
853
+ $ type = $ metadata ->getType ();
854
+ $ name = $ metadata ->getName ();
855
+ $ labelValues = $ metadata ->getLabelValuesEncoded ();
856
+ $ keyPrefix = self ::$ prefix . $ type . self ::PROMETHEUS_METRIC_KEYS_SUFFIX ;
857
+ return implode (': ' , [$ keyPrefix , $ name , $ labelValues ]);
858
+ }
742
859
}
0 commit comments