Aggregating Usage: From Events to Dashboards
Raw events are a firehose. Dashboards need aggregates. The aggregation step groups usage events by dimension and time bucket so a dashboard can answer 'how many tokens did support-chat burn today?' without scanning every request.
from collections import defaultdict
from datetime import datetime, timedelta
def aggregate_daily(events, day):
'''Sum tokens per feature for one UTC day.'''
totals = defaultdict(lambda: {'input': 0, 'output': 0, 'requests': 0})
day_start = datetime.fromisoformat(day)
day_end = day_start + timedelta(days=1)
for e in events:
ts = datetime.fromisoformat(e['timestamp'])
if day_start <= ts < day_end:
key = (e['feature'], e['tenant'])
totals[key]['input'] += e['input_tokens']
totals[key]['output'] += e['output_tokens']
totals[key]['requests'] += 1
return dict(totals)
events = [
{'feature': 'support-chat', 'tenant': 'acme', 'environment': 'production',
'model': 'claude-sonnet-4-6', 'input_tokens': 1200, 'output_tokens': 300,
'timestamp': '2026-09-30T10:15:00'},
{'feature': 'support-chat', 'tenant': 'acme', 'environment': 'production',
'model': 'claude-sonnet-4-6', 'input_tokens': 900, 'output_tokens': 250,
'timestamp': '2026-09-30T10:16:00'},
{'feature': 'doc-extraction', 'tenant': 'acme', 'environment': 'staging',
'model': 'claude-haiku-4-5', 'input_tokens': 4000, 'output_tokens': 800,
'timestamp': '2026-09-30T10:16:30'},
]
print(aggregate_daily(events, '2026-09-30'))
Output:
{('support-chat', 'acme'): {'input': 2100, 'output': 550, 'requests': 2},
('doc-extraction', 'acme'): {'input': 4000, 'output': 800, 'requests': 1}}
The non-obvious line is the defaultdict(lambda: ...). A plain dict would raise KeyError on the first unseen feature-tenant pair; the defaultdict creates the zeroed bucket on first access, so the aggregation loop stays a single pass with no existence checks.
Group by (feature, tenant) and the dashboard question answers itself: support-chat burned 2,650 tokens across two requests, doc-extraction burned 4,800 in one. The staging environment tag on the second row is what tells you that 4,800-token request is a test loop, not customer traffic.
In production this aggregation runs as a streaming job over the event log, writing rollups to a time-series store. The logic is identical; only the execution model changes.
Alerts That Catch Runaway Loops in Hours
Aggregates are history. Alerts are the present. The goal is to know about a runaway loop before the invoice does — while there is still time to kill the deploy.
The simplest effective alert is a per-customer spend threshold. Each tenant has a plan limit; when their trailing 24-hour spend crosses 80% of it, page the team. At 100%, the proxy rejects new requests with a clear error rather than letting the tenant blow through the month.
def check_spend_alert(tenant_spend_24h: float, plan_limit: float) -> str | None:
ratio = tenant_spend_24h / plan_limit
if ratio >= 1.0:
return 'BLOCK'
if ratio >= 0.8:
return 'PAGE'
return None
Thresholds catch known failure modes: a buggy integration retrying in a loop, an agent that never terminates, a customer scraping your API. They do not catch slow leaks — a feature that quietly doubles its token use over a week. For that, compare each day's per-feature total against a rolling baseline:
def detect_spike(today: int, baseline_avg: float) -> bool:
return baseline_avg > 0 and today > baseline_avg * 2.5
A 2.5x jump over the trailing 14-day average is the signal that something changed — a new prompt that doubled context, a retrieval config that started pulling ten chunks instead of three, a model swap with different tokenization. The baseline matters: alerting on raw totals pages you every Monday morning.
Route these alerts to the team that owns the feature, not a generic ops channel. The support-chat on-call can judge whether a spike is a launch or a loop; a central team cannot.
Reconciling Against Provider Invoices
Your metering says you spent $11,200. The provider invoice says $12,400. The $1,200 gap is drift, and it is normal. The question is whether your drift is 2% or 20%.
Drift has a short list of causes:
| Cause | Direction | Why it happens |
|---|---|---|
| Retries | You over-count | Your meter counts the retry; the provider bills it too, but your success-rate dashboard hides the duplicate |
| Streaming token counts | You under-count | Providers sometimes report final usage only after the stream closes; your meter reads the request-time estimate |
| Cache hits | You over-count | You bill full input tokens; the provider bills cached tokens at the discounted rate |
| Model version changes | Either | A model update can change tokenization, so the same text costs different tokens |
Reconcile monthly, mechanically:
- Pull the provider invoice's per-model token totals.
- Pull your metering's per-model token totals for the same window.
- Compute the ratio per model. A ratio within 1.0–1.1 is healthy; above 1.2 means your metering is missing something the provider is billing.
- Investigate the largest gap first — it is usually one cause, not five.
Reconciliation is also your defense against the 'our dashboards are wrong' conversation. When finance asks why the bill is $12,400, the answer is not 'the meter is approximate' — it is 'the meter tracks within 3% of the invoice, and here is the breakdown.' That answer comes from tracing LLM calls for the request-level detail and metering for the aggregate view.
Quiz
1. Why is the environment tag (production vs staging) critical in usage metering?
2. What does a per-customer spend threshold at 80% of plan limit accomplish?
3. Why does a streaming response cause drift between internal metering and provider invoices?
4. Why compare each day's per-feature total against a rolling baseline instead of alerting on raw totals?
Flashcards
Question
Why are feature, tenant, and environment tags on every request non-negotiable?
Click to reveal answer
Answer
They turn a single total into answers: which feature is growing, which tenant is over plan, and whether spend is production or staging. Without them, a cost spike is a number you cannot act on.
Question
What does a per-customer spend threshold at 80% catch?
Click to reveal answer
Answer
Buggy integrations retrying in loops, agents that never terminate, and abusive API traffic — while there is still time to investigate before the 100% block rejects requests.
Question
Why use a rolling baseline for spike detection instead of raw totals?
Click to reveal answer
Answer
Raw totals page you every Monday when traffic naturally spikes. A 2.5x jump over the trailing 14-day average signals that something changed, not that the week started.
Question
What are the four causes of drift between internal metering and provider invoices?
Click to reveal answer
Answer
Retries (over-count), streaming token counts reported after close (under-count), cache hits billed at full rate (over-count), and model version changes that alter tokenization (either direction).
Question
Why must metering use the provider's usage field rather than a local estimate?
Click to reveal answer
Answer
The provider bills on its own token counts. A local estimate diverges from the invoice by whatever your tokenizer guesses wrong, and reconciliation cannot explain a gap it did not measure.
Revision Notes
Key Takeaways
- 1. Tag every request with feature, tenant, and environment before it reaches the model — without dimensions, a cost spike is a number you cannot act on.
- 2. Aggregate per feature per day with a defaultdict so the first unseen pair creates a zeroed bucket; the staging tag is what separates test loops from customer traffic.
- 3. Alert on per-customer spend thresholds (80% page, 100% block) and on 2.5x jumps over a rolling 14-day baseline — raw totals page you every Monday.
- 4. Reconcile against provider invoices monthly; drift above 1.2x means your metering is missing something the provider is billing.
Interview Tips
- • Describe a concrete runaway loop your metering caught: the feature, the threshold that fired, and the deploy you killed — numbers beat adjectives.
- • Explain why you alert on a rolling baseline rather than raw totals; the Monday-morning false positive is the detail that shows you have operated this.
- • Walk through the four drift causes and how reconciliation quantifies them; interviewers check whether you trust your own dashboards.
- • Connect metering to tracing and budgeting: metering gives the aggregate view, traces give request-level replay, and budgets turn both into controls.
Cheat Sheet
Metering Token Usage — Cheat Sheet
Tag every request: feature, tenant, environment, model, input_tokens, output_tokens, timestamp. The environment tag is the one teams skip and regret.
Aggregate: group by (feature, tenant) per UTC day. Use defaultdict so the first unseen pair creates a zeroed bucket — single pass, no existence checks.
Alerts:
- Per-customer: 80% of plan → page, 100% → block at the proxy.
- Per-feature spike:
today > 14-day_avg * 2.5→ something changed. - Route alerts to the feature's on-call, not a generic ops channel.
Drift causes: retries (over), streaming final-count (under), cache hits (over), model version changes (either).
Reconcile monthly: per-model ratio of internal vs invoice totals. 1.0–1.1 healthy; above 1.2 investigate the largest gap first.
Links: tracing LLM calls · profiling latency
Continue Learning
Before You Start
- No prerequisites listed.