9.0 KiB
title, date, tags
| title | date | tags | ||||
|---|---|---|---|---|---|---|
| CPM episode telemetry — фаза 2: паузы по фону/сну и смысл was_stuck (план анализа) | 2026-10-20 |
|
CPM episode telemetry — фаза 2
Статус: план, не реализовано. Фаза 1 — PR #7376 (web_extension_cpm_episode, was_stuck = любой поздний не-restoration таймаут). Состав отчёта фазы 1: cpm-pr0-episode-telemetry-summary-2026-10-16. Общий контекст: cpm-status-2026-10-15.
Работа ведётся отдельной веткой поверх #7376 (alex/cpm-episode-background-pause, стекнутый PR). Obsidian MCP не подключался, файл записан напрямую.
Зачем
Проверка кода 20 октября 2026 показала два смещения:
- Таймеры считают wall clock.
nowпо умолчаниюDate.init;discardExpiredState()вызывается в началеhandle(),reportFailureиreportSuccess. Если процесс спал или был в фоне внутри 5-минутного окна, первое событие после пробуждения закрывает эпизод какno_response_before_deadline, а пришедший сразу ответ теряется. ДляTask.sleep(nanoseconds:)считает ли он сон — не проверено. - 4-секундный замер тоже. Фон/сон внутри 4 с даёт ложный первый таймаут, потому что веб-процесс ещё просыпается.
Следствие: завышена доля no_response_before_deadline, занижены доли исходов с ответом. Число эпизодов, _daily/_count и диагностический снимок не страдают.
Решения (согласованы 20 октября 2026)
Таймауты
- Первый замер: 4 с без изменений. Ожидание загрузки расширения: 30 с без изменений.
- События паузы: iOS
didEnterBackground/willEnterForeground; macOSNSWorkspace.willSleep/didWake.resignActiveне паузит: процесс жив. - На паузе замеры, ждавшие ответа, замораживаются. На пробуждении они перезапускаются полным окном 8 с (отдельная константа, стартовое значение; общий 4 с не трогаем, чтобы не сдвигать базу сравнения).
- Дедлайн эпизода (5 мин) на паузе замораживается и продолжается с остатком, не начинается заново.
response_afterпосле этого — время наблюдения, не wall clock. - Замеры, уже получившие ответ, не трогаем.
was_stuck
- Переопределяем смысл (тип boolean не меняется):
was_stuck=true— в рамках эпизода таймаут минимум в двух разных вкладках (вторая не restoration). Повторы в одной вкладке остаютсяwas_stuck=false. - Триггер автоперезагрузки
onConfirmedHangне меняем: любой поздний не-restoration таймаут, как сейчас. Отчётность и логика развязаны. Иначе у пользователей с одной активной вкладкой (iOS) перезагрузка не наступила бы никогда. - Новых параметров нет (счётчик повторов и число вкладок потребовали бы нового privacy-триажа). Цена: счётчик повторов в одной вкладке недоступен.
- Частичная замена счётчику:
was_stuck=false+reload_stateнеnone≈ «в одной вкладке сбой повторился, перезагрузили». Не точно:reload_stateменяется от любых событий перезагрузки расширения, не только от нашего подтверждения. - Требуется обновить описание в json5 (iOS и macOS) и сообщить владельцу privacy-ревью; нужен ли повторный триаж — решает процесс.
Как читать данные (после изменения)
Поля: reason, was_stuck, outcome, reload_state, response_after, плюс снимок первого сбоя (bg_*, 8 булевых, bg_events).
| Конфигурация | was_stuck |
outcome |
reload_state |
Смысл |
|---|---|---|---|---|
| One tab, timeout, then response on the same measurement | false | failed_measurement_response | none | Slow, not broken (warm-up / slow response). response_after gives the delay bucket. |
| One tab, timeout, later navigation in that tab answers | false | failed_tab_response | none | Transient; recovered without reload. |
| One tab, timeout repeated on 2+ navigations (reload fired) | false | depends (see below) | pending / loaded / failed / response_timed_out | Single-tab confirmed hang; automatic reload applied. |
| ... then a post-reload measurement answers | false | failed_tab_response (or other_tab_response) | loaded | Reload fixed it. |
| ... then post-reload measurement times out again | false | any | response_timed_out | Reload did not help in this tab. |
| ... then nothing observed | false | no_response_before_deadline | loaded / pending / failed | Reload done, no evidence either way. |
| Timeouts in 2+ distinct tabs (2nd is not restoration) | true | no_response_before_deadline | loaded or response_timed_out | Likely persistent, not proven (deadline is not proof). |
| Timeouts in 2+ distinct tabs, then any tab answers | true | other_tab_response / failed_tab_response | any | Extension-wide issue that recovered; other_tab_response does not prove the failed tabs recovered. |
| Restoration-batch timeouts only | false | usually other_tab_response / failed_tab_response | none | Startup race; restoration failures cannot confirm each other. reason=session_restoration. |
| First post-reload measurement fails | any | any | response_timed_out | reason=extension_reload: reload itself did not restore messaging. |
| Idle after first timeout, no further navigation | false | no_response_before_deadline | none | Undetermined. Before the pause fix this also includes sleep/background (biased). |
| Response matched several documents | any | unattributed_response | none or loaded | Extension answers, tab not attributable. After a reload this outcome cannot close the episode. |
Outcome no_response_before_deadline is never proof of permanent failure. reload_state is none unless an extension reload lifecycle event occurred during the episode.
Фаза 2 анализа (после выкатки)
- Распределение
outcome×was_stuck×reload_state, по платформам. - Эффективность автоперезагрузки: среди
reload_state=loadedдоляfailed_tab_responseпротивresponse_timed_out. - Оценка смещения фона/сна: сравнить долю
no_response_before_deadlineв сборках до и после паузы (фаза 1 vs фаза 2). Разница — размер смещения. Нужны обе когорты, поэтому фазу 1 нужно выкатить первой. - Прогрев против поломки по снимку: сильные признаки —
bg_process_alive=false,bg_process_responsive=false, error 6 (bg_load_error_*),bg_ready_after=failed; прогрев —bg_ready_after=pendingпри маломbg_view_age. was_stuck=trueдолю смотреть отдельно по iOS и macOS: на iOS мало одновременных вкладок, значение будет другим.
Ограничения и риски
- Если iOS убьёт приложение в фоне, незавершённый эпизод теряется (принято в фазе 1).
- Пропущенное событие паузы ломает учёт; начальное состояние (запуск в фоне) брать из системы, не считать «активным».
was_stuckменяет смысл относительно описания в PR #7376 и в любых данных до выкатки; данные до и после изменения не склеивать по этому полю.- Сохранённые, но не используемые в этой фазе правки (
stuck= true/false × one/many, продолжениеbg_eventsчерез;) лежат на локальной веткеalex/cpm-7376-extras-backup.