DarkRiDDeR12 мин

Почему общий PASS не равен бюджету пути: модель сравнения и допуск

ПроизводительностьFrontend

Проблема появляется, когда общий PASS считают доказательством неизменности пути. Путь может получить лишнюю работу в одной части и сохранить приемлемую общую сумму. Цена — неверный выбор действия: команда смотрит на score или total, не видит сдвиг в компоненте и начинает менять всё подряд. Через несколько таких проверок бюджет превращается в табличку, которая ничего не запрещает.

Причина не в том, что aggregate бесполезен. Он отвечает на другой вопрос: не вышла ли общая учебная сумма за свой предел. Бюджет пути отвечает сначала на вопрос о каждой именованной части и только потом о сумме. Чтобы ответ был воспроизводимым, рядом нужны controlled conditions, baseline snapshot, candidate и явный tolerance. Ни одно из этих слов не означает реальный browser observation: fixture строит только локальные objects с условными ticks.

Четыре слоя одной проверки

Первый слой — measurement contract. Он связывает route, user scenario, version workload и условия. Второй — snapshot baseline, который описывает известный вход именно в этом contract. Третий — component checks: каждый field сравнивается со своим limit плюс tolerance. Четвёртый — decision. Overall FAIL появляется, если хотя бы один named component не проходит; aggregate остаётся вторичным guard. Это простое правило убирает самую частую лазейку: «total зелёный, значит можно не смотреть красную строку».

В fixture четыре поля называются documentTicks, styleTicks, scriptTicks и renderTicks. Они не названы LCP, TTFB или browser event, потому что не получены из браузера. Это project vocabulary учебной модели. Если настоящий проект берёт browser field, его официальное значение, supported browser и сбор нужно описать отдельно. Нельзя взять знакомое имя метрики и приклеить его к произвольному object только ради убедительного отчёта.

Слои budget comparison и границы вывода
СлойВходВыходПравилоЗапрещённый вывод
Contractroute, scenario, declared conditionscomparable / invalidлюбое несовпадение останавливает compare«числа сами по себе сопоставимы»
Snapshotbaseline timingFieldsточка отсчётахранит тот же schema«последний отчёт всегда baseline»
Componentcandidate field и limit+tolerancepass/fail по именикаждый field виден отдельно«общая сумма лечит частный fail»
Aggregateсумма fields и total ruleвторичный pass/failчитается после component«PASS total доказывает путь»
Diagnosisfailed named componentone bounded next actionне расширяет scope без факта«найдена глобальная причина скорости»

Допуск — часть контракта, а не скрытая скидка

Если limit равен 28, а tolerance равен 2, то allowed равен 30. Такой расчёт должен быть виден в output, иначе reviewer не может отличить осознанный допуск от опечатки или смены правила. В fixture candidate имеет scriptTicks: 34. Он превышает allowed на четыре tick. Отдельное поле deltaFromLimit равно шести, потому что описывает расстояние до базового limit, а не до порога с допуском. Оба числа нужны, если заранее объяснить их смысл.

Допуск нельзя переводить в процент без контекста. У одной части пути абсолютное изменение может быть осмысленным, у другой — нет. Настоящий способ выбрать tolerance зависит от источника наблюдения, повторов, среды и стоимости ложного FAIL. Учебная fixture не знает ни одной из этих вещей. Поэтому она не предлагает «правильные 2 ticks», а требует явного поля. Реальная команда добавляет обоснование рядом со своим mapping и не переносит учебные значения в конфигурацию молча.

Почему линия aggregate может оставаться зелёной

Baseline fixture суммируется до 85 ticks. Candidate суммируется до 93. Aggregate allowed равен 98, поэтому aggregate status — PASS. Однако рост находится в scriptTicks: baseline 26, candidate 34, allowed 30. Эта комбинация сделана специально. Если result позволил бы overall PASS, потому что 93 меньше 98, budget не защищал бы контракт пользователя: он прятал бы изменение в самой части, для которой владелец и limit были заведены.

Сравнительная диаграмма baseline и candidate учебного маршрута: total растёт с 85 до 93 и остаётся ниже aggregate allowed 98, но scriptTicks растёт с 26 до 34 и превышает allowed 30. Итоговый verdict показан как fail по named component, а не как глобальная оценка скорости.
Линия aggregate нужна для контекста, а не для отмены component failure. На схеме значения подписаны как teaching ticks и отделены от браузерных метрик.

Механизм сравнения в одном объекте

import { compareRouteWorkload, createTeachingBudget } from "./upgrade-2022-02.mjs";

const budget = createTeachingBudget();
const baseline = {
  route: "/training/checkout/review",
  scenario: "anonymous-cart-with-one-item",
  conditions: budget.conditions,
  timingFields: { documentTicks: 24, styleTicks: 15, scriptTicks: 26, renderTicks: 20 },
};
const candidate = {
  ...baseline,
  timingFields: { ...baseline.timingFields, scriptTicks: 34 },
};

