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 / DeliveredTableResponseSize.php

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

85 lines 4.1 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 * How many bytes of an admin-table response actually REACHED the browser.
9 *
10 * The exact mirror of ABJ_404_Solution_EncodedTableResponseSize, which answers
11 * the same question about the other end of the same response: that class says
12 * how many bytes the server encoded, this one says how many the browser
13 * measured arriving. Two modules rather than one because the two numbers come
14 * from different subsystems, different record kinds and different journals'
15 * worth of assumptions, and because either can be present without the other --
16 * which is the whole finding when they disagree. Joining them is
17 * ABJ_404_Solution_ResponseBodyDeliveryEvidence's job, not this class's.
18 *
19 * Read ONLY from Resource Timing's `decodedBodySize`, never from the client
20 * report's own `bytes` field. That field is `responseText.length`, a UTF-16
21 * CHARACTER count, and the table payload carries URLs that are not guaranteed
22 * ASCII -- comparing a character count against the server's octet count would
23 * manufacture a rewritten-in-transit verdict out of an accented redirect.
24 * `rt` is also the first field the client trims out of an oversized report
25 * (view_updater_transport_telemetry_delivery.js TRIM_FIELD_ORDER), so its
26 * absence is ordinary and stays a named absence rather than a number.
27 */
28 final class ABJ_404_Solution_DeliveredTableResponseSize {
29
30 /**
31 * What the browser said one table request weighed, or a named absence.
32 *
33 * Pure: it takes journal lines rather than reading them, so a report with
34 * no timing entry, a trimmed report, and a report about a different
35 * attempt are all directly assertable.
36 *
37 * The attempt a report is ABOUT is resolved through
38 * ABJ_404_Solution_DiagnosticClientVerdict, the one extractor that already
39 * maps a browser report to the journal key its attempt was recorded under.
40 * Deriving a second keying here is exactly the defect that made the
41 * browser's verdicts land on orphan placeholder groups for a whole
42 * release.
43 *
44 * @param array<int, string> $lines Checkpoint JSONL lines, oldest first.
45 * @param string $requestId Normalized ledger id of the table attempt.
46 * @return array{bytes: int|null, source: string, resource_timing_state: string}
47 */
48 public static function forRequest(array $lines, string $requestId): array {
49 $result = array(
50 'bytes' => null,
51 'source' => ABJ_404_Solution_MeasuredBodyBytes::SOURCE_UNAVAILABLE,
52 'resource_timing_state' => ABJ_404_Solution_MeasuredBodyBytes::SOURCE_UNAVAILABLE,
53 );
54 if ($requestId === '') {
55 return $result;
56 }
57 foreach ($lines as $line) {
58 // Lines that cannot possibly match are rejected before the JSON
59 // decoder sees them: this pass runs over whole journals.
60 if (strpos($line, ABJ_404_Solution_DiagnosticClientVerdict::PRIOR_ATTEMPT_EVENT) === false) {
61 continue;
62 }
63 $record = json_decode($line, true);
64 if (!is_array($record)
65 || ABJ_404_Solution_DiagnosticClientVerdict::reportedOutcome($record)['id'] !== $requestId) {
66 continue;
67 }
68 // Latest report wins: a retried attempt is reported more than once
69 // and the later report describes the later delivery.
70 $report = is_array($record['report'] ?? null) ? $record['report'] : array();
71 $timing = is_array($report['rt'] ?? null) ? $report['rt'] : array();
72 $bytes = ABJ_404_Solution_MeasuredBodyBytes::disclosed($timing['decodedBodySize'] ?? null);
73 $state = $report['rtState'] ?? null;
74 $result = array(
75 'bytes' => $bytes,
76 'source' => $bytes === null ? ABJ_404_Solution_MeasuredBodyBytes::SOURCE_UNAVAILABLE : ABJ_404_Solution_MeasuredBodyBytes::SOURCE_RESOURCE_TIMING,
77 'resource_timing_state' => is_scalar($state)
78 ? (string)$state : ABJ_404_Solution_MeasuredBodyBytes::SOURCE_UNAVAILABLE,
79 );
80 }
81 return $result;
82 }
83
84 }
85