HistogramSamplesBuilder::parse()   A
last analyzed

Complexity

Conditions 2
Paths 1

Size

Total Lines 36
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 25
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 36
ccs 25
cts 25
cp 1
crap 2
rs 9.52
1
<?php
2
3
namespace Krenor\Prometheus\Storage\Builders;
4
5
use InvalidArgumentException;
6
use Krenor\Prometheus\Metrics\Histogram;
7
use Tightenco\Collect\Support\Collection;
8
use Krenor\Prometheus\Contracts\SamplesBuilder;
9
10
class HistogramSamplesBuilder extends SamplesBuilder
11
{
12
    /**
13
     * HistogramSamplesBuilder constructor.
14
     *
15
     * @param Histogram $histogram
16
     * @param Collection $items
17
     */
18 10
    public function __construct(Histogram $histogram, Collection $items)
19
    {
20 10
        parent::__construct($histogram, $items);
21 10
    }
22
23
    /**
24
     * {@inheritdoc}
25
     */
26 10
    protected function parse(): Collection
27
    {
28 10
        $name = $this->metric->key();
29 10
        $buckets = $this->metric->buckets()->push('+Inf');
30
31
        return parent
32 10
            ::parse()
33 10
            ->groupBy(fn($data) => json_encode($data['labels']))
34 10
            ->flatMap(function (Collection $data) use ($name, $buckets) {
35 10
                $labels = $data->first()['labels'];
36
37
                /** @var $sum Collection */
38
                /** @var $observations Collection */
39 10
                [$sum, $observations] = $data->partition('bucket', null);
40
41 10
                if ($sum->count() !== 1) {
42 1
                    throw new InvalidArgumentException('Sum of bucket observations missing.');
43
                }
44
45 9
                return $this
46 9
                    ->pad($this->complete($buckets, $observations), new Collection)
47 9
                    ->map(function (array $items) use ($name, $labels) {
48 9
                        $items['name'] = "{$name}_bucket";
49 9
                        $items['labels'] = $labels + [
50 9
                            'le' => $items['bucket'],
51
                        ];
52
53 9
                        return $items;
54 9
                    })->push([
55 9
                        'name'   => "{$name}_count",
56 9
                        'value'  => $observations->sum('value'),
57 9
                        'labels' => $labels,
58 9
                    ])->push([
59 9
                        'name'   => "{$name}_sum",
60 9
                        'value'  => $sum->first()['value'],
61 9
                        'labels' => $labels,
62
                    ]);
63 10
            });
64
    }
65
66
    /**
67
     * @param Collection $buckets
68
     * @param Collection $observations
69
     *
70
     * @return Collection
71
     */
72 9
    private function complete(Collection $buckets, Collection $observations): Collection
73
    {
74 9
        $missing = $buckets
75 9
            ->diff($observations->pluck('bucket'))
76 9
            ->map(fn($bucket) => compact('bucket') + ['value' => 0]);
77
78
        return $observations
79 9
            ->reject(fn(array $observation) => !$buckets->contains($observation['bucket']))->merge($missing)
80 9
            ->sort(function (array $left, array $right) {
81
                // Due to http://php.net/manual/en/language.types.string.php#language.types.string.conversion the
82
                // bucket containing "+Inf" will be cast to 0. Sorting regularly would end up with it incorrectly
83
                // sitting at the very first spot instead of where it belongs - at the end.
84 9
                if ($left['bucket'] === '+Inf') {
85 6
                    return 1;
86
                }
87
88 9
                if ($right['bucket'] === '+Inf') {
89 9
                    return -1;
90
                }
91
92 9
                return $left['bucket'] <=> $right['bucket'];
93 9
            })->values();
94
    }
95
96
    /**
97
     * @param Collection $data
98
     * @param Collection $result
99
     * @param int $sum
100
     * @param int $i
101
     *
102
     * @return Collection
103
     */
104 9
    private function pad(Collection $data, Collection $result, int $sum = 0, int $i = 0): Collection
105
    {
106 9
        if ($i >= $data->count()) {
107 9
            return $result;
108
        }
109
110 9
        $value = $data[$i]['value'] + $sum;
111 9
        $bucket = $data[$i]['bucket'];
112
113 9
        $result->push(compact('bucket', 'value'));
114
115 9
        return $this->pad($data, $result, $value, ++$i);
116
    }
117
}
118