Skip to content

Commit 257bf8b

Browse files
committed
Added support of Predis library as storage adapter (#127)
Signed-off-by: Łukasz Adamczewski <[email protected]>
1 parent 82e42b5 commit 257bf8b

13 files changed

+420
-43
lines changed

composer.json

+2
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@
2525
"phpstan/phpstan-phpunit": "^1.1.0",
2626
"phpstan/phpstan-strict-rules": "^1.1.0",
2727
"phpunit/phpunit": "^9.4",
28+
"predis/predis": "^2.0",
2829
"squizlabs/php_codesniffer": "^3.6",
2930
"symfony/polyfill-apcu": "^1.6"
3031
},
3132
"suggest": {
3233
"ext-redis": "Required if using Redis.",
34+
"predis/predis": "Required if using Predis.",
3335
"ext-apc": "Required if using APCu.",
3436
"promphp/prometheus_push_gateway_php": "An easy client for using Prometheus PushGateway.",
3537
"symfony/polyfill-apcu": "Required if you use APCu on PHP8.0+"

examples/metrics.php

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
if ($adapter === 'redis') {
1212
Redis::setDefaultOptions(['host' => $_SERVER['REDIS_HOST'] ?? '127.0.0.1']);
1313
$adapter = new Prometheus\Storage\Redis();
14+
} elseif ($adapter === 'predis') {
15+
$adapter = new Prometheus\Storage\Predis(['host' => $_SERVER['REDIS_HOST'] ?? '127.0.0.1']);
1416
} elseif ($adapter === 'apc') {
1517
$adapter = new Prometheus\Storage\APC();
1618
} elseif ($adapter === 'apcng') {

src/Prometheus/Storage/Predis.php

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Prometheus\Storage;
6+
7+
use Predis\Configuration\Option\Prefix;
8+
use Prometheus\Exception\StorageException;
9+
use Predis\Client;
10+
11+
/**
12+
* @property Client $redis
13+
*/
14+
final class Predis extends Redis
15+
{
16+
/**
17+
* @var mixed[]
18+
*/
19+
private static array $defaultOptions = [
20+
'host' => '127.0.0.1',
21+
'port' => 6379,
22+
'scheme' => 'tcp',
23+
'timeout' => 0.1,
24+
'read_timeout' => '10',
25+
'persistent' => 0,
26+
'password' => null,
27+
];
28+
29+
public function __construct(array $options = [])
30+
{
31+
$this->options = array_merge(self::$defaultOptions, $options);
32+
33+
parent::__construct($options);
34+
35+
$this->redis = new Client($this->options);
36+
}
37+
38+
public static function fromClient(Client $redis): self
39+
{
40+
if ($redis->isConnected() === false) {
41+
throw new StorageException('Connection to Redis server not established');
42+
}
43+
44+
$self = new self();
45+
$self->redis = $redis;
46+
47+
return $self;
48+
}
49+
50+
protected function ensureOpenConnection(): void
51+
{
52+
if ($this->redis->isConnected() === false) {
53+
$this->redis->connect();
54+
}
55+
}
56+
57+
public static function fromExistingConnection(\Redis $redis): Redis
58+
{
59+
throw new \RuntimeException('This method is not supported by predis adapter');
60+
}
61+
62+
protected function getGlobalPrefix(): ?string
63+
{
64+
if ($this->redis->getOptions()->prefix === null) {
65+
return null;
66+
}
67+
68+
if ($this->redis->getOptions()->prefix instanceof Prefix) {
69+
return $this->redis->getOptions()->prefix->getPrefix();
70+
}
71+
72+
return null;
73+
}
74+
75+
/**
76+
* @param mixed[] $args
77+
* @param int $keysCount
78+
* @return mixed[]
79+
*/
80+
protected function evalParams(array $args, int $keysCount): array
81+
{
82+
return [$keysCount, ...$args];
83+
}
84+
85+
86+
protected function prefix(string $key): string
87+
{
88+
// the predis is doing key prefixing on its own
89+
return '';
90+
}
91+
92+
protected function setParams(array $input): array
93+
{
94+
$values = array_values($input);
95+
$params = [];
96+
97+
if (isset($input['EX'])) {
98+
$params[] = 'EX';
99+
$params[] = $input['EX'];
100+
}
101+
102+
if (isset($input['PX'])) {
103+
$params[] = 'PX';
104+
$params[] = $input['PX'];
105+
}
106+
107+
$params[] = $values[0];
108+
109+
return $params;
110+
}
111+
}

src/Prometheus/Storage/Redis.php

+76-43
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Prometheus\Storage;
66

77
use InvalidArgumentException;
8+
use Predis\Client;
89
use Prometheus\Counter;
910
use Prometheus\Exception\StorageException;
1011
use Prometheus\Gauge;
@@ -38,12 +39,12 @@ class Redis implements Adapter
3839
/**
3940
* @var mixed[]
4041
*/
41-
private $options = [];
42+
protected $options = [];
4243

4344
/**
44-
* @var \Redis
45+
* @var \Redis|Client
4546
*/
46-
private $redis;
47+
protected $redis;
4748

4849
/**
4950
* @var boolean
@@ -112,8 +113,7 @@ public function wipeStorage(): void
112113

113114
$searchPattern = "";
114115

115-
$globalPrefix = $this->redis->getOption(\Redis::OPT_PREFIX);
116-
// @phpstan-ignore-next-line false positive, phpstan thinks getOptions returns int
116+
$globalPrefix = $this->getGlobalPrefix();
117117
if (is_string($globalPrefix)) {
118118
$searchPattern .= $globalPrefix;
119119
}
@@ -133,8 +133,10 @@ public function wipeStorage(): void
133133
until cursor == "0"
134134
LUA
135135
,
136-
[$searchPattern],
137-
0
136+
...$this->evalParams(
137+
[$searchPattern],
138+
0
139+
)
138140
);
139141
}
140142

@@ -187,7 +189,7 @@ function (array $metric): MetricFamilySamples {
187189
/**
188190
* @throws StorageException
189191
*/
190-
private function ensureOpenConnection(): void
192+
protected function ensureOpenConnection(): void
191193
{
192194
if ($this->connectionInitialized === true) {
193195
return;
@@ -260,15 +262,17 @@ public function updateHistogram(array $data): void
260262
return result
261263
LUA
262264
,
263-
[
264-
$this->toMetricKey($data),
265-
self::$prefix . Histogram::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX,
266-
json_encode(['b' => 'sum', 'labelValues' => $data['labelValues']]),
267-
json_encode(['b' => $bucketToIncrease, 'labelValues' => $data['labelValues']]),
268-
$data['value'],
269-
json_encode($metaData),
270-
],
271-
2
265+
...$this->evalParams(
266+
[
267+
$this->toMetricKey($data),
268+
self::$prefix . Histogram::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX,
269+
json_encode(['b' => 'sum', 'labelValues' => $data['labelValues']]),
270+
json_encode(['b' => $bucketToIncrease, 'labelValues' => $data['labelValues']]),
271+
$data['value'],
272+
json_encode($metaData),
273+
],
274+
2
275+
)
272276
);
273277
}
274278

@@ -301,7 +305,7 @@ public function updateSummary(array $data): void
301305
$done = false;
302306
while (!$done) {
303307
$sampleKey = $valueKey . ':' . uniqid('', true);
304-
$done = $this->redis->set($sampleKey, $data['value'], ['NX', 'EX' => $data['maxAgeSeconds']]);
308+
$done = $this->redis->set($sampleKey, $data['value'], ...$this->setParams(['NX', 'EX' => $data['maxAgeSeconds']]));
305309
}
306310
}
307311

@@ -331,15 +335,17 @@ public function updateGauge(array $data): void
331335
end
332336
LUA
333337
,
334-
[
335-
$this->toMetricKey($data),
336-
self::$prefix . Gauge::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX,
337-
$this->getRedisCommand($data['command']),
338-
json_encode($data['labelValues']),
339-
$data['value'],
340-
json_encode($metaData),
341-
],
342-
2
338+
...$this->evalParams(
339+
[
340+
$this->toMetricKey($data),
341+
self::$prefix . Gauge::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX,
342+
$this->getRedisCommand($data['command']),
343+
json_encode($data['labelValues']),
344+
$data['value'],
345+
json_encode($metaData),
346+
],
347+
2
348+
)
343349
);
344350
}
345351

