mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
a192df40f9
Three bugs in the self-hosted telemetry services (telemetry-worker/ and
telemetry-dashboard/), reported against 6560052a and all still on main.
1. An index run that uploads late never counted toward activation. The
dashboard's funnel reads machine_first_seen.first_index_day, which only the
nightly rollup wrote, and the rollup re-rolls just the last three days plus
days with no daily_machines row. Ingest accepts timestamps up to 30 days old
and the client keeps an event's original timestamp when it re-queues it, so
an index event 4+ days late landed on a day nothing revisited and
first_index_day stayed NULL. The ingest upsert of machine_first_seen now
lowers first_index_day the way it lowers first_day:
coalesce(min(old, new), old, new), because SQLite's min() is NULL if either
side is. It is the same statement and row, with no new index and no
migration (the column exists since 0002). The rollup still re-derives it
from raw events, which covers events stored before this change.
2. ingest_stalled read only max(day) FROM events, but usage counters have
lived in usage_daily since 0003, so a day with usage and no lifecycle events
read as "The ingest worker ... is not storing anything". /api/meta now also
reads max(day) FROM usage_daily (a primary-key lookup) and returns
latest_usage_day and latest_ingest_day. Ingest counts as stalled only when
there is no lifecycle event from yesterday or later and no usage counter
from the day before yesterday or later, because clients upload a day's
counters only after that day ends. The banner names latest_ingest_day.
3. runNightly rolled up the 3 regular days, then up to 31 missed days, and ran
purgeOldEvents last. Each day first folds its legacy usage_rollup rows,
which takes minutes on a heavy day, so a backlog could run past Cloudflare's
15-minute Cron Trigger limit and skip the purge and the summary line night
after night. The purge now runs first: it is bounded and touches only days
past the window, which no rollup reads. After that, rollups stop starting
new days or fold chunks 10 minutes in (NIGHTLY_BUDGET_MS). A day stopped
mid-fold gets no rollup at all, so it stays a missed day and the next night
continues from its last committed chunk. The summary line counts those days
as `deferred`, and `caught_up` now counts missed days actually rolled up.
Verification: __tests__/telemetry-services.test.ts (new; the root vitest
config picks it up) runs the worker's fetch/cron code and the dashboard API
against the checked-in migrations in in-memory node:sqlite through a small
D1-shaped adapter. With the source fix reversed, all 5 tests fail (activation
0, first_index_day null, ingest_stalled true, and "nightly rollup incomplete:
4 day(s) failed, purge failed" once statements pass the 15-minute mark). With
the fix, all 5 pass. `npm run check` is clean in both packages. Against
wrangler dev, run locally only: smoke:rollup 71/71, smoke:cutover 62/62,
smoke:api 122/122 and render-check 87/87. smoke (ingest) is 46/47; the one
failure is the 70 KB oversized-body curl argument hitting the Windows
command-line limit, and main fails it the same way. The new smoke assertions
fail on main.
Takes effect only once both workers are redeployed; no migration.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
345 lines
20 KiB
Bash
Executable File
345 lines
20 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# End-to-end check of the nightly rollup + retention purge against a real
|
|
# `wrangler dev` and the local D1 state.
|
|
#
|
|
# Seeds three synthetic days of events straight into local D1 (the ingest path clamps
|
|
# client timestamps to the last 30 days, so backdating far enough to exercise the purge
|
|
# has to bypass it), drives the rollup through the admin endpoint and the cron handler,
|
|
# then inspects what actually landed against hand-computed numbers.
|
|
#
|
|
# What it pins:
|
|
# * rollup numbers match the events they came from, including the two that are easy
|
|
# to get wrong — usage_rollup SUMs its `count` prop, and array props unnest
|
|
# * running a day twice changes nothing (idempotent upserts, no double counting)
|
|
# * ?reset=1 drops stale rollup rows on a live day and REFUSES to blank a day whose
|
|
# raw events are already purged
|
|
# * the purge deletes only rows past the window, and leaves machine_days /
|
|
# machine_first_seen alone
|
|
# * each machine's first_index_day is its earliest index, and survives the purge
|
|
# * the cron catches up on a day that saw activity but was never rolled up, and
|
|
# logs its one summary line
|
|
# * usage comes from usage_daily; a legacy usage row still in `events` is folded
|
|
# into it (counts added, row deleted) before the day is rolled up
|
|
# * /admin/rollup does not exist without ADMIN_TOKEN, and rejects a wrong one
|
|
#
|
|
# Re-runnable: it wipes its own synthetic days first, and they are chosen to sit
|
|
# outside the cron's 3-day lookback so the nightly run never rewrites them.
|
|
#
|
|
# npm run smoke:rollup # or: ROLLUP_PORT=8792 ./scripts/smoke-rollup.sh
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")/.."
|
|
PORT="${ROLLUP_PORT:-8788}"
|
|
BASE="http://127.0.0.1:$PORT"
|
|
DB=codegraph-telemetry
|
|
TOKEN=smoke-admin-token
|
|
SEED_SQL=/tmp/cg-smoke-rollup-seed.sql
|
|
LOG=/tmp/cg-smoke-rollup.log
|
|
|
|
pass=0; fail=0
|
|
ok() { pass=$((pass + 1)); printf ' ok %s\n' "$1"; }
|
|
bad() { fail=$((fail + 1)); printf ' FAIL %s — expected %s, got %s\n' "$1" "$2" "$3"; }
|
|
is() { [ "$2" = "$3" ] && ok "$1" || bad "$1" "$2" "$3"; }
|
|
|
|
day_ago() { node -e 'console.log(new Date(Date.now()-process.argv[1]*864e5).toISOString().slice(0,10))' "$1"; }
|
|
|
|
# Synthetic days. MAIN/RESET sit inside the 90-day retention window but outside the
|
|
# cron's 3-day lookback; OLD sits past the window so the purge takes it. MISSED is
|
|
# never rolled up by hand — the cron has to find it on its own.
|
|
DAY_MAIN=$(day_ago 40)
|
|
DAY_RESET=$(day_ago 41)
|
|
DAY_MISSED=$(day_ago 30)
|
|
DAY_OLD=$(day_ago 200)
|
|
DAY_ANCIENT=$(day_ago 199) # a usage counter past the window, for the usage purge
|
|
CUTOFF=$(day_ago 90)
|
|
|
|
# First column of the first row of a query against the LOCAL D1 state.
|
|
q() {
|
|
npx wrangler d1 execute "$DB" --local --json --command "$1" 2>/dev/null |
|
|
node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{
|
|
const r=JSON.parse(s.slice(s.indexOf("[")))[0]?.results?.[0];
|
|
console.log(r===undefined?"":String(Object.values(r)[0]));})'
|
|
}
|
|
|
|
# A daily_dim_counts cell as "count/machines" — "" when the row does not exist.
|
|
dim() { q "select count||'/'||machines from daily_dim_counts
|
|
where day='$1' and event='$2' and dim='$3' and value='$4'"; }
|
|
|
|
# POST /admin/rollup, printing the HTTP status.
|
|
roll() { curl -s -o /dev/null -w '%{http_code}' -X POST -H "x-admin-token: $TOKEN" "$BASE/admin/rollup?$1"; }
|
|
|
|
boot() { # extra wrangler dev args
|
|
npx wrangler dev --port "$PORT" "$@" >"$LOG" 2>&1 &
|
|
DEV_PID=$!
|
|
trap 'kill "$DEV_PID" 2>/dev/null || true; wait "$DEV_PID" 2>/dev/null || true' EXIT
|
|
local up=
|
|
for _ in $(seq 1 60); do
|
|
curl -sf -o /dev/null "$BASE/" && { up=1; break; }
|
|
kill -0 "$DEV_PID" 2>/dev/null || { echo "wrangler dev died:"; cat "$LOG"; exit 1; }
|
|
sleep 1
|
|
done
|
|
[ -n "$up" ] || { echo "worker never came up:"; cat "$LOG"; exit 1; }
|
|
# If wrangler could not bind the port, something else answers every probe and the
|
|
# whole run silently grades a different server. Check who picked up.
|
|
case "$(curl -s "$BASE/")" in
|
|
*'codegraph anonymous-telemetry ingest'*) : ;;
|
|
*) echo "port $PORT is serving something else — set ROLLUP_PORT to a free one"; exit 1 ;;
|
|
esac
|
|
}
|
|
shutdown() {
|
|
kill "$DEV_PID" 2>/dev/null || true
|
|
wait "$DEV_PID" 2>/dev/null || true
|
|
trap - EXIT
|
|
sleep 1 # let miniflare release the local sqlite file
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Seed
|
|
# ---------------------------------------------------------------------------
|
|
echo "applying migrations to local D1"
|
|
npx wrangler d1 migrations apply "$DB" --local >/dev/null 2>&1
|
|
|
|
echo "seeding $DAY_MAIN / $DAY_RESET / $DAY_MISSED / $DAY_OLD"
|
|
node -e '
|
|
const [main, reset, old, missed, ancient, seedFile] = process.argv.slice(1);
|
|
const sq = (v) => `'"'"'${String(v).replace(/'"'"'/g, "'"'"''"'"'")}'"'"'`;
|
|
const M = ["11111111-1111-4111-8111-111111111111", "22222222-2222-4222-8222-222222222222",
|
|
"33333333-3333-4333-8333-333333333333", "44444444-4444-4444-8444-444444444444",
|
|
"99999999-9999-4999-8999-999999999999", "55555555-5555-4555-8555-555555555555"];
|
|
const out = [];
|
|
|
|
// Re-runnable: every table this script touches, scoped to its own synthetic days.
|
|
for (const t of ["events", "daily_event_counts", "daily_dim_counts", "daily_machines", "machine_days", "usage_daily"]) {
|
|
out.push(`DELETE FROM ${t} WHERE day IN (${[main, reset, old, missed, ancient].map(sq).join(", ")});`);
|
|
}
|
|
out.push(`DELETE FROM machine_first_seen WHERE machine_id IN (${M.map(sq).join(", ")});`);
|
|
|
|
// day, machine, event, os, arch, version, node_major, ci, props
|
|
const rows = [
|
|
[main, M[0], "install", "darwin", "arm64", "1.5.0", 22, 0, {targets:["claude","cursor"], scope:"local", kind:"fresh"}],
|
|
[main, M[0], "index", "darwin", "arm64", "1.5.0", 22, 0, {languages:["typescript","go"], file_count_bucket:"100-1k", duration_bucket:"10-60s"}],
|
|
[main, M[1], "index", "darwin", "x64", "1.5.0", 20, 0, {languages:["typescript"], file_count_bucket:"1k-10k", duration_bucket:"10-60s"}],
|
|
[main, M[1], "usage_rollup", "darwin", "x64", "1.5.0", 20, 0, {kind:"mcp_tool", name:"codegraph_explore", count:5, error_count:0, client_name:"Cursor"}],
|
|
[main, M[2], "install", "linux", "x64", "1.4.1", 22, 1, {targets:["claude"], scope:"global", kind:"upgrade"}],
|
|
[main, M[2], "uninstall", "linux", "x64", "1.4.1", 22, 1, {targets:["claude"]}],
|
|
[reset, M[3], "index", "darwin", "arm64", "1.5.0", 22, 0, {languages:["python"], file_count_bucket:"<100", duration_bucket:"<10s"}],
|
|
[old, M[4], "install", "linux", "x64", "1.0.0", 20, 0, {targets:["codex"], scope:"local", kind:"fresh"}],
|
|
[old, M[4], "index", "linux", "x64", "1.0.0", 20, 0, {languages:["rust"], file_count_bucket:"<100", duration_bucket:"<10s"}],
|
|
[missed, M[5], "install", "win32", "x64", "1.5.0", 22, 0, {targets:["claude"], scope:"local", kind:"fresh"}],
|
|
[missed, M[5], "index", "win32", "x64", "1.5.0", 22, 0, {languages:["go"], file_count_bucket:"<100", duration_bucket:"<10s"}],
|
|
];
|
|
for (const [day, m, event, os, arch, version, node, ci, props] of rows) {
|
|
out.push(`INSERT INTO events (received_at, ts, day, event, machine_id, codegraph_version, os, arch, node_major, ci, schema_version, props)
|
|
VALUES (${sq(day + "T12:00:00.000Z")}, ${sq(day + "T12:00:00.000Z")}, ${sq(day)}, ${sq(event)}, ${sq(m)},
|
|
${sq(version)}, ${sq(os)}, ${sq(arch)}, ${node}, ${ci}, 1, ${sq(JSON.stringify(props))});`);
|
|
}
|
|
|
|
// Usage for M[0] arrives the way the ingest path writes it now: added into usage_daily.
|
|
// Usage for M[1] (in the rows above) is a LEGACY row in `events`, as stored before
|
|
// migrations/0003 — the rollup has to fold it in before counting.
|
|
out.push(`INSERT INTO usage_daily (day, machine_id, kind, name, client_name, client_version,
|
|
codegraph_version, os, arch, node_major, count, error_count)
|
|
VALUES (${[main, M[0], "mcp_tool", "codegraph_explore", "Claude Code", "",
|
|
"1.5.0", "darwin", "arm64", "22"].map(sq).join(", ")}, 10, 2);`);
|
|
// A usage counter past the retention window: the purge must take it.
|
|
out.push(`INSERT INTO usage_daily (day, machine_id, kind, name, count, error_count)
|
|
VALUES (${[ancient, M[4], "cli_command", "index"].map(sq).join(", ")}, 3, 0);`);
|
|
|
|
// What the ingest path would have written alongside those events.
|
|
for (const [m, day, prod] of [[M[0], main, 1], [M[1], main, 1], [M[2], main, 0], [M[3], reset, 1], [M[4], old, 1], [M[5], missed, 1]]) {
|
|
out.push(`INSERT INTO machine_days (machine_id, day, prod) VALUES (${sq(m)}, ${sq(day)}, ${prod});`);
|
|
out.push(`INSERT INTO machine_first_seen (machine_id, first_day) VALUES (${sq(m)}, ${sq(day)})
|
|
ON CONFLICT (machine_id) DO UPDATE SET first_day = min(machine_first_seen.first_day, excluded.first_day);`);
|
|
}
|
|
|
|
// A rollup row from a dimension that no longer exists — only ?reset=1 should clear it.
|
|
out.push(`INSERT INTO daily_dim_counts (day, event, dim, value, count, machines)
|
|
VALUES (${sq(reset)}, ${sq("index")}, ${sq("obsolete_dim")}, ${sq("stale")}, 99, 99);`);
|
|
|
|
require("fs").writeFileSync(seedFile, out.join("\n"));
|
|
' "$DAY_MAIN" "$DAY_RESET" "$DAY_OLD" "$DAY_MISSED" "$DAY_ANCIENT" "$SEED_SQL"
|
|
npx wrangler d1 execute "$DB" --local --file "$SEED_SQL" >/dev/null
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# The admin route does not exist without a token
|
|
# ---------------------------------------------------------------------------
|
|
echo
|
|
echo "admin route, no ADMIN_TOKEN configured"
|
|
boot
|
|
is "POST /admin/rollup → 404" 404 "$(curl -s -o /dev/null -w '%{http_code}' -X POST "$BASE/admin/rollup")"
|
|
is "…even with a token header" 404 \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' -X POST -H "x-admin-token: $TOKEN" "$BASE/admin/rollup")"
|
|
shutdown
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Drive the rollup
|
|
# ---------------------------------------------------------------------------
|
|
echo
|
|
echo "admin route, ADMIN_TOKEN configured"
|
|
boot --test-scheduled --var "ADMIN_TOKEN:$TOKEN"
|
|
|
|
is "no token → 401" 401 "$(curl -s -o /dev/null -w '%{http_code}' -X POST "$BASE/admin/rollup")"
|
|
is "wrong token → 401" 401 \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' -X POST -H 'x-admin-token: nope' "$BASE/admin/rollup")"
|
|
is "GET → 405" 405 "$(curl -s -o /dev/null -w '%{http_code}' -H "x-admin-token: $TOKEN" "$BASE/admin/rollup")"
|
|
is "impossible day → 400" 400 "$(roll 'day=2026-02-31')"
|
|
is "malformed day → 400" 400 "$(roll 'day=yesterday')"
|
|
is "days out of range → 400" 400 "$(roll "day=$DAY_MAIN&days=99")"
|
|
|
|
echo
|
|
echo "rollup"
|
|
is "rollup $DAY_MAIN → 200" 200 "$(roll "day=$DAY_MAIN")"
|
|
is "rollup $DAY_MAIN again → 200" 200 "$(roll "day=$DAY_MAIN")"
|
|
is "rollup $DAY_OLD, whose events are still there → 200" 200 "$(roll "day=$DAY_OLD")"
|
|
is "rollup $DAY_RESET with reset → 200" 200 "$(roll "day=$DAY_RESET&reset=1")"
|
|
|
|
# The cron body: rolls up the last three days, catches up $DAY_MISSED (active, never
|
|
# rolled up), and purges everything past the window.
|
|
is "cron trigger → 200" 200 "$(curl -s -o /dev/null -w '%{http_code}' "$BASE/__scheduled?cron=30+0+*+*+*")"
|
|
sleep 2
|
|
# Its one summary line: written once the purge and every rollup are done, and with
|
|
# nothing this small left for the next night by the time budget.
|
|
is "the cron logs its summary, nothing failed or deferred" "0/0" \
|
|
"$(grep -ao '{"msg":"nightly rollup".*}' "$LOG" | tail -1 |
|
|
node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{
|
|
const r=JSON.parse(s||"{}");console.log(`${r.failed}/${r.deferred}`);})')"
|
|
|
|
# Rolling a purged day with reset=1 must NOT blank the rollups it already has: past
|
|
# the window the reset is ignored, so the delete-then-rebuild can't find zero events.
|
|
is "rollup $DAY_OLD after the purge, with reset → 200" 200 "$(roll "day=$DAY_OLD&reset=1")"
|
|
is "…and reports the reset it refused to run" "[\"$DAY_OLD\"]" \
|
|
"$(curl -s -X POST -H "x-admin-token: $TOKEN" "$BASE/admin/rollup?day=$DAY_OLD&reset=1" |
|
|
node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.stringify(JSON.parse(s).reset_ignored)))')"
|
|
|
|
# first_index_day only ever moves earlier. Give M[1] an index day before $DAY_MAIN,
|
|
# then re-roll $DAY_MAIN (where it indexed again): the earlier day must stand.
|
|
q "update machine_first_seen set first_index_day='$DAY_RESET'
|
|
where machine_id='22222222-2222-4222-8222-222222222222'" >/dev/null
|
|
is "re-roll $DAY_MAIN over an earlier first index day → 200" 200 "$(roll "day=$DAY_MAIN")"
|
|
|
|
shutdown
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# What actually landed — every number below is hand-computed from the seed above
|
|
# ---------------------------------------------------------------------------
|
|
echo
|
|
echo "daily_machines"
|
|
is "3 machines, 2 of them production (one is ci)" "3/2" \
|
|
"$(q "select machines||'/'||prod_machines from daily_machines where day='$DAY_MAIN'")"
|
|
|
|
echo
|
|
echo "daily_event_counts"
|
|
is "install: 2 events from 2 machines" "2/2" \
|
|
"$(q "select count||'/'||machines from daily_event_counts where day='$DAY_MAIN' and event='install'")"
|
|
is "index: 2 events from 2 machines" "2/2" \
|
|
"$(q "select count||'/'||machines from daily_event_counts where day='$DAY_MAIN' and event='index'")"
|
|
is "uninstall: 1 event from 1 machine" "1/1" \
|
|
"$(q "select count||'/'||machines from daily_event_counts where day='$DAY_MAIN' and event='uninstall'")"
|
|
# The one that is easy to get wrong: 2 rows carrying count 10 and 5 is 15 tool calls.
|
|
is "usage_rollup: SUMs the count prop (10+5), not the rows" "15/2" \
|
|
"$(q "select count||'/'||machines from daily_event_counts where day='$DAY_MAIN' and event='usage_rollup'")"
|
|
is "one row per event type" 4 "$(q "select count(*) from daily_event_counts where day='$DAY_MAIN'")"
|
|
|
|
echo
|
|
echo "daily_dim_counts"
|
|
is "os / index / darwin" "2/2" "$(dim "$DAY_MAIN" index os darwin)"
|
|
is "os / usage_rollup / darwin sums counts" "15/2" "$(dim "$DAY_MAIN" usage_rollup os darwin)"
|
|
is "arch / uninstall / x64" "1/1" "$(dim "$DAY_MAIN" uninstall arch x64)"
|
|
is "codegraph_version / index / 1.5.0" "2/2" "$(dim "$DAY_MAIN" index codegraph_version 1.5.0)"
|
|
is "node_major / install / 22 (stored as text)" "2/2" "$(dim "$DAY_MAIN" install node_major 22)"
|
|
is "file_count_bucket / index / 100-1k" "1/1" "$(dim "$DAY_MAIN" index file_count_bucket 100-1k)"
|
|
is "duration_bucket / index / 10-60s" "2/2" "$(dim "$DAY_MAIN" index duration_bucket 10-60s)"
|
|
is "scope / install / global" "1/1" "$(dim "$DAY_MAIN" install scope global)"
|
|
is "kind / install / fresh" "1/1" "$(dim "$DAY_MAIN" install kind fresh)"
|
|
is "kind / usage_rollup / mcp_tool (same dim, other event)" "15/2" "$(dim "$DAY_MAIN" usage_rollup kind mcp_tool)"
|
|
is "name / usage_rollup / codegraph_explore" "15/2" "$(dim "$DAY_MAIN" usage_rollup name codegraph_explore)"
|
|
is "client_name / usage_rollup / Claude Code" "10/1" "$(dim "$DAY_MAIN" usage_rollup client_name 'Claude Code')"
|
|
# languages and targets are JSON arrays: one row per element, counted once per event.
|
|
is "language / index / typescript (unnested, 2 events)" "2/2" "$(dim "$DAY_MAIN" index language typescript)"
|
|
is "language / index / go (unnested, 1 event)" "1/1" "$(dim "$DAY_MAIN" index language go)"
|
|
is "target / install / claude (unnested, 2 events)" "2/2" "$(dim "$DAY_MAIN" install target claude)"
|
|
is "target / install / cursor" "1/1" "$(dim "$DAY_MAIN" install target cursor)"
|
|
is "target / uninstall / claude" "1/1" "$(dim "$DAY_MAIN" uninstall target claude)"
|
|
# Only groups with at least one error are stored, so machines = machines that saw one.
|
|
is "name_error / usage_rollup / codegraph_explore" "2/1" "$(dim "$DAY_MAIN" usage_rollup name_error codegraph_explore)"
|
|
is "no dimension row for a machine with no errors" "" "$(dim "$DAY_MAIN" usage_rollup name_error nothing)"
|
|
is "40 dimension rows in total (no strays, no doubles)" 40 \
|
|
"$(q "select count(*) from daily_dim_counts where day='$DAY_MAIN'")"
|
|
|
|
# Independent of the hand-computed numbers: recompute two of them straight off `events`.
|
|
echo
|
|
echo "cross-check against the raw events"
|
|
is "machines matches count(distinct machine_id)" \
|
|
"$(q "select count(distinct machine_id) from events where day='$DAY_MAIN' and event='index'")" \
|
|
"$(q "select machines from daily_event_counts where day='$DAY_MAIN' and event='index'")"
|
|
is "usage count matches sum(usage_daily.count)" \
|
|
"$(q "select sum(count) from usage_daily where day='$DAY_MAIN'")" \
|
|
"$(q "select count from daily_event_counts where day='$DAY_MAIN' and event='usage_rollup'")"
|
|
|
|
echo
|
|
echo "legacy usage rows"
|
|
is "folded out of events" 0 "$(q "select count(*) from events where day='$DAY_MAIN' and event='usage_rollup'")"
|
|
is "…into usage_daily, counts and client intact" "5|0|Cursor|darwin|x64|20" \
|
|
"$(q "select count||'|'||error_count||'|'||client_name||'|'||os||'|'||arch||'|'||node_major from usage_daily
|
|
where day='$DAY_MAIN' and machine_id='22222222-2222-4222-8222-222222222222'")"
|
|
is "…and re-rolling the day adds nothing (no double count)" 15 \
|
|
"$(q "select sum(count) from usage_daily where day='$DAY_MAIN'")"
|
|
|
|
echo
|
|
echo "reset"
|
|
is "?reset=1 drops a rollup row whose dimension no longer exists" 0 \
|
|
"$(q "select count(*) from daily_dim_counts where day='$DAY_RESET' and dim='obsolete_dim'")"
|
|
is "…and recomputes the day correctly" "1/1" "$(dim "$DAY_RESET" index language python)"
|
|
is "…leaving exactly the 7 dimensions that day has" 7 \
|
|
"$(q "select count(*) from daily_dim_counts where day='$DAY_RESET'")"
|
|
|
|
echo
|
|
echo "retention purge"
|
|
is "raw events past the window are gone" 0 "$(q "select count(*) from events where day='$DAY_OLD'")"
|
|
is "nothing older than the cutoff survives" 0 "$(q "select count(*) from events where day<'$CUTOFF'")"
|
|
# 6 seeded, minus the legacy usage row the rollup folded into usage_daily.
|
|
is "events inside the window are untouched" 5 "$(q "select count(*) from events where day='$DAY_MAIN'")"
|
|
is "usage counters past the window are purged" 0 "$(q "select count(*) from usage_daily where day='$DAY_ANCIENT'")"
|
|
is "…and inside it they stay" 2 "$(q "select count(*) from usage_daily where day='$DAY_MAIN'")"
|
|
is "machine_days is NOT purged (retention cohorts need it)" 1 \
|
|
"$(q "select count(*) from machine_days where day='$DAY_OLD'")"
|
|
is "machine_first_seen is NOT purged" "$DAY_OLD" \
|
|
"$(q "select first_day from machine_first_seen where machine_id='99999999-9999-4999-8999-999999999999'")"
|
|
|
|
echo
|
|
echo "first_index_day — the activation funnel's input"
|
|
is "machines that indexed get that day" "$DAY_MAIN" \
|
|
"$(q "select first_index_day from machine_first_seen where machine_id='11111111-1111-4111-8111-111111111111'")"
|
|
is "a machine that never indexed stays NULL" "null" \
|
|
"$(q "select coalesce(first_index_day, 'null') from machine_first_seen where machine_id='33333333-3333-4333-8333-333333333333'")"
|
|
is "set on a reset run too" "$DAY_RESET" \
|
|
"$(q "select first_index_day from machine_first_seen where machine_id='44444444-4444-4444-8444-444444444444'")"
|
|
is "outlives the purge of the events it came from" "$DAY_OLD" \
|
|
"$(q "select first_index_day from machine_first_seen where machine_id='99999999-9999-4999-8999-999999999999'")"
|
|
is "only ever lowered: re-rolling a later index day leaves an earlier one" "$DAY_RESET" \
|
|
"$(q "select first_index_day from machine_first_seen where machine_id='22222222-2222-4222-8222-222222222222'")"
|
|
|
|
echo
|
|
echo "the cron catches up on a day nobody rolled up"
|
|
is "$DAY_MISSED got its daily_machines row" "1/1" \
|
|
"$(q "select machines||'/'||prod_machines from daily_machines where day='$DAY_MISSED'")"
|
|
is "…and its event counts" "1/1" \
|
|
"$(q "select count||'/'||machines from daily_event_counts where day='$DAY_MISSED' and event='index'")"
|
|
is "…and its breakdowns" "1/1" "$(dim "$DAY_MISSED" index language go)"
|
|
is "…and its first index day" "$DAY_MISSED" \
|
|
"$(q "select first_index_day from machine_first_seen where machine_id='55555555-5555-4555-8555-555555555555'")"
|
|
|
|
echo
|
|
echo "rollups outlive the events they came from"
|
|
is "daily_event_counts survives the purge" "1/1" \
|
|
"$(q "select count||'/'||machines from daily_event_counts where day='$DAY_OLD' and event='index'")"
|
|
is "daily_dim_counts survives the purge" "1/1" "$(dim "$DAY_OLD" index language rust)"
|
|
is "…all 14 rows of it, even after a reset run over the purged day" 14 \
|
|
"$(q "select count(*) from daily_dim_counts where day='$DAY_OLD'")"
|
|
is "daily_machines is still rebuilt for a purged day (machine_days survives)" "1/1" \
|
|
"$(q "select machines||'/'||prod_machines from daily_machines where day='$DAY_OLD'")"
|
|
|
|
echo
|
|
echo "$pass passed, $fail failed"
|
|
[ "$fail" -eq 0 ]
|