Files
AWatch-rus/docs/OPERATIONS_RUNBOOK_WORKTIME_RU.md
T

20 KiB
Raw Blame History

Runbook восстановления worktime reports

Документ описывает безопасную диагностику и восстановление цепочки ActivityWatch -> aw-worktime-api -> portal executive reports.

ClickHouse не является обязательной зависимостью worktime reports. Не перезапускайте ClickHouse для восстановления отчетов рабочего времени, если нет отдельного подтвержденного отказа ClickHouse.

Stable host id

Worktime reports используют stable logical host id, а не обязательно текущее Windows COMPUTERNAME. Для DetMir production текущий logical id:

SHARKON2025

При переименовании RDP-сервера не меняйте awHostname автоматически. Сначала обновите Windows account domain для задач, затем проверьте, что collectors продолжают писать в bucket-и *_SHARKON2025. Подробный порядок: docs/WINDOWS_LOGICAL_HOST_ID_RU.md.

Симптомы перегруза

  • /portal/api/reports?role=executive открывается медленно или отвечает degraded/stale.
  • /portal/api/health показывает degraded-состояние worktime source.
  • /reports/worktime/management на aw-worktime-api возвращает status=DEGRADED.
  • В журнале aw-worktime-api растут aw_query_timeout_count или report_build_error_count.
  • ActivityWatch HTTP API отвечает медленно, не отвечает или держит SQLite под высокой нагрузкой.

Быстрая диагностика

Проверить состояние сервисов:

systemctl status activitywatch-server aw-worktime-api --no-pager
systemctl status aw-worktime-ui-bridge.timer aw-worktime-autoheal.timer aw-rus-healthd.timer --no-pager

Проверить последние журналы:

journalctl -u aw-worktime-api -n 80 --no-pager
journalctl -u activitywatch-server -n 80 --no-pager

Проверить health worktime API:

curl -sS --max-time 5 http://<AW_SERVER_HOST>:5610/health | jq

Проверить отчет в безопасном bounded-режиме:

curl -sS --max-time 8 \
  "http://<AW_SERVER_HOST>:5610/reports/worktime/management?format=json&host=SHARKON2025&allow_stale=1" \
  | jq '.status,.stale,.runtime'

Проверить portal health:

curl -sS --max-time 8 http://<PORTAL_HOST>/portal/api/health | jq
curl -sS --max-time 12 "http://<PORTAL_HOST>/portal/api/reports?role=executive" | jq '.status,.sources'

Проверка свежести bucket

Проверить metadata конкретного worktime bucket:

curl -sS --max-time 5 \
  http://<AW_SERVER_HOST>:5600/api/0/buckets/aw-worktime-sessions_SHARKON2025 \
  | jq '.metadata.end'

Проверить список bucket без чтения тяжелых событий:

curl -sS --max-time 5 http://<AW_SERVER_HOST>:5600/api/0/buckets | jq 'keys'

Если metadata свежая, а report degraded, вероятная причина - перегрузка чтения events или временная недоступность ActivityWatch API. Не запускайте повторные тяжелые запросы вручную без лимитов --max-time.

Дубли пользователей в Grafana/Influx

Симптом: панели Grafana показывают одного сотрудника несколькими строками, например USER5 и user5, Администратор и администратор, или показывают служебные/битые метки вроде SHARKON2025$ и строк с .

Причина: старые версии worktime exporter писали raw username/userId в tag user, а Grafana группировала Influx series по этому сырому tag. Поэтому варианты регистра, machine account и поврежденная OEM/Unicode строка становились разными series. После исправления exporter пишет canonical tags, но старые series остаются в диапазоне Grafana до истечения retention/range, поэтому Flux queries должны фильтровать и схлопывать их.

Текущая canonical policy для DetMir RDP host:

  • USER1/user1, USER4/user4, USER5/user5 -> user1, user4, user5;
  • администратор -> Администратор;
  • users с suffix $ исключаются;
  • users, содержащие Unicode replacement char , исключаются.

Кодовые точки, где должна сохраняться одинаковая нормализация:

  • adk-rust/crates/worktime-api/src/main.rs;
  • adk-rust/crates/worktime-influx-exporter/src/main.rs.

Проверка перед deploy:

cd <REPO_ROOT>/adk-rust
cargo fmt --all --check
cargo test -p worktime-api -p worktime-influx-exporter
cargo build --release -p worktime-api -p worktime-influx-exporter

Минимальный deploy с backup бинарников:

cd <REPO_ROOT>/ansible
export no_proxy="localhost,127.0.0.1,10.10.10.13,10.10.10.2,10.10.10.0/24"
export NO_PROXY="$no_proxy"

