Lines 94.11% 16 / 17
Methods 75.00% 3 / 4
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 recordCall 88.88% 8 / 9 0.00% 0 / 1 4.02
 buildServiceCallStats 100.00% 1 / 1 100.00% 1 / 1 2
 buildLatencyHistogram 100.00% 6 / 6 100.00% 1 / 1 2
12final class ServiceCallStatsRecorder
13{
14    // Prometheus' default seconds buckets, in milliseconds.
15    private const array BOUNDS_MS = [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000];
16
17    private int $count = 0;
18
19    /** @var array<int, int> */
20    private array $bucketCounts = [];
21
22    private int $timed = 0;
23
24    private int $sumMicroseconds = 0;
25
26    public function __construct(private readonly ServiceCallOutcome $outcome) {}
27
28    public function recordCall(?Duration $latency): void
29    {
30        ++$this->count;
31
32        if ($latency === null) {
33            return;
34        }
35
36        ++$this->timed;
37        $this->sumMicroseconds += $latency->toMicroseconds();
38
39        foreach (self::BOUNDS_MS as $index => $bound) {
40            if ($latency->toMicroseconds() <= $bound * 1000) {
41                $this->bucketCounts[$index] = ($this->bucketCounts[$index] ?? 0) + 1;
42
43                return;
44            }
45        }
46    }
47
48    public function buildServiceCallStats(): ServiceCallStats
49    {
50        return new ServiceCallStats($this->outcome, $this->count, $this->timed === 0 ? null : $this->buildLatencyHistogram());
51    }
52
53    private function buildLatencyHistogram(): LatencyHistogram
54    {
55        $cumulative = [];
56        $running = 0;
57
58        foreach (array_keys(self::BOUNDS_MS) as $index) {
59            $running += $this->bucketCounts[$index] ?? 0;
60            $cumulative[] = $running;
61        }
62
63        return new LatencyHistogram(self::BOUNDS_MS, $cumulative, $this->timed, Duration::microseconds($this->sumMicroseconds));
64    }
65}