Files
obsidian-vault/work/projects/cpm-status-2026-10-15.md
T

24 KiB
Executable File
Raw Blame History

title, date, tags
title date tags
CPM Web Extension — единый статус (PR, данные, гипотезы, телеметрия) 2026-10-15
work
cpm
status

CPM Web Extension: единый статус на 15 октября 2026

Внутренний документ. Цифры из ClickHouse, в публичные PR, комментарии и issues не переносить (репозитории duckduckgo/apple-browsers и duckduckgo/duckduckgo-privacy-extension публичные). Заменяет заметку cpm-telemetry-data-requirements-2026-10-15 (теперь ссылка сюда). Старые документы с устаревшими числами: cpm-web-extension-public-attribution-2026-10-05, cpm-web-extension-data-analysis-summary-2026-10-08, cpm-web-extension-repro-and-fix-2026-10-08, cpm-web-extension-code-guide-2026-10-09 (карта «большого» worktree, больше не актуальна: всё нарезано на PR ниже).

1. Коротко

  • Фикс B (падение Network-процесса) проверен локально и красным прогоном на CI. Основание в данных есть, но неполное: обогащение в stuck есть, а доказать, что строки это именно мёртвый worker, нельзя (не хватает network_replaced_since_view).
  • Фикс A (первая загрузка посреди сессии) реален, но мелкий: около 1% stuck на iOS, на macOS почти не виден, чаще самоизлечивается.
  • Три самые крупные группы сбоев фиксами A и B не закрыты: пробуждение после idle, error 6 (iOS), «нет background view» на запуске (iOS). Механизмы воспроизведены локально на macOS, но проверенного рычага в приложении нет.
  • Телеметрия PR0 пока в виде, вызвавшем вопросы о склейке пикселей. Решение по правкам не принято (раздел 8).

2. Цепочки PR (все в duckduckgo/apple-browsers, задачи Asana в телах PR; не читать)

Телеметрия:

PR Ветка База Что Статус
#7376 (PR0) alex/cpm-messaging-readiness-telemetry main параметры готовности и тайминга на failure/recovery пикселях (без новых пикселей) CI зелёный, нужен ревью. Ревьюер по пикселям задал вопросы JA1–JA3 о склейке, ответ не отправлен
#7384 (0b) alex/cpm-readiness-baseline #7376 ежедневные базлайн-пиксели cpm_background_ready_{context_load,wake}, нагрузка устройства, возраст возобновления CI в основном зелёный
#7385 (0c) alex/cpm-init-progress-native #7384 init_stage на failure-пикселях (нужен JS из privacy-extension#3727) CI в основном зелёный
privacy-extension #3727 alex/cpm-init-progress уведомления cpmInitProgress из встроенного расширения открыт, нужен ревью

Harness и фиксы (красный коммит, затем зелёный; не ребейзить и не форс-пушить, иначе пропадёт доказательство красного прогона):

PR Ветка База Что Статус
#7378 alex/cpm-repro-harness main harness (только тесты); 4 быстрых теста в CI около 3 с, остальное локально через CPM_REPRO=1 CI зелёный, нужен ревью. Danger: 1570 добавленных строк (предупреждение). В ветке есть коммит другой сессии «Require initResp in the page…»
#7379 alex/cpm-fix-init-loss harness фикс A: ожидание первой загрузки background после установки/апгрейда посреди сессии красный прогон и зелёный: CI зелёный
#7381 alex/cpm-fix-network-crash фикс A фикс B: перезапуск background при замене Network-процесса и мёртвом worker'е красный прогон подтверждён (падает testNetworkProcessCrashKeepsEveryTabWorking, 3 ретрая, Non-Sandbox). Фикс-коммит f1085846e7 локально, не запушен
(нет PR) alex/cpm-fix-pixels фикс B + PR0 пиксели background_restart и first_load_wait для измерения эффекта держим до слияния PR0, A, B

Локальные проверки фикса A: на коде без фикса оба repro-теста красные (5 из 5 итераций), с фиксом 0 из 10 потерь, 761 тест пакета зелёный. Фикс B: на main красные тесты падения Network-процесса и смерти worker'а, 5 из 5 итераций.

