1111use Prometheus \Histogram ;
1212use Prometheus \MetricFamilySamples ;
1313use Prometheus \Storage \RedisTxn \Metadata ;
14+ use Prometheus \Storage \RedisTxn \MetadataBuilder ;
1415use Prometheus \Storage \RedisTxn \Metric ;
1516use Prometheus \Summary ;
1617use RedisException ;
@@ -39,6 +40,8 @@ class RedisTxn implements Adapter
3940
4041 const PROMETHEUS_METRIC_META_SUFFIX = '_METRIC_META ' ;
4142
43+ const DEFAULT_TTL_SECONDS = 600 ;
44+
4245 /**
4346 * @var mixed[]
4447 */
@@ -303,15 +306,14 @@ public function updateSummary(array $data): void
303306 {
304307 $ this ->ensureOpenConnection ();
305308
306- // Prepare summary metadata
307- $ metaHashKey = self ::$ prefix . self ::PROMETHEUS_METRIC_META_SUFFIX ;
308- $ summaryMetadata = $ this ->toMetadata ($ data );
309- $ ttl = $ summaryMetadata ->getMaxAgeSeconds ();
309+ // Prepare metadata
310+ $ metadata = $ this ->toMetadata ($ data );
311+ $ ttl = $ metadata ->getMaxAgeSeconds ();
310312
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 ' ] );
313+ // Create Redis keys
314+ $ metricKey = $ this -> getMetricKey ( $ metadata ) ;
315+ $ registryKey = $ this -> getMetricRegistryKey ( $ metadata -> getType () );
316+ $ metadataKey = $ this -> getMetadataKey ( $ metadata -> getType () );
315317
316318 // Get summary sample
317319 //
@@ -350,10 +352,10 @@ public function updateSummary(array $data): void
350352LUA
351353 ,
352354 [
353- $ summaryRegistryKey ,
354- $ metaHashKey ,
355- $ summaryKey ,
356- $ summaryMetadata ->toJson (),
355+ $ registryKey ,
356+ $ metadataKey ,
357+ $ metricKey ,
358+ $ metadata ->toJson (),
357359 $ value ,
358360 $ currentTime ,
359361 $ ttl ,
@@ -407,27 +409,50 @@ public function updateGauge(array $data): void
407409 public function updateCounter (array $ data ): void
408410 {
409411 $ 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
412+
413+ // Prepare metadata
414+ $ metadata = $ this ->toMetadata ($ data );
415+
416+ // Create Redis keys
417+ $ metricKey = $ this ->getMetricKey ($ metadata );
418+ $ registryKey = $ this ->getMetricRegistryKey ($ metadata ->getType ());
419+ $ metadataKey = $ this ->getMetadataKey ($ metadata ->getType ());
420+
421+ // Prepare script input
422+ $ command = $ metadata ->getCommand () === Adapter::COMMAND_INCREMENT_INTEGER ? 'incrby ' : 'incrbyfloat ' ;
423+ $ value = $ data ['value ' ];
424+ $ ttl = time () + ($ metadata ->getMaxAgeSeconds () ?? self ::DEFAULT_TTL_SECONDS );
425+
426+ $ this ->redis ->eval (<<<LUA
427+ -- Parse script input
428+ local registryKey = KEYS[1]
429+ local metadataKey = KEYS[2]
430+ local metricKey = KEYS[3]
431+ local metadata = ARGV[1]
432+ local observeCommand = ARGV[2]
433+ local value = ARGV[3]
434+ local ttl = ARGV[4]
435+
436+ -- Register metric value
437+ redis.call('sadd', registryKey, metricKey)
438+
439+ -- Register metric metadata
440+ redis.call('hset', metadataKey, metricKey, metadata)
441+
442+ -- Update metric value
443+ redis.call(observeCommand, metricKey, value)
444+ redis.call('expire', metricKey, ttl)
420445LUA
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
446+ , [
447+ $ registryKey ,
448+ $ metadataKey ,
449+ $ metricKey ,
450+ $ metadata -> toJson ( ),
451+ $ command ,
452+ $ value ,
453+ $ ttl
454+ ],
455+ 3
431456 );
432457 }
433458
@@ -440,11 +465,13 @@ private function toMetadata(array $data): Metadata
440465 {
441466 return Metadata::newBuilder ()
442467 ->withName ($ data ['name ' ])
468+ ->withType ($ data ['type ' ])
443469 ->withHelp ($ data ['help ' ])
444470 ->withLabelNames ($ data ['labelNames ' ])
445471 ->withLabelValues ($ data ['labelValues ' ])
446- ->withQuantiles ($ data ['quantiles ' ])
447- ->withMaxAgeSeconds ($ data ['maxAgeSeconds ' ])
472+ ->withQuantiles ($ data ['quantiles ' ] ?? null )
473+ ->withMaxAgeSeconds ($ data ['maxAgeSeconds ' ] ?? null )
474+ ->withCommand ($ data ['command ' ] ?? null )
448475 ->build ();
449476 }
450477
@@ -548,23 +575,23 @@ private function collectSummaries(): array
548575 // Register summary key
549576 $ keyPrefix = self ::$ prefix . Summary::TYPE . self ::PROMETHEUS_METRIC_KEYS_SUFFIX ;
550577 $ summaryRegistryKey = implode (': ' , [$ keyPrefix , 'keys ' ]);
551- $ metaHashKey = self :: $ prefix . self :: PROMETHEUS_METRIC_META_SUFFIX ;
578+ $ metadataKey = $ this -> getMetadataKey (Summary:: TYPE ) ;
552579 $ currentTime = time ();
553580
554581 $ result = $ this ->redis ->eval (<<<LUA
555582-- Parse script input
556583local summaryRegistryKey = KEYS[1]
557- local metaHashKey = KEYS[2]
584+ local metadataKey = KEYS[2]
558585local currentTime = tonumber(ARGV[1])
559- local result = {}
560586
561587-- Process each registered summary metric
588+ local result = {}
562589local summaryKeys = redis.call('smembers', summaryRegistryKey)
563590for i, summaryKey in ipairs(summaryKeys) do
564591 -- Get metric sample TTL
565592 local ttlFieldName = summaryKey .. ":ttl"
566593 redis.call('set', 'foo', ttlFieldName)
567- local summaryTtl = redis.call("hget", metaHashKey , ttlFieldName)
594+ local summaryTtl = redis.call("hget", metadataKey , ttlFieldName)
568595 if summaryTtl ~= nil then
569596 summaryTtl = tonumber(summaryTtl)
570597 end
@@ -582,13 +609,13 @@ private function collectSummaries(): array
582609 local summarySamples = {}
583610 if numSamples > 0 then
584611 -- Configure results
585- summaryMetadata = redis.call("hget", metaHashKey , summaryKey)
612+ summaryMetadata = redis.call("hget", metadataKey , summaryKey)
586613 summarySamples = redis.call("zrange", summaryKey, startScore, "+inf", "byscore")
587614 else
588615 -- Remove the metric's associated metadata if there are no associated samples remaining
589616 redis.call('srem', summaryRegistryKey, summaryKey)
590- redis.call('hdel', metaHashKey , summaryKey)
591- redis.call('hdel', metaHashKey , ttlFieldName)
617+ redis.call('hdel', metadataKey , summaryKey)
618+ redis.call('hdel', metadataKey , ttlFieldName)
592619 end
593620
594621 -- Add the processed metric to the set of results
@@ -603,17 +630,17 @@ private function collectSummaries(): array
603630 ,
604631 [
605632 $ summaryRegistryKey ,
606- $ metaHashKey ,
633+ $ metadataKey ,
607634 $ currentTime ,
608635 ],
609636 2
610637 );
611638
612- // Format summary metrics and hand them off to the calling collector
639+ // Format metrics and hand them off to the calling collector
613640 $ summaries = [];
614641 $ redisSummaries = json_decode ($ result , true );
615642 foreach ($ redisSummaries as $ summary ) {
616- $ serializedSummary = Metric::newBuilder ()
643+ $ serializedSummary = Metric::newSummaryMetricBuilder ()
617644 ->withMetadata ($ summary ['metadata ' ])
618645 ->withSamples ($ summary ['samples ' ])
619646 ->build ()
@@ -657,26 +684,68 @@ private function collectGauges(): array
657684 */
658685 private function collectCounters (): array
659686 {
660- $ keys = $ this ->redis ->sMembers (self ::$ prefix . Counter::TYPE . self ::PROMETHEUS_METRIC_KEYS_SUFFIX );
661- sort ($ keys );
687+ // Create Redis keys
688+ $ registryKey = $ this ->getMetricRegistryKey (Counter::TYPE );
689+ $ metadataKey = $ this ->getMetadataKey (Counter::TYPE );
690+
691+ // Execute transaction to collect metrics
692+ $ result = $ this ->redis ->eval (<<<LUA
693+ -- Parse script input
694+ local registryKey = KEYS[1]
695+ local metadataKey = KEYS[2]
696+
697+ -- Process each registered counter metric
698+ local result = {}
699+ local counterKeys = redis.call('smembers', registryKey)
700+ for i, counterKey in ipairs(counterKeys) do
701+ local doesExist = redis.call('exists', counterKey)
702+ if doesExist then
703+ -- Get counter metadata
704+ local metadata = redis.call('hget', metadataKey, counterKey)
705+
706+ -- Get counter sample
707+ local sample = redis.call('get', counterKey)
708+
709+ -- Add the processed metric to the set of results
710+ result[counterKey] = {}
711+ result[counterKey]["metadata"] = metadata
712+ result[counterKey]["samples"] = sample
713+ else
714+ -- Remove metadata for expired key
715+ redis.call('srem', registryKey, counterKey)
716+ redis.call('hdel', metadataKey, counterKey)
717+ end
718+ end
719+
720+ -- Return the set of collected metrics
721+ return cjson.encode(result)
722+ LUA
723+ , [
724+ $ registryKey ,
725+ $ metadataKey ,
726+ ],
727+ 2
728+ );
729+
730+ // Collate counter metrics by metric name
731+ $ metrics = [];
732+ $ redisCounters = json_decode ($ result , true );
733+ foreach ($ redisCounters as $ counter ) {
734+ // Get metadata
735+ $ phpMetadata = json_decode ($ counter ['metadata ' ], true );
736+ $ metadata = MetadataBuilder::fromArray ($ phpMetadata )->build ();
737+
738+ // Create or update metric
739+ $ metricName = $ metadata ->getName ();
740+ $ builder = $ metrics [$ metricName ] ?? Metric::newScalarMetricBuilder ()->withMetadata ($ metadata );
741+ $ builder ->withSample ($ counter ['samples ' ], $ metadata ->getLabelValues ());
742+ $ metrics [$ metricName ] = $ builder ;
743+ }
744+
745+ // Format metrics and hand them off to the calling collector
662746 $ 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 ;
747+ foreach ($ metrics as $ _ => $ metric ) {
748+ $ counters [] = $ metric ->build ()->toArray ();
680749 }
681750 return $ counters ;
682751 }
@@ -739,4 +808,36 @@ private function decodeLabelValues(string $values): array
739808 }
740809 return $ decodedValues ;
741810 }
811+
812+ /**
813+ * @param string $metricType
814+ * @return string
815+ */
816+ private function getMetricRegistryKey (string $ metricType ): string
817+ {
818+ $ keyPrefix = self ::$ prefix . $ metricType . self ::PROMETHEUS_METRIC_KEYS_SUFFIX ;
819+ return implode (': ' , [$ keyPrefix , 'keys ' ]);
820+ }
821+
822+ /**
823+ * @param string $metricType
824+ * @return string
825+ */
826+ private function getMetadataKey (string $ metricType ): string
827+ {
828+ return self ::$ prefix . $ metricType . self ::PROMETHEUS_METRIC_META_SUFFIX ;
829+ }
830+
831+ /**
832+ * @param Metadata $metadata
833+ * @return string
834+ */
835+ private function getMetricKey (Metadata $ metadata ): string
836+ {
837+ $ type = $ metadata ->getType ();
838+ $ name = $ metadata ->getName ();
839+ $ labelValues = $ metadata ->getLabelValuesEncoded ();
840+ $ keyPrefix = self ::$ prefix . $ type . self ::PROMETHEUS_METRIC_KEYS_SUFFIX ;
841+ return implode (': ' , [$ keyPrefix , $ name , $ labelValues ]);
842+ }
742843}
0 commit comments