Lines 94.11% 16 / 17
Functions and Methods 75.00% 3 / 4
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
ServiceCallStatsRecorder 94.11% 16 / 17 75.00% 3 / 4 9.02 0.00% 0 / 1
 __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
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Runtime\Broker;
6
7use Stewart\Contracts\Time\Duration;
8use Stewart\Runtime\Control\Protocol\Status\LatencyHistogram;
9use Stewart\Runtime\Control\Protocol\Status\ServiceCallStats;
10use Stewart\Runtime\Model\ServiceCallOutcome;
11
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}