ts=$(date -u +%Y%m%dT%H%M%SZ)
ansible -i inventory.ini aw_server -m shell -a "set -e; sudo cp -a /usr/local/bin/aw-worktime-api-rust /usr/local/bin/aw-worktime-api-rust.bak.${ts}; sudo cp -a /usr/local/bin/aw-worktime-influx-exporter-rust /usr/local/bin/aw-worktime-influx-exporter-rust.bak.${ts}"
ansible -i inventory.ini aw_server -m copy -a "src=/home/igor/.cache/detmir-adk-rust-target/release/worktime-api dest=/tmp/aw-worktime-api-rust.new mode=0755"
ansible -i inventory.ini aw_server -m copy -a "src=/home/igor/.cache/detmir-adk-rust-target/release/worktime-influx-exporter dest=/tmp/aw-worktime-influx-exporter-rust.new mode=0755"
ansible -i inventory.ini aw_server -m shell -a 'set -e; sudo install -o root -g root -m 0755 /tmp/aw-worktime-api-rust.new /usr/local/bin/aw-worktime-api-rust; sudo install -o root -g root -m 0755 /tmp/aw-worktime-influx-exporter-rust.new /usr/local/bin/aw-worktime-influx-exporter-rust'
ansible -i inventory.ini aw_server -m shell -a 'set -e; sudo systemctl restart aw-worktime-api; sudo systemctl start aw-worktime-influx-exporter.service; systemctl is-active aw-worktime-api'

Grafana cleanup для старых Influx series:

  • dashboard JSON: grafana/detmir-rdp-user-activity-dashboard.json и grafana/detmir-aw-main-dashboard.json;
  • Flux должен фильтровать user_id !~ /\$$/ и user_id !~ //;
  • известные текущие accounts должны мапиться в canonical labels до grouping;
  • grouping должен быть по report_date,user или _time,user;
  • для схлопывания duplicate series использовать max(column: "_value"), чтобы не удваивать часы.

Если Grafana API import возвращает 403, используйте provisioning/DB fallback:

scp grafana/detmir-aw-main-dashboard.json grafana/detmir-rdp-user-activity-dashboard.json igor@10.10.10.2:~/codex-dashboard-import/
ssh igor@10.10.10.2 'sudo pct push 201 /home/igor/codex-dashboard-import/detmir-aw-main-dashboard.json /etc/grafana/provisioning/dashboards/aw/detmir-aw-main.json --perms 0644'
ssh igor@10.10.10.2 'sudo pct push 201 /home/igor/codex-dashboard-import/detmir-rdp-user-activity-dashboard.json /etc/grafana/provisioning/dashboards/aw/detmir-rdp-user-activity.json --perms 0644'
ssh igor@10.10.10.2 'sudo pct exec 201 -- bash -lc "cp -a /var/lib/grafana/grafana.db /var/lib/grafana/grafana.db.bak.$(date -u +%Y%m%dT%H%M%SZ); systemctl restart grafana-server"'

Если provisioning не перезаписал существующие DB dashboards, перед изменением сделать backup /var/lib/grafana/grafana.db, затем заменить только dashboard.data rows по uid detmir-aw-main и detmir-rdp-user-activity.

Проверка после deploy:

  • live panel Вчера: активность по сотрудникам возвращает только labels user1, user4, user5, Администратор;
  • bad labels list пуст для USER*, SHARKON2025$, администратор, и labels, начинающихся с \;
  • Grafana dashboard открывается с HTTP 200, HTML title содержит Grafana;
  • aw-worktime-api, grafana-server и aw-worktime-influx-exporter.timer активны.

Проверка лимитов

Проверить системные настройки:

systemctl cat aw-worktime-api
grep '^AW_WORKTIME_' /etc/activitywatch/aw-server.env

Ключевые параметры:

  • AW_WORKTIME_EVENTS_LIMIT - верхний лимит чтения events из ActivityWatch. Для дневной управленческой аналитики значение должно покрывать рабочий день по всем активным сессиям. Для пилотного контура используется 5000; малые значения вроде 250 допустимы только для аварийного degraded-smoke, иначе отчет будет построен по последнему хвосту событий, а не по полному дню.
  • AW_WORKTIME_AW_HTTP_TIMEOUT_SECONDS - timeout запросов к ActivityWatch API.
  • AW_WORKTIME_SOURCE_HTTP_TIMEOUT_SECONDS - timeout внешних source-запросов.
  • AW_WORKTIME_REPORT_CACHE_TTL_SECONDS - TTL fresh report cache.
  • AW_WORKTIME_REPORT_STALE_TTL_SECONDS - TTL stale cache для degraded path.

Нормальная production-политика: bounded timeouts, ограниченный events limit, stale cache включен. Нулевой stale TTL допустим только для специальных тестов, но не для демонстрации или промышленного пилота.

