Completed
Push — develop ( 28a887...d46380 )
by Stan
02:57
created

HistogramSamplesBuilder::group()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

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