Skip to content

Commit cd6ca11

Browse files
committed
Optimize counter metric collection.
1 parent 87997f8 commit cd6ca11

File tree

7 files changed

+759
-471
lines changed

7 files changed

+759
-471
lines changed

src/Prometheus/Storage/RedisTxn.php

Lines changed: 164 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Prometheus\Histogram;
1212
use Prometheus\MetricFamilySamples;
1313
use Prometheus\Storage\RedisTxn\Metadata;
14+
use Prometheus\Storage\RedisTxn\MetadataBuilder;
1415
use Prometheus\Storage\RedisTxn\Metric;
1516
use Prometheus\Summary;
1617
use 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
350352
LUA
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)
420445
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
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
556583
local summaryRegistryKey = KEYS[1]
557-
local metaHashKey = KEYS[2]
584+
local metadataKey = KEYS[2]
558585
local currentTime = tonumber(ARGV[1])
559-
local result = {}
560586
561587
-- Process each registered summary metric
588+
local result = {}
562589
local summaryKeys = redis.call('smembers', summaryRegistryKey)
563590
for 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

Comments
 (0)