PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / diagnostics / DiagnosticRequestGroup.php

DiagnosticRequestGroup.php in 404 Solution trunk, at includes/diagnostics/DiagnosticRequestGroup.php

143 lines 4.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Everything one request left behind in a diagnostic journal, and what those
9 * records say about how it ended.
10 *
11 * The journals are append-only streams of independent records, but the unit a
12 * developer reads -- and the unit a bounded support payload has to keep whole
13 * or drop whole -- is the request. This is that unit: which lines belong to a
14 * request, what they cost in bytes, and the three facts that decide whether
15 * the request is worth budget (did it reach a terminal record, did it record a
16 * failure, and which attempt was it retrying).
17 *
18 * It holds no ordering and no budget policy; that is
19 * ABJ_404_Solution_DiagnosticEvidencePriority's. It reads no files; that is
20 * ABJ_404_Solution_DiagnosticJournalExcerpt's.
21 */
22 final class ABJ_404_Solution_DiagnosticRequestGroup {
23
24 /** @var string */
25 private $requestId;
26
27 /** @var bool Whether these records carry a usable request id at all. */
28 private $joinable;
29
30 /** @var array<int, int> Line indexes into the caller's stream, in stream order. */
31 private $indexes = array();
32
33 /** @var int Bytes these lines cost, newlines included. */
34 private $bytes = 0;
35
36 /** @var bool */
37 private $terminal = false;
38
39 /** @var bool */
40 private $failure = false;
41
42 /** @var array<string, bool> Request ids this one recorded as its retry parent. */
43 private $parents = array();
44
45 public function __construct(string $requestId, bool $joinable) {
46 $this->requestId = $requestId;
47 $this->joinable = $joinable;
48 }
49
50 public function requestId(): string {
51 return $this->requestId;
52 }
53
54 /** @return array<int, int> */
55 public function indexes(): array {
56 return $this->indexes;
57 }
58
59 public function bytes(): int {
60 return $this->bytes;
61 }
62
63 public function recordCount(): int {
64 return count($this->indexes);
65 }
66
67 public function hasRecords(): bool {
68 return $this->indexes !== array();
69 }
70
71 /** @return array<int, string> */
72 public function parentIds(): array {
73 return array_keys($this->parents);
74 }
75
76 /**
77 * Whether this request is one the investigation is about.
78 *
79 * "No terminal record" counts: a request that simply stops, with no
80 * response and no teardown, IS the symptom under investigation, and it can
81 * only be recognised by that absence. Unjoinable lines are never failing --
82 * they belong to no request, so they cannot be one that failed.
83 */
84 public function isFailing(): bool {
85 return $this->joinable && ($this->failure || !$this->terminal);
86 }
87
88 /**
89 * Whether this request's record run must survive INTACT rather than
90 * being trimmed to its two ends.
91 *
92 * A completed request's head and tail really are its two most
93 * informative records, because a "how it ended" record exists at the
94 * tail. A request with no terminal event never wrote one: every record
95 * it still holds, including the middle, is the only account of what it
96 * was doing while it stalled (report 193: the 165-second holder had no
97 * request_end, and trimming it to head+tail dropped exactly the seven
98 * records that showed it was stuck).
99 */
100 public function isMaximallyDecisive(): bool {
101 return $this->joinable && !$this->terminal;
102 }
103
104 public function addLine(int $index, int $bytes): void {
105 $this->indexes[] = $index;
106 $this->bytes += $bytes;
107 }
108
109 /**
110 * Fold one of this request's own records into the classification.
111 *
112 * @param array<array-key, mixed> $record
113 */
114 public function applyRecord(array $record): void {
115 $event = isset($record['event']) && is_scalar($record['event']) ? (string)$record['event'] : '';
116 if (in_array($event, ABJ_404_Solution_DiagnosticEvidencePriority::TERMINAL_EVENTS, true)) {
117 $this->terminal = true;
118 }
119 if (in_array($event, ABJ_404_Solution_DiagnosticEvidencePriority::FAILURE_EVENTS, true)) {
120 $this->markFailed();
121 }
122 // Any status other than 'complete' -- 'error', a truncated status
123 // string, anything a future boundary invents -- is treated as a
124 // failure. An allowlist rather than a deny-list, so a new failure
125 // status cannot be silently filed as healthy.
126 $status = isset($record['status']) && is_scalar($record['status']) ? (string)$record['status'] : '';
127 if ($status !== '' && $status !== 'complete') {
128 $this->markFailed();
129 }
130 if ($event === 'selftest' && array_key_exists('ok', $record) && $record['ok'] === false) {
131 $this->markFailed();
132 }
133 if (isset($record['retry_parent_id']) && is_scalar($record['retry_parent_id'])
134 && (string)$record['retry_parent_id'] !== '') {
135 $this->parents[(string)$record['retry_parent_id']] = true;
136 }
137 }
138
139 public function markFailed(): void {
140 $this->failure = true;
141 }
142 }
143