Недоделано в harness и CI: CI гоняет тесты с -test-iterations 3 -retry-tests-on-failure, красный тест должен падать стабильно; тесты WebExtensions идут внутри схем macOS Browser и iOS Browser на osx-tahoe-27. Медленные эксперименты (заморозка 45 с, заполнение кэша около 10 минут, медленный старт worker'а) локальные и не для коммита.

3. Данные (ClickHouse, 12–14 октября, версии iOS 7.240+ и macOS 1.210+)

Единица: daily-пиксель = устройство-день на причину, не пользователь и не эпизод. initialization_failed только .daily; stuck, recovered_*, reload_failed: daily+count. Параметры снимка есть только на init_failed и stuck.

iOS macOS
initialization_failed.daily 3 273 025 119 950
stuck.daily 511 660 40 842
init восстановились без reload 1 135 477 (34.7%) 30 738 (25.6%)
stuck → восстановились после M4 reload 293 557 (57.4%) 21 865 (53.5%)
reload_failed.daily 140 921 (27.5% от stuck) 17 796 (43.6%)

Когорты (по приоритету: error 6 → нет view → вкладка не подключена → Network заменён → пробуждение (view <30 с после dealloc) → недавняя загрузка (load/base_url_changed ≤2 мин) → other). Доля init → stuck:

Когорта iOS macOS Смысл роста или падения
error 6 6.1% → 21.5% 1.0% → 1.9% остаётся сломанной
нет background view 48.1% → 10.9% 3.3% → 1.8% самоизлечивается
вкладка не подключена 0.03% → 0.05% 6.2% → 8.9% остаётся сломанной
Network заменён 16.8% → 39.7% 13.6% → 26.6% остаётся сломанной
пробуждение 7.9% → 9.5% 32.5% → 32.2% без изменений
недавняя загрузка контекста 15.7% → 6.0% 20.0% → 6.4% самоизлечивается
other 5.3% → 12.5% 23.4% → 22.2%

Объёмы stuck.count в разных версиях на порядки выше daily: дашборды сравнивать только по версии и частоте.

4. Основание фиксов

  • B: доля «Network заменён» растёт в 2–2.4× при переходе init → stuck. reload_failed высок: reload расширения мёртвый worker не лечит. Оговорка: network_restarted сравнивает PID с загрузкой контекста, а не с последним view. Если view пересоздан после смены процесса, worker свежий и механизм неприменим. По заметке 8 октября механизм объясняет в лучшем случае 20–45% «здоровых» stuck. Причинность подтвердят network_replaced_since_view (нет ни в одной ветке) и init_stage=none (#7385).
  • A: сигнатура «загрузка контекста ≤2 мин, не при запуске, первый view»: iOS 129k init (3.9%) → 4.6k stuck (0.9%); macOS 505 → 118. Разовая потеря одной навигации (0.23× при переходе в stuck). Сигнатура неточна (захватывает M4-reload). Название связанной Asana-задачи («init loss on background unload») относится скорее к группе пробуждения, чем к фиксу A; несоответствие надо снять с автором задачи.

5. Картины событий (stuck.daily, 14 октября, хвост bg_events без возрастов) и воспроизведение

Картина Хвост iOS / macOS Воспроизведено? Рычаг в приложении
P1. Цикл пробуждения view>proxy_on>dealloc>view>proxy_on 16.0k / 4.3k (wake) + 16.3k / 2.4k (старше 30 с) только с искусственно занятым worker'ом (5 с: init_failed и поздний ответ; 12 с: stuck → M4); органическое пробуждение в тестах проходит. WebKit-баг 324743 (исправлен 22 сентября, нет в 26.7/27.0) гипотезы: отсчёт 4 с от готовности background («фаза 2 детектора»), ожидание готовности при пробуждении, предпрогрев
P2. «Пробуждение → error 6», циклы proxy_on>dealloc>view>proxy_on>error_E:6 (23.6k), error>dealloc>view>proxy_on>error (5.7k), после died_crash (1.5k) iOS; macOS 118 macOS: заморозка нового процесса на ≥30 с даёт error 6, корзина 25–35 с. iPhone: 4 сценария безуспешно. Главный iOS-хвост без unresponsive (в воспроизведении он есть) корень в WebKit (wakeUpBackgroundContentIfNecessary взводит 30-секундный таймер до загрузки worker'а). Гипотеза: перезагрузка расширения при возврате на передний план
P3. Контекст загружен, view нет load / load>base_url_changed 17.1k + 0.9k / 162 + 81 задержка очистки при смене baseURL воспроизведена на macOS при большом кэше (около 10 минут заполнения); iOS-гонка запуска (bg_view_creations=0, восстановление сессии, первые 30 с) не воспроизведена: холодный старт в harness'е отвечает за миллисекунды кандидат: стабильный baseURL (ветка cpm-stable-base-url: не доведён, меняет URL всем расширениям, не прогнан через harness, старая база). Фикс WebKit 318756@main, номер сборки ОС не проверен
P4. Первая загрузка load>view>proxy_on 6.8k + 2.3k / 570 да (фикс A) фикс A
P5. Фон выгружен в момент сбоя proxy_on>dealloc>view>proxy_on>dealloc 1.9k / 494 нет нет гипотезы
P6. macOS: вкладка без расширения load>view>proxy_on>dealloc, с extension_updated iOS ≈0 / 627 + 235 нет сценария нет; фиксы привязку вкладки не трогают
Network заменён см. раздел 4 да (краш, worker мёртв при том же view) фикс B
«Здоровый» остаток, other iOS 12.5%, macOS 22% нет нет гипотезы

Размер группы не равен возможности её починить: P1 и P3 в основном опаздывающие ответы, а часть «сбоев» это срабатывание детектора («нет ответа за 4 с», опоздавший ответ тоже считается). iOS-когорта «other» на самом деле тот же рисунок P1, но со view старше 30 секунд.

6. Телеметрия

Что делает PR0 (#7376): на 8 failure-пикселей и 2 recovery-пикселя добавлены параметры bg_ready_after, bg_view_age, bg_load_error_after, bg_load_error_app_state, cpm_app_state, cpm_open_tabs, cpm_loading_tabs (failure) и cpm_recovery_document, cpm_recovery_after (recovery). Новых пикселей нет. Recovery-пиксели получают только свои два параметра, extension_reload_failed без параметров.

Замечания ревьюера (JA1–JA3): корреляция между пикселями из-за множества перечислений; сколько пикселей на ошибку; константность cpm_open_tabs/cpm_app_state; что такое background view.

Что верно по пикселям (исправление моей прежней ошибки): initialization_failed только daily (не больше одного на устройство в день на причину); stuck daily+count (daily и count одного события идут с одинаковыми параметрами); snapshot пересобирается при каждой отправке, поэтому быстро меняющиеся поля (bg_view_age, bg_ready_after) между init_failed и stuck различаются, медленные (app_state, open_tabs) повторяются.

Уникальность строк stuck.daily внутри дня (доля уникальных по всем параметрам):

Вариант iOS macOS
сейчас, bg_events из 12 событий 8.3% 30%
информативные токены, последние 4 2.1% 5.2%
скаляры вместо bg_events 0.96% 4.4%
без bg_events 0.25% 2.0%

Верхняя граница прироста от новых полей (синтетическое независимое равновероятное поле): со старым bg_events при десяти значениях iOS 21%, macOS 65%; со скалярами 3.6% / 15.8%. Вывод: «хуже не будет» можно утверждать только со скалярами. Главный идентификатор уже выкачен (bg_events).

bg_events: единственный источник давности загрузки (load, base_url_changed) и причины смерти (died_*). view, dealloc, proxy_on, error_* дублируют bg_view_creations и context_errors; hold/release/proxy_off равны нулю. Скаляры воспроизводят когорты загрузки и пробуждения на 100% строк. Теряется: поиск неожиданных последовательностей в остатке (смягчить удалённым флагом).

Минимум данных по гипотезам:

Поле / пиксель Вопрос Где
bg_ready_after, bg_view_age идёт ли загрузка, пробуждение или давно PR0
bg_load_error_after, bg_load_error_app_state (только при error 6) таймер 30 с, состояние приложения PR0
recovery_document, recovery_after опоздавший ответ или потерянный PR0
app_state, open_tabs (грубо, 1 / 2–5 / >5), только на initialization_failed когорта «нет view» на запуске PR0
новое: context_load_cause, context_load_age (вместо load@ и extension_updated) точная сигнатура A PR0
новое: bg_died_reason, base_url_changed смерть процесса, очистка при смене baseURL PR0
новое: мелкие корзины app_launch_age для первых 30 с (<4 / 4–10 / 10–30) гонка запуска PR0
новое на stuck: stuck_after сколько от первого сбоя до stuck PR0
новое, срочно: network_replaced_since_view доля реальных мёртвых worker'ов в когорте B (рекордер вычисляет, но только логирует; предложено 8 октября, нигде не реализовано) PR0
init_stage дошёл ли init до background #7385 (срочно)
cpm_background_ready_{context_load,wake} базлайн здоровых загрузок и пробуждений #7384
background_restart (причина, исход; убрать из него bg_ready_after, он дублирует соседний failure-пиксель), first_load_wait эффект B и A удержанная ветка
убрать loading_tabs; из таймлайна view, dealloc, proxy_on, error_*, hold/release/proxy_off

Связность стека: #7384 добавляет ещё 5 измерений на те же 8 failure-пикселей (context_load_to_background_view, cpm_low_power_mode, cpm_thermal_state, cpm_lockdown_mode, cpm_resume_age); оценку склейки надо повторять и по нему. background_restart срабатывает вместе с первым initialization_failed: общий ключ bg_ready_after.

7. Критерии успеха и опровержения

  • B подтверждается, если после релиза падают доля «Network заменён» среди stuck и reload_failed, background_restart показывает восстановление, а у таких stuck init_stage=none.
  • A подтверждается, если сокращается когорта «мид-сессионная первая загрузка» (context_load_cause). Если нет, основание слабое.
  • Остаток P1/P3: гипотеза «медленный старт, а не потеря» подтверждается, если bg_ready_after у упавших заметно хуже базлайна здоровых загрузок и recovery_document=failed_navigation.

8. Открытые решения и следующие шаги

  1. Правки PR0 (ждут вашего «да»): скаляры вместо bg_events, context_load_cause, stuck_after, network_replaced_since_view, мелкие корзины app_launch_age, убрать loading_tabs, константы только на initialization_failed, open_tabs грубо, bg_load_error_* только при error 6; после этого ребейз #7384 и #7385. Альтернатива «оставить bg_events» требует ответа ревьюеру без утверждения «хуже не будет» и обязательства перемерить после релиза.
  2. Ответ ревьюеру (публично, без цифр): JA1 (init_failed daily-only; stuck daily+count с одинаковыми параметрами; recovered несут только recovery_*), JA2 (медленные поля повторяются, быстрые различаются; планируется сократить набор), JA3 (background view = скрытый WKWebView, в котором WebKit запускает фоновый скрипт расширения; выгружается через около 30 с простоя и пересоздаётся при пробуждении). Обязательство: перемерить различимость строк после первого релиза.
  3. Запушить фикс B (f1085846e7): красный прогон подтверждён, ждёт зелёного.
  4. Судьба фикса A: слабое основание в данных, не совпадает с названием задачи; решить, сливать ли без зелёного измерения эффекта через first_load_wait.
  5. P3: довести стабильный baseURL (ограничить встроенными расширениями без web_accessible_resources, прогнать harness, перенести на свежий main), проверить номер сборки ОС с фиксом 318756@main.
  6. Локальные repro для P1–P3 отдельным PR без фиксов (по образцу harness'а, requireLocalRun), чтобы гонять после релизов WebKit.
  7. iOS: воспроизведение error 6 на реальном устройстве; сценарий «вкладка без расширения» на macOS.
  8. Блокеры по ревью: #7376 и #7378 ждут ревью; #7384/#7385 стоят на #7376.

9. Методика и инструменты

  • Доступ: ~/Developer/grafana-data-tools (Grafana → ClickHouse metrics.pixels, read-only; авторизация через grafana-auth). Ad-hoc SQL запускался отдельным раннером через ~/DuckDuckGo/grafana-dashboards/tools/grafana-query/read-dashboard.py. Сканы по pixel LIKE без фильтра по дате, агенту и pixel_id='debug' превышают лимит строк. Временные запросы лежали в папке сессии и могут пропасть; ключевая классификация ниже.
  • Версии с диагностикой: iOS 7.240+, macOS 1.210+. Окно: 12–14 октября (3 полных дня). Ограничения: сигнатуры эвристические; network_restarted только прокси; устройства нельзя проследить от init до stuck, сравниваются только доли; доля не равна пользовательскому эффекту.
-- классификация когорт для init_failed.daily / stuck.daily
multiIf(
  errs LIKE '%ContextErrorDomain:6%', '1_error6',
  creations = '0', '2_no_background_view',
  scripts = 'false', '3_tab_not_connected',
  net = 'true', '4_network_replaced',
  has(ev,'view@30s') AND arrayExists(x -> startsWith(x,'dealloc@'), ev), '5_wake_view_lt30s',
  arrayExists(x -> x IN ('load@30s','load@1m','load@2m','base_url_changed@30s','base_url_changed@1m','base_url_changed@2m'), ev), '6_context_load_recent',
  '7_other')
-- параметры: network_restarted, context_errors, bg_view_creations, tab_has_ext_scripts, bg_events (decodeURLComponent)