From cd6ca1145f41a840512b4af416610819b45ea953 Mon Sep 17 00:00:00 2001 From: Arris Ray Date: Sun, 22 Jan 2023 00:16:17 -0500 Subject: [PATCH] Optimize counter metric collection. --- src/Prometheus/Storage/RedisTxn.php | 227 ++++++++++---- src/Prometheus/Storage/RedisTxn/Metadata.php | 254 +++++++++------- .../Storage/RedisTxn/MetadataBuilder.php | 280 +++++++++++------- src/Prometheus/Storage/RedisTxn/Metric.php | 98 +++--- .../Storage/RedisTxn/SampleBuilder.php | 158 +++++----- .../Storage/RedisTxn/ScalarMetricBuilder.php | 94 ++++++ ...icBuilder.php => SummaryMetricBuilder.php} | 119 ++++---- 7 files changed, 759 insertions(+), 471 deletions(-) create mode 100644 src/Prometheus/Storage/RedisTxn/ScalarMetricBuilder.php rename src/Prometheus/Storage/RedisTxn/{MetricBuilder.php => SummaryMetricBuilder.php} (64%) diff --git a/src/Prometheus/Storage/RedisTxn.php b/src/Prometheus/Storage/RedisTxn.php index cb4e11e5..fae00f10 100644 --- a/src/Prometheus/Storage/RedisTxn.php +++ b/src/Prometheus/Storage/RedisTxn.php @@ -11,6 +11,7 @@ use Prometheus\Histogram; use Prometheus\MetricFamilySamples; use Prometheus\Storage\RedisTxn\Metadata; +use Prometheus\Storage\RedisTxn\MetadataBuilder; use Prometheus\Storage\RedisTxn\Metric; use Prometheus\Summary; use RedisException; @@ -39,6 +40,8 @@ class RedisTxn implements Adapter const PROMETHEUS_METRIC_META_SUFFIX = '_METRIC_META'; + const DEFAULT_TTL_SECONDS = 600; + /** * @var mixed[] */ @@ -303,15 +306,14 @@ public function updateSummary(array $data): void { $this->ensureOpenConnection(); - // Prepare summary metadata - $metaHashKey = self::$prefix . self::PROMETHEUS_METRIC_META_SUFFIX; - $summaryMetadata = $this->toMetadata($data); - $ttl = $summaryMetadata->getMaxAgeSeconds(); + // Prepare metadata + $metadata = $this->toMetadata($data); + $ttl = $metadata->getMaxAgeSeconds(); - // Create summary key - $keyPrefix = self::$prefix . Summary::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX; - $summaryKey = implode(':', [$keyPrefix, $data['name'], $summaryMetadata->getLabelValuesEncoded()]); - $summaryRegistryKey = implode(':', [$keyPrefix, 'keys']); + // Create Redis keys + $metricKey = $this->getMetricKey($metadata); + $registryKey = $this->getMetricRegistryKey($metadata->getType()); + $metadataKey = $this->getMetadataKey($metadata->getType()); // Get summary sample // @@ -350,10 +352,10 @@ public function updateSummary(array $data): void LUA , [ - $summaryRegistryKey, - $metaHashKey, - $summaryKey, - $summaryMetadata->toJson(), + $registryKey, + $metadataKey, + $metricKey, + $metadata->toJson(), $value, $currentTime, $ttl, @@ -407,27 +409,50 @@ public function updateGauge(array $data): void public function updateCounter(array $data): void { $this->ensureOpenConnection(); - $metaData = $data; - unset($metaData['value'], $metaData['labelValues'], $metaData['command']); - $this->redis->eval( - <<toMetadata($data); + + // Create Redis keys + $metricKey = $this->getMetricKey($metadata); + $registryKey = $this->getMetricRegistryKey($metadata->getType()); + $metadataKey = $this->getMetadataKey($metadata->getType()); + + // Prepare script input + $command = $metadata->getCommand() === Adapter::COMMAND_INCREMENT_INTEGER ? 'incrby' : 'incrbyfloat'; + $value = $data['value']; + $ttl = time() + ($metadata->getMaxAgeSeconds() ?? self::DEFAULT_TTL_SECONDS); + + $this->redis->eval(<<toMetricKey($data), - self::$prefix . Counter::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX, - $this->getRedisCommand($data['command']), - $data['value'], - json_encode($data['labelValues']), - json_encode($metaData), - ], - 2 + , [ + $registryKey, + $metadataKey, + $metricKey, + $metadata->toJson(), + $command, + $value, + $ttl + ], + 3 ); } @@ -440,11 +465,13 @@ private function toMetadata(array $data): Metadata { return Metadata::newBuilder() ->withName($data['name']) + ->withType($data['type']) ->withHelp($data['help']) ->withLabelNames($data['labelNames']) ->withLabelValues($data['labelValues']) - ->withQuantiles($data['quantiles']) - ->withMaxAgeSeconds($data['maxAgeSeconds']) + ->withQuantiles($data['quantiles'] ?? null) + ->withMaxAgeSeconds($data['maxAgeSeconds'] ?? null) + ->withCommand($data['command'] ?? null) ->build(); } @@ -548,23 +575,23 @@ private function collectSummaries(): array // Register summary key $keyPrefix = self::$prefix . Summary::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX; $summaryRegistryKey = implode(':', [$keyPrefix, 'keys']); - $metaHashKey = self::$prefix . self::PROMETHEUS_METRIC_META_SUFFIX; + $metadataKey = $this->getMetadataKey(Summary::TYPE); $currentTime = time(); $result = $this->redis->eval(<< 0 then -- Configure results - summaryMetadata = redis.call("hget", metaHashKey, summaryKey) + summaryMetadata = redis.call("hget", metadataKey, summaryKey) summarySamples = redis.call("zrange", summaryKey, startScore, "+inf", "byscore") else -- Remove the metric's associated metadata if there are no associated samples remaining redis.call('srem', summaryRegistryKey, summaryKey) - redis.call('hdel', metaHashKey, summaryKey) - redis.call('hdel', metaHashKey, ttlFieldName) + redis.call('hdel', metadataKey, summaryKey) + redis.call('hdel', metadataKey, ttlFieldName) end -- Add the processed metric to the set of results @@ -603,17 +630,17 @@ private function collectSummaries(): array , [ $summaryRegistryKey, - $metaHashKey, + $metadataKey, $currentTime, ], 2 ); - // Format summary metrics and hand them off to the calling collector + // Format metrics and hand them off to the calling collector $summaries = []; $redisSummaries = json_decode($result, true); foreach ($redisSummaries as $summary) { - $serializedSummary = Metric::newBuilder() + $serializedSummary = Metric::newSummaryMetricBuilder() ->withMetadata($summary['metadata']) ->withSamples($summary['samples']) ->build() @@ -657,26 +684,68 @@ private function collectGauges(): array */ private function collectCounters(): array { - $keys = $this->redis->sMembers(self::$prefix . Counter::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX); - sort($keys); + // Create Redis keys + $registryKey = $this->getMetricRegistryKey(Counter::TYPE); + $metadataKey = $this->getMetadataKey(Counter::TYPE); + + // Execute transaction to collect metrics + $result = $this->redis->eval(<<build(); + + // Create or update metric + $metricName = $metadata->getName(); + $builder = $metrics[$metricName] ?? Metric::newScalarMetricBuilder()->withMetadata($metadata); + $builder->withSample($counter['samples'], $metadata->getLabelValues()); + $metrics[$metricName] = $builder; + } + + // Format metrics and hand them off to the calling collector $counters = []; - foreach ($keys as $key) { - $raw = $this->redis->hGetAll(str_replace($this->redis->_prefix(''), '', $key)); - $counter = json_decode($raw['__meta'], true); - unset($raw['__meta']); - $counter['samples'] = []; - foreach ($raw as $k => $value) { - $counter['samples'][] = [ - 'name' => $counter['name'], - 'labelNames' => [], - 'labelValues' => json_decode($k, true), - 'value' => $value, - ]; - } - usort($counter['samples'], function ($a, $b): int { - return strcmp(implode("", $a['labelValues']), implode("", $b['labelValues'])); - }); - $counters[] = $counter; + foreach ($metrics as $_ => $metric) { + $counters[] = $metric->build()->toArray(); } return $counters; } @@ -739,4 +808,36 @@ private function decodeLabelValues(string $values): array } return $decodedValues; } + + /** + * @param string $metricType + * @return string + */ + private function getMetricRegistryKey(string $metricType): string + { + $keyPrefix = self::$prefix . $metricType . self::PROMETHEUS_METRIC_KEYS_SUFFIX; + return implode(':', [$keyPrefix, 'keys']); + } + + /** + * @param string $metricType + * @return string + */ + private function getMetadataKey(string $metricType): string + { + return self::$prefix . $metricType . self::PROMETHEUS_METRIC_META_SUFFIX; + } + + /** + * @param Metadata $metadata + * @return string + */ + private function getMetricKey(Metadata $metadata): string + { + $type = $metadata->getType(); + $name = $metadata->getName(); + $labelValues = $metadata->getLabelValuesEncoded(); + $keyPrefix = self::$prefix . $type . self::PROMETHEUS_METRIC_KEYS_SUFFIX; + return implode(':', [$keyPrefix, $name, $labelValues]); + } } diff --git a/src/Prometheus/Storage/RedisTxn/Metadata.php b/src/Prometheus/Storage/RedisTxn/Metadata.php index 0c2e3554..c082eadd 100644 --- a/src/Prometheus/Storage/RedisTxn/Metadata.php +++ b/src/Prometheus/Storage/RedisTxn/Metadata.php @@ -9,115 +9,141 @@ */ class Metadata { - /** - * @var string - */ - private $name; - - /** - * @var string - */ - private $help; - - /** - * @var string[] - */ - private $labelNames; - - /** - * @var mixed[] - */ - private $labelValues; - - /** - * @var int - */ - private $maxAgeSeconds; - - /** - * @var float[] - */ - private $quantiles; - - /** - * @return MetadataBuilder - */ - public static function newBuilder(): MetadataBuilder - { - return new MetadataBuilder(); - } - - /** - * @param string $name - * @param string $help - * @param array $labelNames - * @param array $labelValues - * @param int $maxAgeSeconds - * @param array $quantiles - */ - public function __construct( - string $name, - string $help, - array $labelNames, - array $labelValues, - int $maxAgeSeconds, - array $quantiles - ) - { - $this->name = $name; - $this->help = $help; - $this->labelNames = $labelNames; - $this->labelValues = $labelValues; - $this->maxAgeSeconds = $maxAgeSeconds; - $this->quantiles = $quantiles; - } - - /** + /** + * @var string + */ + private $name; + + /** + * @var string + */ + private $type; + + /** + * @var string + */ + private $help; + + /** + * @var string[] + */ + private $labelNames; + + /** + * @var mixed[] + */ + private $labelValues; + + /** + * @var int + */ + private $maxAgeSeconds; + + /** + * @var float[] + */ + private $quantiles; + + /** + * @var int + */ + private $command; + + /** + * @return MetadataBuilder + */ + public static function newBuilder(): MetadataBuilder + { + return new MetadataBuilder(); + } + + /** + * @param string $name + * @param string $type + * @param string $help + * @param array $labelNames + * @param array $labelValues + * @param int $maxAgeSeconds + * @param array $quantiles + * @param int $command + */ + public function __construct( + string $name, + string $type, + string $help, + array $labelNames, + array $labelValues, + int $maxAgeSeconds, + array $quantiles, + int $command + ) + { + $this->name = $name; + $this->type = $type; + $this->help = $help; + $this->labelNames = $labelNames; + $this->labelValues = $labelValues; + $this->maxAgeSeconds = $maxAgeSeconds; + $this->quantiles = $quantiles; + $this->command = $command; + } + + /** * Prometheus metric name. * - * @return string - */ - public function getName(): string - { - return $this->name; - } - - /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Prometheus metric type. + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** * Prometheus metric help description. * * @internal Optional. - * @return string - */ - public function getHelp(): string - { - return $this->help; - } - - /** + * @return string + */ + public function getHelp(): string + { + return $this->help; + } + + /** * Prometheus metric label names. * * Note that each label introduces a degree of cardinality for a given metric. * * @internal Optional. It is permissible to have no label names. - * @return string[] - */ - public function getLabelNames(): array - { - return $this->labelNames; - } - - /** + * @return string[] + */ + public function getLabelNames(): array + { + return $this->labelNames; + } + + /** * Prometheus metric label values. * * Note that each label value should correspond to a label name. * * @internal Optional. - * @return mixed[] - */ - public function getLabelValues(): array - { - return $this->labelValues; - } + * @return mixed[] + */ + public function getLabelValues(): array + { + return $this->labelValues; + } /** * Prometheus metric label values encoded for storage in Redis. @@ -132,27 +158,35 @@ public function getLabelValuesEncoded(): string return base64_encode(json_encode($this->labelValues)); } - /** + /** * Prometheus metric time-to-live (TTL) in seconds. * * This property is used internally by the storage adapter to enforce a TTL for metrics stored in Redis. * - * @return int - */ - public function getMaxAgeSeconds(): int - { - return $this->maxAgeSeconds; - } - - /** + * @return int + */ + public function getMaxAgeSeconds(): int + { + return $this->maxAgeSeconds; + } + + /** * Prometheus metric metadata that describes the set of quantiles to report for a summary-type metric. * - * @return array - */ - public function getQuantiles(): array - { - return $this->quantiles; - } + * @return array + */ + public function getQuantiles(): array + { + return $this->quantiles; + } + + /** + * @return int + */ + public function getCommand(): int + { + return $this->command; + } /** * Represents this data structure as a JSON object. @@ -163,11 +197,13 @@ public function toJson(): string { return json_encode([ 'name' => $this->getName(), + 'type' => $this->getType(), 'help' => $this->getHelp(), 'labelNames' => $this->getLabelNames(), 'labelValues' => $this->getLabelValuesEncoded(), 'maxAgeSeconds' => $this->getMaxAgeSeconds(), 'quantiles' => $this->getQuantiles(), + 'command' => $this->getCommand(), ]); } } diff --git a/src/Prometheus/Storage/RedisTxn/MetadataBuilder.php b/src/Prometheus/Storage/RedisTxn/MetadataBuilder.php index 6f507290..5eec2f7f 100644 --- a/src/Prometheus/Storage/RedisTxn/MetadataBuilder.php +++ b/src/Prometheus/Storage/RedisTxn/MetadataBuilder.php @@ -3,131 +3,181 @@ namespace Prometheus\Storage\RedisTxn; use InvalidArgumentException; +use Prometheus\Storage\Adapter; /** * Fluent-builder for the {@see \Prometheus\Storage\RedisTxn\Metadata} structure. */ class MetadataBuilder { - /** - * @var string|null - */ - private $name; - - /** - * @var string|null - */ - private $help; - - /** - * @var string[]|null - */ - private $labelNames; - - /** - * @var mixed[]|null - */ - private $labelValues; - - /** - * @var int|null - */ - private $maxAgeSeconds; - - /** - * @var float[]|null - */ - private $quantiles; - - /** - * @param string $name - * @return MetadataBuilder - */ - public function withName(string $name): MetadataBuilder - { - $this->name = $name; - return $this; - } - - /** - * @param string|null $help - * @return MetadataBuilder - */ - public function withHelp(?string $help): MetadataBuilder - { - $this->help = $help; - return $this; - } - - /** - * @param string[]|null $labelNames - * @return MetadataBuilder - */ - public function withLabelNames(?array $labelNames): MetadataBuilder - { - $this->labelNames = $labelNames; - return $this; - } - - /** - * @param string|array|null $labelValues - * @return MetadataBuilder - */ - public function withLabelValues($labelValues): MetadataBuilder - { - if (is_array($labelValues)) { + /** + * @var string|null + */ + private $name; + + /** + * @var string|null + */ + private $type; + + /** + * @var string|null + */ + private $help; + + /** + * @var string[]|null + */ + private $labelNames; + + /** + * @var mixed[]|null + */ + private $labelValues; + + /** + * @var int|null + */ + private $maxAgeSeconds; + + /** + * @var float[]|null + */ + private $quantiles; + + /** + * @var int|null + */ + private $command; + + /** + * @param array $metadata + * @return MetadataBuilder + */ + public static function fromArray(array $metadata): MetadataBuilder + { + return Metadata::newBuilder() + ->withName($metadata['name']) + ->withType($metadata['type']) + ->withHelp($metadata['help'] ?? null) + ->withLabelNames($metadata['labelNames'] ?? null) + ->withLabelValues($metadata['labelValues'] ?? null) + ->withMaxAgeSeconds($metadata['maxAgeSeconds'] ?? null) + ->withQuantiles($metadata['quantiles'] ?? null) + ->withCommand($metadata['command'] ?? null); + } + + /** + * @param string $name + * @return MetadataBuilder + */ + public function withName(string $name): MetadataBuilder + { + $this->name = $name; + return $this; + } + + /** + * @param string $type + * @return MetadataBuilder + */ + public function withType(string $type): MetadataBuilder + { + $this->type = $type; + return $this; + } + + /** + * @param string|null $help + * @return MetadataBuilder + */ + public function withHelp(?string $help): MetadataBuilder + { + $this->help = $help; + return $this; + } + + /** + * @param string[]|null $labelNames + * @return MetadataBuilder + */ + public function withLabelNames(?array $labelNames): MetadataBuilder + { + $this->labelNames = $labelNames; + return $this; + } + + /** + * @param string|array|null $labelValues + * @return MetadataBuilder + */ + public function withLabelValues($labelValues): MetadataBuilder + { + if (($labelValues === null) || is_array($labelValues)) { $this->labelValues = $labelValues; } else { // See Metadata::getLabelNamesEncoded() for the inverse operation on this data. $this->labelValues = json_decode(base64_decode($labelValues)); } return $this; - } - - /** - * @param int|null $maxAgeSeconds - * @return MetadataBuilder - */ - public function withMaxAgeSeconds(?int $maxAgeSeconds): MetadataBuilder - { - $this->maxAgeSeconds = $maxAgeSeconds; - return $this; - } - - /** - * @param float[]|null $quantiles - * @return MetadataBuilder - */ - public function withQuantiles(?array $quantiles): MetadataBuilder - { - $this->quantiles = $quantiles; - return $this; - } - - /** - * @return Metadata - */ - public function build(): Metadata - { - $this->validate(); - return new Metadata( - $this->name, - $this->help ?? '', - $this->labelNames ?? [], - $this->labelValues ?? [], - $this->maxAgeSeconds ?? 0, - $this->quantiles ?? [] - ); - } - - /** - * @return void - * @throws InvalidArgumentException - */ - private function validate(): void - { - if ($this->name === null) { - throw new InvalidArgumentException('Metadata name field is required'); - } - } + } + + /** + * @param int|null $maxAgeSeconds + * @return MetadataBuilder + */ + public function withMaxAgeSeconds(?int $maxAgeSeconds): MetadataBuilder + { + $this->maxAgeSeconds = $maxAgeSeconds; + return $this; + } + + /** + * @param float[]|null $quantiles + * @return MetadataBuilder + */ + public function withQuantiles(?array $quantiles): MetadataBuilder + { + $this->quantiles = $quantiles; + return $this; + } + + /** + * @param int|null $command + * @return MetadataBuilder + */ + public function withCommand(?int $command): MetadataBuilder + { + $this->command = $command; + return $this; + } + + /** + * @return Metadata + */ + public function build(): Metadata + { + $this->validate(); + return new Metadata( + $this->name, + $this->type ?? '', + $this->help ?? '', + $this->labelNames ?? [], + $this->labelValues ?? [], + $this->maxAgeSeconds ?? 0, + $this->quantiles ?? [], + $this->command ?? Adapter::COMMAND_INCREMENT_FLOAT + ); + } + + /** + * @return void + * @throws InvalidArgumentException + */ + private function validate(): void + { + if ($this->name === null) { + throw new InvalidArgumentException('Metadata name field is required'); + } + } } diff --git a/src/Prometheus/Storage/RedisTxn/Metric.php b/src/Prometheus/Storage/RedisTxn/Metric.php index ae008dce..e3d8bcb5 100644 --- a/src/Prometheus/Storage/RedisTxn/Metric.php +++ b/src/Prometheus/Storage/RedisTxn/Metric.php @@ -2,10 +2,6 @@ namespace Prometheus\Storage\RedisTxn; -use Prometheus\Math; -use Prometheus\MetricFamilySamples; -use Prometheus\Summary as PrometheusSummary; - /** * This structure represents all the data associated with a single, unique metric that this library * should present to a Prometheus scraper. @@ -15,51 +11,67 @@ */ class Metric { - /** - * @var Metadata - */ - private $metadata; + /** + * @var Metadata + */ + private $metadata; + + /** + * @var double[]|float[]|int[] + */ + private $samples; + + /** + * @return SummaryMetricBuilder + */ + public static function newSummaryMetricBuilder(): SummaryMetricBuilder + { + return new SummaryMetricBuilder(); + } - /** - * @var double[]|float[]|int[] - */ - private $samples; + /** + * @return ScalarMetricBuilder + */ + public static function newScalarMetricBuilder(): ScalarMetricBuilder + { + return new ScalarMetricBuilder(); + } - /** - * @return MetricBuilder - */ - public static function newBuilder(): MetricBuilder - { - return new MetricBuilder(); - } + /** + * @param Metadata $metadata + * @param array|int|float $samples + */ + public function __construct(Metadata $metadata, $samples) + { + $this->metadata = $metadata; + $this->samples = $samples; + } - /** - * @param Metadata $metadata - * @param array $samples - */ - public function __construct(Metadata $metadata, array $samples) - { - $this->metadata = $metadata; - $this->samples = $samples; - } + /** + * @return Metadata + */ + public function getMetadata(): Metadata + { + return $this->metadata; + } - /** + /** * Represents this data structure as a PHP associative array. * * This array generally conforms to the expectations of the {@see \Prometheus\MetricFamilySamples} structure. * - * @return array - */ - public function toArray(): array - { - return [ - 'name' => $this->metadata->getName(), - 'help' => $this->metadata->getHelp(), - 'type' => PrometheusSummary::TYPE, - 'labelNames' => $this->metadata->getLabelNames(), - 'maxAgeSeconds' => $this->metadata->getMaxAgeSeconds(), - 'quantiles' => $this->metadata->getQuantiles(), - 'samples' => $this->samples, - ]; - } + * @return array + */ + public function toArray(): array + { + return [ + 'name' => $this->metadata->getName(), + 'help' => $this->metadata->getHelp(), + 'type' => $this->metadata->getType(), + 'labelNames' => $this->metadata->getLabelNames(), + 'maxAgeSeconds' => $this->metadata->getMaxAgeSeconds(), + 'quantiles' => $this->metadata->getQuantiles() ?? [], + 'samples' => $this->samples, + ]; + } } diff --git a/src/Prometheus/Storage/RedisTxn/SampleBuilder.php b/src/Prometheus/Storage/RedisTxn/SampleBuilder.php index fd8b59fb..cc14f611 100644 --- a/src/Prometheus/Storage/RedisTxn/SampleBuilder.php +++ b/src/Prometheus/Storage/RedisTxn/SampleBuilder.php @@ -9,92 +9,94 @@ */ class SampleBuilder { - /** - * @var string|null - */ - private $name; + /** + * @var string|null + */ + private $name; - /** - * @var string[]|null - */ - private $labelNames; + /** + * @var string[]|null + */ + private $labelNames; - /** - * @var float[]|int[]|null - */ - private $labelValues; + /** + * @var float[]|int[]|null + */ + private $labelValues; - /** - * @var float|int|null - */ - private $value; + /** + * @var float|int|null + */ + private $value; - /** - * @param string $name - * @return SampleBuilder - */ - public function withName(string $name): SampleBuilder - { - $this->name = $name; - return $this; - } + /** + * @param string $name + * @return SampleBuilder + */ + public function withName(string $name): SampleBuilder + { + $this->name = $name; + return $this; + } - /** - * @param string[] $labelNames - * @return SampleBuilder - */ - public function withLabelNames(array $labelNames): SampleBuilder - { - $this->labelNames = $labelNames; - return $this; - } + /** + * @param string[] $labelNames + * @return SampleBuilder + */ + public function withLabelNames(array $labelNames): SampleBuilder + { + $this->labelNames = $labelNames; + return $this; + } - /** - * @param float[]|int[] $labelValues - * @return SampleBuilder - */ - public function withLabelValues(array $labelValues): SampleBuilder - { - $this->labelValues = $labelValues; - return $this; - } + /** + * @param float[]|int[] $labelValues + * @return SampleBuilder + */ + public function withLabelValues(array $labelValues): SampleBuilder + { + $this->labelValues = $labelValues; + return $this; + } - /** - * @param float|int $value - * @return SampleBuilder - */ - public function withValue($value): SampleBuilder - { - $this->value = $value; - return $this; - } + /** + * @param float|int $value + * @return SampleBuilder + */ + public function withValue($value): SampleBuilder + { + $this->value = floatval($value) && (floatval($value) != intval($value)) + ? floatval($value) + : intval($value); + return $this; + } - /** - * @return Sample - */ - public function build(): Sample - { - $this->validate(); - return new Sample( - $this->name, - $this->labelNames ?? [], - $this->labelValues ?? [], - $this->value - ); - } + /** + * @return Sample + */ + public function build(): Sample + { + $this->validate(); + return new Sample( + $this->name, + $this->labelNames ?? [], + $this->labelValues ?? [], + $this->value + ); + } - /** - * @return void - * @throws InvalidArgumentException - */ - private function validate(): void - { - if ($this->name === null) { - throw new InvalidArgumentException('Sample name field is required'); - } + /** + * @return void + * @throws InvalidArgumentException + */ + private function validate(): void + { + if ($this->name === null) { + throw new InvalidArgumentException('Sample name field is required'); + } - if ($this->value === null) { - throw new InvalidArgumentException('Sample name field is required'); - } - } + if ($this->value === null) { + throw new InvalidArgumentException('Sample name field is required'); + } + } } diff --git a/src/Prometheus/Storage/RedisTxn/ScalarMetricBuilder.php b/src/Prometheus/Storage/RedisTxn/ScalarMetricBuilder.php new file mode 100644 index 00000000..18fad273 --- /dev/null +++ b/src/Prometheus/Storage/RedisTxn/ScalarMetricBuilder.php @@ -0,0 +1,94 @@ +metadata = $metadata; + return $this; + } + + /** + * @param string $sample + * @param array $labelValues + * @return ScalarMetricBuilder + */ + public function withSample(string $sample, array $labelValues): ScalarMetricBuilder + { + $sample = $this->coerceSampleType($sample); + $jsonLabelValues = json_encode($labelValues); + $this->samples[$jsonLabelValues] = $this->toSample($sample, $labelValues); + return $this; + } + + /** + * @return Metric + */ + public function build(): Metric + { + $this->validate(); + ksort($this->samples); + return new Metric($this->metadata, $this->samples); + } + + /** + * @return void + * @throws InvalidArgumentException + */ + private function validate(): void + { + if ($this->metadata === null) { + throw new InvalidArgumentException('Summary metadata field is required.'); + } + + if ($this->samples === null) { + throw new InvalidArgumentException('Summary samples field is required.'); + } + } + + + /** + * @param float|int $sourceSample + * @param array $labelValues + * @return array + */ + private function toSample($sourceSample, array $labelValues): array + { + return Sample::newBuilder() + ->withName($this->metadata->getName()) + ->withLabelNames([]) + ->withLabelValues($labelValues) + ->withValue($sourceSample) + ->build() + ->toArray(); + } + + /** + * @param string $sample + * @return float|int + */ + private function coerceSampleType(string $sample) + { + return (floatval($sample) && floatval($sample) != intval($sample)) + ? floatval($sample) + : intval($sample); + } +} \ No newline at end of file diff --git a/src/Prometheus/Storage/RedisTxn/MetricBuilder.php b/src/Prometheus/Storage/RedisTxn/SummaryMetricBuilder.php similarity index 64% rename from src/Prometheus/Storage/RedisTxn/MetricBuilder.php rename to src/Prometheus/Storage/RedisTxn/SummaryMetricBuilder.php index 086dda85..2d747fe7 100644 --- a/src/Prometheus/Storage/RedisTxn/MetricBuilder.php +++ b/src/Prometheus/Storage/RedisTxn/SummaryMetricBuilder.php @@ -8,69 +8,62 @@ /** * Fluent-builder for the {@see \Prometheus\Storage\RedisTxn\Metric} data structure. */ -class MetricBuilder +class SummaryMetricBuilder { - /** - * @var Metadata|null - */ - private $metadata = null; - - /** - * @var array|null - */ - private $samples = null; - - /** - * @param string $jsonMetadata JSON-encoded array of metadata fields. - * @return MetricBuilder - */ - public function withMetadata(string $jsonMetadata): MetricBuilder - { - $metadata = json_decode($jsonMetadata, true); - $this->metadata = Metadata::newBuilder() - ->withName($metadata['name']) - ->withHelp($metadata['help'] ?? null) - ->withLabelNames($metadata['labelNames'] ?? null) - ->withLabelValues($metadata['labelValues'] ?? null) - ->withMaxAgeSeconds($metadata['maxAgeSeconds'] ?? null) - ->withQuantiles($metadata['quantiles'] ?? null) - ->build(); - return $this; - } - - /** - * @param array $samples - * @return MetricBuilder - */ - public function withSamples(array $samples): MetricBuilder - { - $this->samples = $this->processSamples($samples); - return $this; - } - - /** - * @return Metric - */ - public function build(): Metric - { - $this->validate(); - return new Metric($this->metadata, $this->samples); - } - - /** - * @return void - * @throws InvalidArgumentException - */ - private function validate(): void - { - if ($this->metadata === null) { - throw new InvalidArgumentException('Summary metadata field is required.'); - } - - if ($this->samples === null) { - throw new InvalidArgumentException('Summary samples field is required.'); - } - } + /** + * @var Metadata|null + */ + private $metadata = null; + + /** + * @var array|null + */ + private $samples = null; + + /** + * @param string $jsonMetadata JSON-encoded array of metadata fields. + * @return SummaryMetricBuilder + */ + public function withMetadata(string $jsonMetadata): SummaryMetricBuilder + { + $metadata = json_decode($jsonMetadata, true); + $this->metadata = MetadataBuilder::fromArray($metadata)->build(); + return $this; + } + + /** + * @param array $samples + * @return SummaryMetricBuilder + */ + public function withSamples(array $samples): SummaryMetricBuilder + { + $this->samples = $this->processSummarySamples($samples); + return $this; + } + + /** + * @return Metric + */ + public function build(): Metric + { + $this->validate(); + return new Metric($this->metadata, $this->samples); + } + + /** + * @return void + * @throws InvalidArgumentException + */ + private function validate(): void + { + if ($this->metadata === null) { + throw new InvalidArgumentException('Summary metadata field is required.'); + } + + if ($this->samples === null) { + throw new InvalidArgumentException('Summary samples field is required.'); + } + } /** * Calculates the configured quantiles, count, and sum for a summary metric given a set of observed values. @@ -78,7 +71,7 @@ private function validate(): void * @param array $sourceSamples * @return array */ - private function processSamples(array $sourceSamples): array + private function processSummarySamples(array $sourceSamples): array { // Return value $samples = [];