diff --git a/work/projects/cpm-web-extension-breakage-validation.md b/work/projects/cpm-web-extension-breakage-validation.md index e1dd2fe2..7c7beab2 100755 --- a/work/projects/cpm-web-extension-breakage-validation.md +++ b/work/projects/cpm-web-extension-breakage-validation.md @@ -24,6 +24,92 @@ **Следующая проверка:** на public macOS 1.210 / iOS 7.240 с 2026-10-06 раздельно считать affected daily users (recovered и permanent) против DAU той же версии; разбирать ordered `bg_events` и tab-поля по каждой сигнатуре. Воспроизвести на macOS 1.210 update/session restore и off→on Web Extensions с уже открытой вкладкой: сравнить старую и новую вкладки по controller, scripts и CPM outcome. До этого не ранжировать причины по internal `.count`. +## Как получить и проверить attribution data + +**Источник:** Grafana ClickHouse datasource `dekdbd6bqq1hcd` (`grafana-clickhouse-datasource`), таблица `metrics.pixels`. Использовался API через `/Users/admin/DuckDuckGo/grafana-dashboards/tools/grafana-query/read-dashboard.py`, **не** браузер и не локальный Apple worktree. Авторизация этого helper — `grafana-auth`/SSO либо `GS_GRAFANA_API_KEY`; токены в документ не записывать. [Board](https://grafana.duckduckgo.com/d/dfon7whlilnggb/cpm-weird-error-investigation?orgId=1) — визуальная сверка, SQL ниже — источник приведённых чисел. + +1. Сохранить нужный SQL-блок ниже в `/tmp/cpm-query.sql`. Для исторической проверки оставить UTC-окно `[2026-09-28, 2026-10-05)`; для public заменить начало на фактический день публикации и указать **только завершённые UTC-дни**. Сверять `date`, `agent`, `appVersion`, суффикс `.count`/`.daily` и наличие `context_loaded` с Kibana. +2. Из `/Users/admin/DuckDuckGo/grafana-dashboards` выполнить этот read-only API-запрос. Заменить `FROM`/`TO` вместе с датами SQL; пустой Grafana frame без ошибки перепроверить повторным запросом, а не трактовать как ноль: + +```bash +uv run --with requests python3 - /tmp/cpm-query.sql 2026-09-28T00:00:00Z 2026-10-05T00:00:00Z <<'PY' +import importlib.util +import sys +from pathlib import Path + +path = Path('tools/grafana-query/read-dashboard.py').resolve() +spec = importlib.util.spec_from_file_location('grafana_reader', path) +reader = importlib.util.module_from_spec(spec) +spec.loader.exec_module(reader) +target = {'refId': 'A', 'rawSql': Path(sys.argv[1]).read_text(), + 'editorType': 'sql', 'queryType': 'table', 'format': 1} +raw = reader.query_ds_query('dekdbd6bqq1hcd', 'grafana-clickhouse-datasource', + [target], reader.parse_time(sys.argv[2]), reader.parse_time(sys.argv[3])) +result = raw.get('results', {}).get('A', {}) +if result.get('error'): + raise SystemExit(result['error']) +if not result.get('frames'): + raise SystemExit('No data frame returned; retry/inspect Grafana response') +for frame in result['frames']: + print('\t'.join(f['name'] for f in frame['schema']['fields'])) + for row in zip(*frame['data']['values']): + print('\t'.join(map(str, row))) +PY +``` + +**SQL 1 — покрытие по всем версиям, включая пиксели без attribution.** Не фильтровать версии заранее: так обнаруживаются 1.211/7.241 и большой legacy iOS-поток без новых полей. `attributed` — строки с live-параметром `context_loaded`; `rows` — все stuck-пиксели. `.count` и `.daily` не складывать. + +```sql +SELECT + toString(date) AS day, + agent, + arrayStringConcat(arraySlice(splitByChar('.', extractURLParameter(request, 'appVersion')), 1, 3), '.') AS app_version, + multiIf(match(pixel, '\\.daily(\\.ios\\.(phone|tablet))?$'), 'daily', + match(pixel, '\\.count(\\.ios\\.(phone|tablet))?$'), 'count', 'other') AS frequency, + count() AS rows, + countIf(extractURLParameter(request, 'context_loaded') != '') AS attributed, + countIf(extractURLParameter(request, 'bg_events') != '') AS chain_present +FROM metrics.pixels +WHERE date >= toDate('2026-09-28') AND date < toDate('2026-10-05') + AND agent IN ('ddg_ios', 'ddg_mac_desktop') + AND pixel_id = 'debug' + AND pixel LIKE 'debug.web.extension.cpm.messaging.stuck.%' +GROUP BY day, agent, app_version, frequency +ORDER BY day, agent, app_version, frequency; +``` + +**SQL 2 — macOS 1.210 tab-сигнатуры и предшествующие события.** Только attributed `.count` stuck; каждое число — репорты, не люди. `bg_events` упорядочены oldest→newest, но содержат лишь ограниченный хвост и грубые age buckets; `dealloc` не равен доказанному process death. Проверить эту же выборку с `.daily` для чувствительности к повторным репортам. + +```sql +WITH events AS ( + SELECT + extractURLParameter(request, 'tab_in_context') AS tab_in_context, + extractURLParameter(request, 'tab_controller_match') AS controller_match, + extractURLParameter(request, 'tab_has_ext_scripts') AS scripts_present, + extractURLParameter(request, 'bg_view_creations') AS view_creations, + extractURLParameter(request, 'app_version_change') AS version_change, + splitByChar(',', decodeURLComponent(extractURLParameter(request, 'bg_events'))) AS chain + FROM metrics.pixels + WHERE date >= toDate('2026-09-28') AND date < toDate('2026-10-05') + AND agent = 'ddg_mac_desktop' AND pixel_id = 'debug' + AND pixel LIKE 'debug.web.extension.cpm.messaging.stuck.%' + AND match(pixel, '\\.count$') + AND extractURLParameter(request, 'appVersion') LIKE '1.210.%' + AND extractURLParameter(request, 'context_loaded') != '' +) +SELECT + count() AS reports, + countIf(arrayCount(x -> startsWith(x, 'dealloc@'), chain) >= 2) AS repeated_dealloc, + countIf(scripts_present = 'false') AS scripts_absent, + countIf(scripts_present = 'false' AND controller_match = 'false' AND tab_in_context = 'true') AS controller_mismatch, + countIf(scripts_present = 'false' AND controller_match = 'false' AND version_change = 'updated') AS mismatch_after_update, + countIf(scripts_present = 'false' AND controller_match = 'true' AND view_creations = '0') AS same_controller_zero_views, + countIf(scripts_present = 'false' AND controller_match = 'true' AND view_creations = '0' AND version_change = 'updated') AS zero_views_after_update +FROM events; +``` + +**Методика интерпретации:** сначала SQL 1 проверяет покрытие версии/платформы и отсутствие attribution; затем SQL 2 группирует совместные признаки вместо чтения одиночного поля как root cause. Для конкретной сигнатуры дополнительно сгруппировать `bg_events` с сохранением порядка, reason из имени pixel, `app_version_change` и `bg_view_alive`; сверить с исходным кодом `CPMMessagingDiagnosticsRecorder` и созданием `WebExtensionManager` на `apple-browsers.git/main`. Internal forced-failure testing, app update и повторные `.count` могут смещать выборку. Public user-rate и recovered/permanent требуют отдельного user-level числителя и DAU **той же версии**; эти SQL их не вычисляют. + ## Исторический статус и PR-план (2026-09-23—09-25) > Ниже сохранён прежний план. Его формулировки о shipped status, готовности PR и дизайне graveyard A/B не являются текущим release/experiment contract; сверять с `apple-browsers.git/main` и актуальной privacy config.