@@ -362,15 +368,17 @@ public function updateCounter(array $data): void
362368
return result
363369
LUA
364370
,
365-
[
366-
$this->toMetricKey($data),
367-
self::$prefix . Counter::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX,
368-
$this->getRedisCommand($data['command']),
369-
$data['value'],
370-
json_encode($data['labelValues']),
371-
json_encode($metaData),
372-
],
373-
2
371+
...$this->evalParams(
372+
[
373+
$this->toMetricKey($data),
374+
self::$prefix . Counter::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX,
375+
$this->getRedisCommand($data['command']),
376+
$data['value'],
377+
json_encode($data['labelValues']),
378+
json_encode($metaData),
379+
],
380+
2
381+
)
374382
);
375383
}
376384

@@ -395,7 +403,7 @@ private function collectHistograms(): array
395403
sort($keys);
396404
$histograms = [];
397405
foreach ($keys as $key) {
398-
$raw = $this->redis->hGetAll(str_replace($this->redis->_prefix(''), '', $key));
406+
$raw = $this->redis->hGetAll(str_replace($this->prefix(''), '', $key));
399407
if (!isset($raw['__meta'])) {
400408
continue;
401409
}
@@ -473,12 +481,11 @@ private function collectHistograms(): array
473481
*/
474482
private function removePrefixFromKey(string $key): string
475483
{
476-
// @phpstan-ignore-next-line false positive, phpstan thinks getOptions returns int
477-
if ($this->redis->getOption(\Redis::OPT_PREFIX) === null) {
484+
if ($this->getGlobalPrefix() === null) {
478485
return $key;
479486
}
480-
// @phpstan-ignore-next-line false positive, phpstan thinks getOptions returns int
481-
return substr($key, strlen($this->redis->getOption(\Redis::OPT_PREFIX)));
487+
488+
return substr($key, strlen($this->getGlobalPrefix()));
482489
}
483490

484491
/**
@@ -578,7 +585,7 @@ private function collectGauges(bool $sortMetrics = true): array
578585
sort($keys);
579586
$gauges = [];
580587
foreach ($keys as $key) {
581-
$raw = $this->redis->hGetAll(str_replace($this->redis->_prefix(''), '', $key));
588+
$raw = $this->redis->hGetAll(str_replace($this->prefix(''), '', $key));
582589
if (!isset($raw['__meta'])) {
583590
continue;
584591
}
@@ -614,7 +621,7 @@ private function collectCounters(bool $sortMetrics = true): array
614621
sort($keys);
615622
$counters = [];
616623
foreach ($keys as $key) {
617-
$raw = $this->redis->hGetAll(str_replace($this->redis->_prefix(''), '', $key));
624+
$raw = $this->redis->hGetAll(str_replace($this->prefix(''), '', $key));
618625
if (!isset($raw['__meta'])) {
619626
continue;
620627
}
@@ -699,4 +706,30 @@ private function decodeLabelValues(string $values): array
699706
}
700707
return $decodedValues;
701708
}
709+
710+
protected function getGlobalPrefix(): ?string
711+
{
712+
// @phpstan-ignore-next-line false positive, phpstan thinks getOptions returns int
713+
return $this->redis->getOption(\Redis::OPT_PREFIX);
714+
}
715+
716+
/**
717+
* @param mixed[] $args
718+
* @param int $keysCount
719+
* @return mixed[]
720+
*/
721+
protected function evalParams(array $args, int $keysCount): array
722+
{
723+
return [$args, $keysCount];
724+
}
725+
726+
protected function prefix(string $key): string
727+
{
728+
return $this->redis->_prefix($key);
729+
}
730+
731+
protected function setParams(array $params): array
732+
{
733+
return [$params];
734+
}
702735
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Test\Prometheus\Predis;
6+
7+
use Predis\Client;
8+
use Prometheus\Storage\Predis;
9+
use Test\Prometheus\AbstractCollectorRegistryTest;
10+
11+
/**
12+
* @requires predis
13+
*/
14+
class CollectorRegistryTest extends AbstractCollectorRegistryTest
15+
{
16+
public function configureAdapter(): void
17+
{
18+
$this->adapter = new Predis(['host' => REDIS_HOST]);
19+
$this->adapter->wipeStorage();
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Test\Prometheus\Predis;
6+
7+
use Predis\Client;
8+
use Prometheus\Storage\Predis;
9+
use Test\Prometheus\AbstractCounterTest;
10+
11+
/**
12+
* See https://prometheus.io/docs/instrumenting/exposition_formats/
13+
* @requires extension redis
14+
*/
15+
class CounterTest extends AbstractCounterTest
16+
{
17+
public function configureAdapter(): void
18+
{
19+
$this->adapter = new Predis(['host' => REDIS_HOST]);
20+
$this->adapter->wipeStorage();
21+
}
22+
}

0 commit comments

Comments
 (0)