Prometheus::setMetricValues()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
nc 2
nop 3
dl 0
loc 7
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ArangoClient\Prometheus;
6
7
use GuzzleHttp\Psr7\StreamWrapper;
8
use Psr\Http\Message\ResponseInterface;
9
10
class Prometheus
11
{
12
    protected Metrics $output;
13
14
    public function __construct()
15
    {
16
        $this->output = new Metrics();
17
    }
18
19
20
    public function parseStream(ResponseInterface $from): Metrics
21
    {
22
        $stream = $from->getBody();
23
        $resource = StreamWrapper::getResource($stream);
24
25
        while ($line = stream_get_line($resource, 1000000, "\n")) {
26
            $this->handleLine($line);
27
        }
28
        return $this->output;
29
    }
30
31
    public function parseText(string $content): Metrics
32
    {
33
        $fileObject = new \SplFileObject('php://memory', 'r+');
34
        $fileObject->fwrite($content);
35
        $fileObject->rewind();
36
37
        while ($fileObject->valid()) {
38
            $line = $fileObject->current();
39
            if (is_string($line)) {
40
                $this->handleLine($line);
41
            }
42
            $fileObject->next();
43
        }
44
45
        return $this->output;
46
    }
47
48
    protected function handleLine(string $line): void
49
    {
50
        match (true) {
51
            str_starts_with($line, "# TYPE ") => $this->setType($line),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->setType($line) targeting ArangoClient\Prometheus\Prometheus::setType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
52
            str_starts_with($line, "# HELP ") => $this->setHelpText($line),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->setHelpText($line) targeting ArangoClient\Prometheus\Prometheus::setHelpText() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
53
            default =>  $this->setMetric($line),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->setMetric($line) targeting ArangoClient\Prometheus\Prometheus::setMetric() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
54
        };
55
    }
56
57
    protected function createMetricIfNew(string $name): void
58
    {
59
        if (!property_exists($this->output, $name)) {
60
            $this->output->$name = new Metric($name);
61
        }
62
    }
63
64
    protected function setType(string $line): void
65
    {
66
        $matches = [];
67
        if (
68
            preg_match(
69
                "/^# TYPE (?'metricName'[a-zA-Z_:][a-zA-Z0-9_:]*) (?'type'counter|gauge|histogram|summary)$/",
70
                $line,
71
                $matches,
72
            )
73
        ) {
74
            $metricName = $matches['metricName'];
75
            $this->createMetricIfNew($metricName);
76
77
            $this->output->$metricName->type = $matches['type'];
78
        }
79
    }
80
81
    protected function setHelpText(string $line): void
82
    {
83
        $matches = [];
84
        if (
85
            preg_match(
86
                "/^# HELP (?'metricName'[a-zA-Z_:][a-zA-Z0-9_:]*) (?'helpText'[\W\w]*)/",
87
                $line,
88
                $matches,
89
            )
90
        ) {
91
            $metricName = $matches['metricName'];
92
93
            $this->createMetricIfNew($metricName);
94
95
            $this->output->$metricName->help = $matches['helpText'];
96
        }
97
    }
98
99
    protected function setMetric(string $line): void
100
    {
101
        $matches = [];
102
        if (
103
            preg_match(
104
                "/^(?'rawMetricName'[a-zA-Z_:][a-zA-Z0-9_:]*)(?'rawLabels'{[\W\w]*})? (?'value'[-+.,eE\d]+) ?(?'timestamp'[0-9]+)?$/",
105
                $line,
106
                $matches,
107
            )
108
        ) {
109
            // The first group contains the metricName and the suffix
110
            [$metricName, $suffix] = $this->extractMetricNameAndSuffix($matches['rawMetricName']);
111
112
            $this->createMetricIfNew($metricName);
113
114
            // Feed the remaining matches to the suffix dependent parser
115
            match ($suffix) {
116
                "bucket" => $this->setBucket($metricName, $matches),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->setBucket($metricName, $matches) targeting ArangoClient\Prometheus\Prometheus::setBucket() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
117
                default => $this->setMetricValues($metricName, $matches, $suffix),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->setMetricValues($...ame, $matches, $suffix) targeting ArangoClient\Prometheus\...heus::setMetricValues() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
118
            };
119
        }
120
    }
121
122
    /**
123
     * @return string[]
124
     */
125
    protected function extractMetricNameAndSuffix(string $value): array
126
    {
127
        $nameParticles = explode("_", $value);
128
        $suffix = array_pop($nameParticles);
129
130
        if (in_array($suffix, ["bucket", "count", "sum"])) {
131
            $baseName = implode("_", $nameParticles);
132
            return [$baseName, $suffix];
133
        }
134
135
        return [$value, 'value'];
136
    }
137
138
    /**
139
     * @param array<string|int|float> $matches
140
     */
141
    protected function setBucket(mixed $metricName, array $matches): void
142
    {
143
        $labels = null;
144
        $timestamp = null;
145
146
        $value = is_numeric($matches['value']) ? +$matches['value'] : $matches['value'];
147
148
        if (array_key_exists("rawLabels", $matches)) {
149
            $labels = $this->extractLabels((string) $matches['rawLabels']);
150
        }
151
152
        if (array_key_exists("timestamp", $matches)) {
153
            $timestamp = is_numeric($matches['timestamp']) ? +$matches['timestamp'] : $matches['timestamp'];
154
        }
155
156
        $bucket = new Bucket(
157
            $value,
158
            $labels,
159
            $timestamp,
160
        );
161
162
        $this->output->$metricName->buckets[] = $bucket;
163
    }
164
165
    /**
166
     * @param array<string, mixed> $matches
167
     */
168
    protected function setMetricValues(string $metricName, array $matches, string $suffix): void
169
    {
170
        $this->output->$metricName->$suffix = is_numeric($matches['value']) ? +$matches['value'] : $matches['value'];
171
172
        $this->setLabels($metricName, $matches);
173
174
        $this->setTimestamp($metricName, $matches);
175
    }
176
177
    /**
178
     * @param array<string, mixed> $matches
179
     */
180
    protected function setLabels(string $metricName, array $matches): void
181
    {
182
        if (array_key_exists("rawLabels", $matches)) {
183
            $this->output->$metricName->labels  = $this->extractLabels($matches['rawLabels']);
184
        }
185
    }
186
187
    /**
188
     * @param array<string, mixed> $matches
189
     */
190
    protected function setTimestamp(string $metricName, array $matches): void
191
    {
192
        if (array_key_exists("timestamp", $matches)) {
193
            $this->output->$metricName->timestamp = is_numeric($matches['timestamp']) ? +$matches['timestamp'] : $matches['timestamp'];
194
        }
195
    }
196
197
198
    /**
199
     * @return array<string, float|int|string>
200
     */
201
    protected function extractLabels(string $rawLabels): array
202
    {
203
        $labels = [];
204
        $matches = [];
205
        if (
206
            preg_match_all(
207
                "/(?'label'[a-zA-Z0-9]*)=\"(?'value'[^\"]*)\"/",
208
                $rawLabels,
209
                $matches,
210
            )
211
        ) {
212
            foreach ($matches['label'] as $key => $label) {
213
                $value = $matches['value'][$key];
214
215
                $labels[(string) $label] = is_numeric($value) ? +$value : $value;
216
            }
217
        }
218
219
        return $labels;
220
    }
221
}
222