24 KiB
Executable File
title, date, tags
| title | date | tags | |||
|---|---|---|---|---|---|
| CPM Web Extension — единый статус (PR, данные, гипотезы, телеметрия) | 2026-10-15 |
|
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показывает восстановление, а у таких stuckinit_stage=none. - A подтверждается, если сокращается когорта «мид-сессионная первая загрузка» (
context_load_cause). Если нет, основание слабое. - Остаток P1/P3: гипотеза «медленный старт, а не потеря» подтверждается, если
bg_ready_afterу упавших заметно хуже базлайна здоровых загрузок иrecovery_document=failed_navigation.
8. Открытые решения и следующие шаги
- Правки 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» требует ответа ревьюеру без утверждения «хуже не будет» и обязательства перемерить после релиза. - Ответ ревьюеру (публично, без цифр): JA1 (init_failed daily-only; stuck daily+count с одинаковыми параметрами; recovered несут только
recovery_*), JA2 (медленные поля повторяются, быстрые различаются; планируется сократить набор), JA3 (background view = скрытыйWKWebView, в котором WebKit запускает фоновый скрипт расширения; выгружается через около 30 с простоя и пересоздаётся при пробуждении). Обязательство: перемерить различимость строк после первого релиза. - Запушить фикс B (
f1085846e7): красный прогон подтверждён, ждёт зелёного. - Судьба фикса A: слабое основание в данных, не совпадает с названием задачи; решить, сливать ли без зелёного измерения эффекта через
first_load_wait. - P3: довести стабильный
baseURL(ограничить встроенными расширениями безweb_accessible_resources, прогнать harness, перенести на свежийmain), проверить номер сборки ОС с фиксом 318756@main. - Локальные repro для P1–P3 отдельным PR без фиксов (по образцу harness'а,
requireLocalRun), чтобы гонять после релизов WebKit. - iOS: воспроизведение error 6 на реальном устройстве; сценарий «вкладка без расширения» на macOS.
- Блокеры по ревью: #7376 и #7378 ждут ревью; #7384/#7385 стоят на #7376.
9. Методика и инструменты
- Доступ:
~/Developer/grafana-data-tools(Grafana → ClickHousemetrics.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)