20 KiB
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
Вчера: активность по сотрудникамвозвращает только labelsuser1,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
- Зафиксировать текущий сигнал:
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
- Перезапустить только
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
- Перезапустить
activitywatch-serverтолько если ActivityWatch API не отвечает или SQLite явно перегружен:
systemctl restart activitywatch-server
sleep 5
systemctl restart aw-worktime-api
- Прогреть отчет один раз:
curl -sS --max-time 12 \
"http://<AW_SERVER_HOST>:5610/reports/worktime/management?format=json&host=SHARKON2025&allow_stale=1" \
| jq '.status,.stale,.runtime'
- Проверить портал:
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уходит в boundedDEGRADED;/buckets/aw-worktime-sessions_<HOST>/events?limit=...тайм-аутится даже при малом лимите;- RDP browser/category collector пишет
bucket create failedили timeout.
Порядок безопасного восстановления:
- Остановить RDP guard и процессы
aw-windows-telemetry, чтобы не продолжать штурмовать AW API. - Остановить
aw-worktime-*timers/services и другие локальные потребители AW API. - Перезапустить
activitywatch-serverотдельно и проверить/api/0/info. - Если 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.
Порядок:
- Зафиксировать baseline:
./check-aw-full.sh
- На 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.
- 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
- Проверить, что после рестарта нет новых lock/503:
journalctl -u activitywatch-server --since '<RESTART_TIME>' --no-pager |
grep -E 'poisoned lock|Taking datastore lock failed|database is locked|503'
- Проверить 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.