const comparison = compareRouteWorkload(baseline, candidate, budget);
console.log(comparison.componentChecks.scriptTicks.allowed);
// 30: limit 28 plus explicit tolerance 2
console.log(comparison.diagnosis.scope);
// scriptTicks only

Функция compareRouteWorkload сначала вызывает validation для baseline и candidate. Она проверяет, что все четыре поля существуют, лишних нет, значения неотрицательные, route и scenario совпадают с contract, а conditions равны. Только после этого она строит component checks. Если conditions отличаются, component map пуст, aggregate становится not-comparable, а overall получает measurement-contract-invalid. Это предотвращает тихое смешение «до» и «после».

const changedConditions = {
  ...candidate,
  conditions: { ...candidate.conditions, cache: "changed-training-cache-state" },
};
const unavailable = compareRouteWorkload(baseline, changedConditions, budget);

console.log(unavailable.overall.reason);
// measurement-contract-invalid

Этот fragment не пытается «нормализовать» изменённый cache. Он оставляет compare недоступным, пока не восстановлен один declared contract. Так разница условий не превращается в число, которому дают performance-смысл.

Сначала observation, потом архитектурный вывод

M5-подход начинается с пользовательского сценария и заканчивается небольшим изменением, а не с тезиса «пакет тяжёлый». Candidate failure говорит только: в declared teaching model field scriptTicks больше allowed. Из этого нельзя вывести, что виновата конкретная библиотека, загрузчик, server response или пользовательское устройство. Чтобы назвать причину, нужно собрать следующий наблюдаемый артефакт на одной границе: dependency list, bundle diff, route trace или профиль — в зависимости от того, что реально доступно.

Маршрут: симптом → причина → проверка → действие

  1. Симптом. В отчёте есть общий PASS, но изменения одной части пути продолжают накапливаться без понятного владельца.
  2. Причина. Aggregate использовали как final verdict, хотя он не хранит распределение и может пройти при failed component.
  3. Проверка contract. Сверьте route, scenario и все declared conditions baseline/candidate. Изменился хотя бы один обязательный элемент — верните not-comparable.
  4. Проверка allocation. Для каждого component покажите actual, limit, tolerance, allowed и owner. Отдельно посчитайте total, но не смешивайте его с verdict component.
  5. Проверка decision. Overall PASS допустим только когда все names PASS. Overall FAIL обязан перечислить failed component, даже если aggregate остаётся зелёным.
  6. Действие. Возьмите первую failed name и сузьте следующую проверку до одного input и одной route boundary. Зафиксируйте, какой дополнительный факт нужен до архитектурного изменения.

Откуда взять настоящие поля и где не подменять их

Navigation Timing Level 2 уже к январю 2022 года описывал navigation timing information и PerformanceNavigationTiming. User Timing Level 3 описывал named marks/measures и их metadata. Performance Timeline Level 2 описывал получение entries. Эти документы полезны, когда проект выбирает platform source. Они не предлагают готовый budget для checkout, не говорят, что total должен быть 95, и не определяют связь наших четырех ticks с полями API.

Поэтому корректная интеграция начинается с mapping-table. В ней для каждого production field указывают источник, version, значение, условия доступности, преобразование и owner. Затем отдельно документируют runner и repeatability. Пока этого нет, честнее оставить language учебным: teaching-ticks, unavailable-in-example, outside-example. Это не уклонение от работы; это защита от визуально похожих, но невалидных цифр.

Граница CI и browser observation

CI может исполнить compare и упасть по возвратному коду. Но факт исполнения job не создаёт browser observation. Browser observation может быть полезным входом в budget, если его получали по определённому протоколу и сохранили вместе с conditions. У fixture оба значения прямо равны unavailable-in-example. Так output не позволяет сказать «CI замерил маршрут» или «Chrome подтвердил результат».

Критерий корректной эволюции бюджета

Budget change корректен, если меняется одна известная вещь: новый route scenario, новый component, limit/tolerance или способ получения поля. В каждом случае обновляется contract и baseline, а причина изменения остаётся рядом. Неправильно просто повысить общий total, когда scriptTicks красный: это снимает симптом, но не говорит, согласен ли владелец пути на более дорогую часть. Иногда повышение оправдано, но оно должно быть отдельным решением со стоимостью и ограничением.

В fixture есть пятнадцать assertions: baseline проходит каждый named budget; candidate падает по scriptTicks; aggregate PASS не скрывает failure; tolerances явны; conditions несовместимы блокируют compare; diagnosis ограничена одним component; никаких browser, CI, trace или network observations не выполняется. Это техническая проверка нашей модели. Она не измеряет реальную страницу и не должна использоваться как её заменитель.

Историческая граница февраля 2022

Все ссылки ниже существовали до февраля 2022 года: Navigation Timing draft от 17 января 2022 года, User Timing draft от сентября 2021-го и Performance Timeline draft от августа 2021-го. Они процитированы как historical sources и сами обозначены W3C как working drafts. В статье нет поздних interaction metrics и нет современных рекомендаций, появившихся после этой рамки. Технический вывод относится только к структуре учебного comparison.

Проверяемые источники