Безопасный restart

  1. Зафиксировать текущий сигнал:
systemctl status aw-worktime-api activitywatch-server --no-pager
journalctl -u aw-worktime-api -n 120 --no-pager
curl -sS --max-time 5 http://<AW_SERVER_HOST>:5610/health | jq
  1. Перезапустить только aw-worktime-api, если ActivityWatch отвечает, но портал получает degraded report:
systemctl restart aw-worktime-api
sleep 3
curl -sS --max-time 5 http://<AW_SERVER_HOST>:5610/health | jq
  1. Перезапустить activitywatch-server только если ActivityWatch API не отвечает или SQLite явно перегружен:
systemctl restart activitywatch-server
sleep 5
systemctl restart aw-worktime-api
  1. Прогреть отчет один раз:
curl -sS --max-time 12 \
  "http://<AW_SERVER_HOST>:5610/reports/worktime/management?format=json&host=SHARKON2025&allow_stale=1" \
  | jq '.status,.stale,.runtime'
  1. Проверить портал:
curl -sS --max-time 8 http://<PORTAL_HOST>/portal/api/health | jq
curl -sS --max-time 12 "http://<PORTAL_HOST>/portal/api/reports?role=executive" | jq '.status'

Production repair: AW SQLite hot path, 2026-06-30

Симптомы:

  • activitywatch-server отвечает 503 на bucket API;
  • журнал содержит poisoned lock / database is locked;
  • aw-worktime-api уходит в bounded DEGRADED;
  • /buckets/aw-worktime-sessions_<HOST>/events?limit=... тайм-аутится даже при малом лимите;
  • RDP browser/category collector пишет bucket create failed или timeout.

Порядок безопасного восстановления:

  1. Остановить RDP guard и процессы aw-windows-telemetry, чтобы не продолжать штурмовать AW API.
  2. Остановить aw-worktime-* timers/services и другие локальные потребители AW API.
  3. Перезапустить activitywatch-server отдельно и проверить /api/0/info.
  4. Если bucket metadata отвечает, но /events медленный, проверить SQLite plan:
EXPLAIN QUERY PLAN
SELECT id,starttime,endtime,data
FROM events
WHERE bucketrow=(SELECT id FROM buckets WHERE name='aw-worktime-sessions_<HOST>')
ORDER BY starttime DESC
LIMIT 100;

Если план строит TEMP B-TREE FOR ORDER BY, нужен составной индекс:

CREATE INDEX IF NOT EXISTS events_bucketrow_starttime_desc_index
ON events(bucketrow, starttime DESC);
ANALYZE;
PRAGMA optimize;
PRAGMA integrity_check;

Индекс добавлять только в controlled window:

  • остановить activitywatch-server;
  • сделать rollback backup SQLite DB;
  • создать индекс;
  • проверить PRAGMA integrity_check = ok;
  • запустить activitywatch-server;
  • проверить, что /events?limit=100 больше не тайм-аутится.

Production DetMir repair 2026-06-30:

  • оставлены две свежие ежедневные SQLite VACUUM backup-копии, старые backup-и ротированы для освобождения места;
  • создан rollback backup: /var/lib/activitywatch/backups/db/aw-sqlite-before-hotpath-index-20260630T035032Z.db;
  • добавлен индекс events_bucketrow_starttime_desc_index;
  • ROCKET_WORKERS=8 добавлен в /etc/activitywatch/aw-server.env;
  • для aw-worktime-api.service добавлен stabilization drop-in: AW_WORKTIME_EVENTS_LIMIT=100, AW_WORKTIME_AW_HTTP_TIMEOUT_SECONDS=25, AW_WORKTIME_EVENTS_CACHE_TTL_SECONDS=600, AW_WORKTIME_REPORT_STALE_TTL_SECONDS=7200.

После ремонта проверить:

curl -sS --max-time 10 http://127.0.0.1:5600/api/0/info
curl -sS --max-time 15 \
  'http://127.0.0.1:5600/api/0/buckets/aw-worktime-sessions_SHARKON2025/events?limit=100'
curl -sS --max-time 15 \
  'http://127.0.0.1:5610/reports/worktime/today?format=json' | jq '{rows:(.rows|length),degraded,runtime}'

Также проверить отсутствие новых poisoned lock после финального старта:

journalctl -u activitywatch-server --since '<FINAL_START_TIME>' --no-pager |
  grep -E 'poisoned lock|Taking datastore lock failed|database is locked'

Crash/readiness test after AW repair

Цель: проверить, что контур выдерживает restart и короткую параллельную нагрузку, а проверки не путают systemctl active с готовым API.

Порядок:

  1. Зафиксировать baseline:
./check-aw-full.sh
  1. На AW server проверить readiness, hot-path и Worktime API:
AW_API=http://127.0.0.1:5600 \
AW_WORKTIME_API=http://127.0.0.1:5610 \
AW_LOGICAL_HOST_ID=SHARKON2025 \
scripts/detmir_resilience_check.sh --live

Если скрипт запускается с ноутбука, live-mode нужно выполнять на самом AW-сервере через SSH/Ansible, потому что он проверяет local systemd и SQLite.

  1. Controlled restart:
systemctl restart aw-worktime-api
curl -sS --max-time 12 \
  'http://127.0.0.1:5610/reports/worktime/today?format=json&host=SHARKON2025&allow_stale=1' |
  jq '{rows:(.rows|length),degraded}'

systemctl restart activitywatch-server
# Не считать "active" готовностью: дождаться HTTP readiness.
timeout 90 bash -c 'until curl -fsS --max-time 8 http://127.0.0.1:5600/api/0/info >/dev/null; do sleep 2; done'
curl -sS --max-time 15 \
  'http://127.0.0.1:5600/api/0/buckets/aw-worktime-sessions_SHARKON2025/events?limit=100' >/dev/null
  1. Проверить, что после рестарта нет новых lock/503:
journalctl -u activitywatch-server --since '<RESTART_TIME>' --no-pager |
  grep -E 'poisoned lock|Taking datastore lock failed|database is locked|503'
  1. Проверить RDP guard restart отдельно:
Restart-Service AWatchRusCollectorGuard -Force
Start-Sleep -Seconds 75
Get-Service AWatchRusCollectorGuard
Get-Process aw-windows-telemetry -ErrorAction SilentlyContinue | Measure-Object

Ожидаемый результат для DetMir после ремонта 2026-06-30:

  • check-aw-full.sh: FRESH=8, STALE=0, DEAD=0;
  • /events?limit=100 отвечает за bounded time и использует events_bucketrow_starttime_desc_index;
  • aw-rus-healthd.service завершается status=0/SUCCESS;
  • server-side TCP до RDP может быть warn, если AW_RUS_HEALTH_RDP_TCP_REQUIRED=false, но bucket freshness и WinRM/SSH через admin path должны оставаться зелёными;
  • optional DLP/Loki heavy runtime units должны быть inactive в экономном production profile.

Rollback

Rollback нужен, если после обновления бинарника или env-настроек:

  • fresh report не собирается;
  • stale cache не отдается;
  • /health не отражает degraded-состояние;
  • портал зависает вместо bounded degraded response.

Порядок:

systemctl stop aw-worktime-api
cp /usr/local/bin/aw-worktime-api.prev /usr/local/bin/aw-worktime-api
systemctl daemon-reload
systemctl start aw-worktime-api
curl -sS --max-time 5 http://<AW_SERVER_HOST>:5610/health | jq

Если rollback касается env/drop-in:

cp /etc/systemd/system/aw-worktime-api.service.d/override.conf.prev \
  /etc/systemd/system/aw-worktime-api.service.d/override.conf
systemctl daemon-reload
systemctl restart aw-worktime-api

Перед rollback убедитесь, что backup-файлы действительно относятся к предыдущей рабочей версии.

Последний production rollback set после исправления canonical users 2026-06-09:

  • /var/lib/grafana/grafana.db.bak.20260609T013605Z;
  • /usr/local/bin/aw-worktime-api-rust.bak.20260609T011956Z;
  • /usr/local/bin/aw-worktime-influx-exporter-rust.bak.20260609T011956Z.

Признаки успешного восстановления

  • /reports/worktime/management отвечает HTTP 200 в bounded time.
  • При свежей сборке status отсутствует или равен OK, stale=false.
  • При временном отказе ActivityWatch API отдается status=DEGRADED, а не timeout.
  • Если stale cache доступен, response содержит stale=true и runtime.report_stale_served=true.
  • Если stale cache недоступен, response компактный, stale=false, reason=report_unavailable.
  • /health у aw-worktime-api и /portal/api/health не маркируют систему как fully healthy при degraded reports.
  • Счетчики aw_query_timeout_count и report_build_error_count перестают расти после восстановления ActivityWatch API.

Smoke-тест degraded path

Локально, без обращения к рабочему контуру:

cd <REPO_ROOT>
cd adk-rust && cargo build -p worktime-api
cd ..
node scripts/worktime-degraded-smoke.mjs

Ожидаемый результат:

worktime degraded smoke OK

Smoke проверяет:

  • fresh report успевает построиться и прогреть cache;
  • при недоступном ActivityWatch API отдается stale degraded response;
  • при отсутствии stale cache отдается компактный degraded response;
  • health отражает degraded-состояние;
  • runtime-поля присутствуют в JSON.