Lines 92.85% 26 / 28
Functions and Methods 77.77% 7 / 9
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
AppMetrics 92.85% 26 / 28 77.77% 7 / 9 12.05 0.00% 0 / 1
 __construct 100.00% 4 / 4 100.00% 1 / 1 3
 recordActivityReports 100.00% 8 / 8 100.00% 1 / 1 2
 recordFinishedCall 100.00% 1 / 1 100.00% 1 / 1 1
 recordRefusedCall 0.00% 0 / 1 0.00% 0 / 1 2
 recordExposedCommand 0.00% 0 / 1 0.00% 0 / 1 2
 recordLastFailure 100.00% 10 / 10 100.00% 1 / 1 1
 listRunningTotals 100.00% 1 / 1 100.00% 1 / 1 1
 findAppRunningTotals 100.00% 1 / 1 100.00% 1 / 1 1
 findOrCreateRunningTotals 100.00% 1 / 1 100.00% 1 / 1 1
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Runtime\Broker;
6
7use Stewart\Contracts\App\AppId;
8use Stewart\Contracts\Time\Clock;
9use Stewart\Contracts\Time\Duration;
10use Stewart\Runtime\Broker\Collection\WorkerSlotCollection;
11use Stewart\Runtime\Control\Protocol\Status\FailureReport;
12use Stewart\Runtime\Ipc\Message\AppActivityReport;
13use Stewart\Runtime\Ipc\Message\AppFailed;
14use Stewart\Runtime\Ipc\Message\Pong;
15use Stewart\Runtime\Model\ExposedCommandOutcome;
16use Stewart\Runtime\Model\ResourceScope;
17use Stewart\Runtime\Model\ServiceCallOutcome;
18use Stewart\Runtime\Model\WorkerId;
19use WeakMap;
20
21final class AppMetrics
22{
23    /** @var array<string, AppRunningTotals> */
24    private array $runningTotals = [];
25
26    /** @var WeakMap<WorkerHandle, array<string, AppActivityReport>> */
27    private WeakMap $lastActivityReports;
28
29    public function __construct(
30        WorkerSlotCollection $workerSlots,
31        private readonly Clock $clock,
32    ) {
33        $this->lastActivityReports = new WeakMap();
34
35        foreach ($workerSlots as $slot) {
36            foreach ($slot->apps as $app) {
37                $this->runningTotals[$app->id->value] = AppRunningTotals::forApp($app->id, $app->class, $slot->workerId);
38            }
39        }
40    }
41
42    public function recordActivityReports(WorkerHandle $handle, Pong $pong): void
43    {
44        // A respawned worker counts from zero, so its first report is all new activity.
45        $previous = $this->lastActivityReports[$handle] ?? [];
46        $latest = [];
47        $at = $this->clock->getNow();
48
49        foreach ($pong->apps as $report) {
50            $scope = $report->scope->wireValue();
51            $this->findOrCreateRunningTotals($handle->id, $report->scope)->recordActivityReport($report, $previous[$scope] ?? null, $at);
52            $latest[$scope] = $report;
53        }
54
55        $this->lastActivityReports[$handle] = $latest;
56    }
57
58    public function recordFinishedCall(WorkerId $workerId, ResourceScope $scope, ServiceCallOutcome $outcome, Duration $latency): void
59    {
60        $this->findOrCreateRunningTotals($workerId, $scope)->recordCall($outcome, $latency);
61    }
62
63    public function recordRefusedCall(WorkerId $workerId, ResourceScope $scope): void
64    {
65        $this->findOrCreateRunningTotals($workerId, $scope)->recordCall(ServiceCallOutcome::Refused, null);
66    }
67
68    public function recordExposedCommand(AppId $appId, ExposedCommandOutcome $outcome): void
69    {
70        $this->findAppRunningTotals($appId)?->recordExposedCommand($outcome);
71    }
72
73    public function recordLastFailure(WorkerId $workerId, AppFailed $failure): void
74    {
75        $this->findOrCreateRunningTotals($workerId, $failure->scope)->recordLastFailure(
76            new FailureReport(
77                $failure->phase,
78                $failure->class,
79                $failure->message,
80                $failure->origin,
81                $failure->details?->reason,
82                $this->clock->getNow(),
83            ),
84        );
85    }
86
87    /** @return list<AppRunningTotals> */
88    public function listRunningTotals(): array
89    {
90        return array_values($this->runningTotals);
91    }
92
93    public function findAppRunningTotals(AppId $appId): ?AppRunningTotals
94    {
95        return $this->runningTotals[$appId->value] ?? null;
96    }
97
98    private function findOrCreateRunningTotals(WorkerId $workerId, ResourceScope $scope): AppRunningTotals
99    {
100        return $this->runningTotals[AppRunningTotals::buildMetricsKey($scope, $workerId)] ??= AppRunningTotals::forScope($scope, $workerId);
101    }
102}