Async LLM/embedding token usage tracking with multi-backend support (Redis, Postgres, MongoDB)
Project description
Token Usage Metrics
A lightweight, async-first Python package for tracking LLM and embedding token usage with multi-backend support (Redis, PostgreSQL, MongoDB). Built for production with lifetime retention, project-based deletion, comprehensive aggregations, and non-blocking async operations.
Features
โจ Multi-Backend Support: Redis, PostgreSQL, and MongoDB backends with unified API ๐ Rich Aggregations: Daily summaries, project/type grouping, and time-series for dashboards ๐ Async-First: Non-blocking operations with background flushing and circuit breakers ๐พ Lifetime Retention: No enforced TTL; explicit project-based deletion with date ranges ๐ Flexible Queries: Raw event fetching with filters and cursor-based pagination ๐ก๏ธ Production-Ready: Structured logging, retry logic, graceful fallbacks, and health checks
Installation
# Install with Redis support (recommended for getting started)
uv add token-usage-metrics[redis]
# Or with all backends
uv add token-usage-metrics[all]
# Individual backends
uv add token-usage-metrics[postgres] # PostgreSQL
uv add token-usage-metrics[mongo] # MongoDB
Quick Start
import asyncio
from datetime import datetime, timezone
from token_usage_metrics import TokenUsageClient, UsageEvent, Settings
async def main():
# Create client with Redis (default)
settings = Settings(
backend="redis",
redis_url="redis://localhost:6379/0"
)
async with TokenUsageClient(settings) as client:
# Log token usage (async, non-blocking)
event = UsageEvent(
project_name="my_app",
request_type="chat",
input_tokens=100,
output_tokens=50,
total_tokens=150, # Optional: auto-calculated if omitted
request_count=1,
metadata={"model": "gpt-4", "user_id": "user_123"}
)
await client.log(event)
# Fetch raw events with filters
from token_usage_metrics import UsageFilter
filters = UsageFilter(
project_name="my_app",
time_from=datetime.now(timezone.utc) - timedelta(days=7)
)
events, cursor = await client.fetch_raw(filters)
print(f"Found {len(events)} events")
# Get daily aggregates for graphs
from token_usage_metrics import AggregateSpec
spec = AggregateSpec()
daily_data = await client.summary_by_day(spec, filters)
for bucket in daily_data:
print(f"{bucket.start.date()}: {bucket.metrics}")
asyncio.run(main())
Configuration
Configure via environment variables (prefix: TUM_) or Settings object:
from token_usage_metrics import Settings
settings = Settings(
# Backend selection
backend="redis", # redis | postgres | mongodb
# Redis settings
redis_url="redis://localhost:6379/0",
redis_pool_size=10,
# Postgres settings
postgres_dsn="postgresql://user:pass@localhost:5432/token_usage",
# MongoDB settings
mongodb_url="mongodb://localhost:27017",
mongodb_database="token_usage",
# Queue/buffering
buffer_size=1000,
flush_interval=1.0, # seconds
flush_batch_size=200,
# Resilience
max_retries=3,
circuit_breaker_threshold=5,
)
Or via .env:
TUM_BACKEND=redis
TUM_REDIS_URL=redis://localhost:6379/0
TUM_BUFFER_SIZE=1000
TUM_FLUSH_INTERVAL=1.0
Backend Setup
Redis
# Docker
docker run -d -p 6379:6379 redis:7-alpine
# Or use managed Redis (AWS ElastiCache, Redis Cloud, etc.)
Schema: Hash-per-event + day-partitioned ZSETs + daily aggregate hashes. Optimized for fast writes and efficient date-range queries.
PostgreSQL
# Docker
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=pass -e POSTGRES_DB=token_usage postgres:16-alpine
# Tables auto-created on first connection
Schema: usage_events table + daily_aggregates table with indexes on (project, timestamp), (type, timestamp).
MongoDB
# Docker
docker run -d -p 27017:27017 mongo:7
# Collections auto-created with indexes
Schema: usage_events collection + daily_aggregates collection with compound indexes.
API Reference
Logging Events
# Single event (async, non-blocking)
await client.log(event)
# Multiple events
await client.log_many([event1, event2, event3])
# Force flush pending events
flushed_count = await client.flush(timeout=5.0)
Fetching Raw Events
from token_usage_metrics import UsageFilter
filters = UsageFilter(
project_name="my_app",
request_type="chat",
time_from=datetime(...),
time_to=datetime(...),
limit=100,
cursor=None # For pagination
)
events, next_cursor = await client.fetch_raw(filters)
# Pagination
if next_cursor:
more_events, cursor = await client.fetch_raw(
UsageFilter(cursor=next_cursor, limit=100)
)
Aggregations & Summaries
from token_usage_metrics import AggregateSpec, AggregateMetric
# Daily time-series (for graphs)
spec = AggregateSpec(
metrics={
AggregateMetric.SUM_TOTAL,
AggregateMetric.COUNT_REQUESTS,
AggregateMetric.AVG_TOTAL_PER_REQUEST
}
)
daily_buckets = await client.summary_by_day(spec, filters)
# Grouped by project
project_summaries = await client.summary_by_project(spec, filters)
for summary in project_summaries:
print(f"{summary.group_keys['project_name']}: {summary.metrics}")
# Grouped by request type
type_summaries = await client.summary_by_request_type(spec, filters)
Deleting Project Data
from token_usage_metrics import DeleteOptions
# Simulate deletion (dry-run)
options = DeleteOptions(
project_name="my_app",
time_from=datetime(...), # Optional
time_to=datetime(...), # Optional
include_aggregates=True,
simulate=True
)
result = await client.delete_project(options)
print(f"Would delete {result.events_deleted} events and {result.aggregates_deleted} aggregates")
# Actual deletion
options.simulate = False
result = await client.delete_project(options)
Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ TokenUsageClient โ
โ (Async API: log, fetch_raw, summary_*, delete_project) โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโดโโโโโโโโโ
โ AsyncEventQueue โ (Background flusher, circuit breaker)
โโโโโโโโโโฌโโโโโโโโโโ
โ
โโโโโโโโโโโโโดโโโโโโโโโโโโ
โ Backend Interface โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ
โ โ โ
โโโโโผโโโโโ โโโโโผโโโโโโ โโโโผโโโโโโโ
โ Redis โ โPostgres โ โ MongoDB โ
โโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
- Async Queue: Buffers events in memory, flushes batches periodically
- Circuit Breaker: Auto-recovery when backend is unhealthy
- Retry Logic: Exponential backoff with jitter for transient errors
- Lifetime Retention: No enforced TTL (configurable per-backend if needed)
Testing
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=token_usage_metrics
# Run specific test
uv run pytest tests/test_redis_backend.py -v
Development
# Install dev dependencies
uv add --dev ruff mypy pytest pytest-asyncio fakeredis
# Lint and format
uv run ruff check .
uv run ruff format .
# Type checking
uv run mypy token_usage_metrics
Performance
- Redis: ~10k writes/sec (pipelined batches), ~5k reads/sec (optimized ZSETs)
- Postgres: ~2k writes/sec (bulk inserts), ~10k reads/sec (indexed queries)
- MongoDB: ~5k writes/sec (batched inserts), ~8k reads/sec (indexed scans)
Benchmarks on single-instance deployments. Scale horizontally for higher throughput.
License
MIT
Contributing
Contributions welcome! Please open an issue or PR on GitHub.
Built with โค๏ธ for production LLM applications.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file token_usage_metrics-0.1.0.tar.gz.
File metadata
- Download URL: token_usage_metrics-0.1.0.tar.gz
- Upload date:
- Size: 87.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da120a207cb33a27736f0622ae057d12c653c9e7a7e65ffa44b853a614594f34
|
|
| MD5 |
b86b8ad90a27f5bc16cb04b48332d727
|
|
| BLAKE2b-256 |
f6afc411c9632a93f2fe4c9803f209c858748cd78dd63e12095ca3133f961b30
|
Provenance
The following attestation bundles were made for token_usage_metrics-0.1.0.tar.gz:
Publisher:
python-publish.yml on lazzyms/token_usage_metrics
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
token_usage_metrics-0.1.0.tar.gz -
Subject digest:
da120a207cb33a27736f0622ae057d12c653c9e7a7e65ffa44b853a614594f34 - Sigstore transparency entry: 707201601
- Sigstore integration time:
-
Permalink:
lazzyms/token_usage_metrics@6523c4cc5bdeb22d373e6281a19210e9a5a0ee6c -
Branch / Tag:
refs/heads/master - Owner: https://github.com/lazzyms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@6523c4cc5bdeb22d373e6281a19210e9a5a0ee6c -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file token_usage_metrics-0.1.0-py3-none-any.whl.
File metadata
- Download URL: token_usage_metrics-0.1.0-py3-none-any.whl
- Upload date:
- Size: 54.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a270f68238bc3f001e1e136b087b05e520603fa283aada7e0fb8264e428e1f9a
|
|
| MD5 |
df48ca170140031bf3193b32dbb56f92
|
|
| BLAKE2b-256 |
2d628da9a8121c56c2c8693a3f962fcfbbf2e4e227099b3df3994c5f88cf8620
|
Provenance
The following attestation bundles were made for token_usage_metrics-0.1.0-py3-none-any.whl:
Publisher:
python-publish.yml on lazzyms/token_usage_metrics
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
token_usage_metrics-0.1.0-py3-none-any.whl -
Subject digest:
a270f68238bc3f001e1e136b087b05e520603fa283aada7e0fb8264e428e1f9a - Sigstore transparency entry: 707201628
- Sigstore integration time:
-
Permalink:
lazzyms/token_usage_metrics@6523c4cc5bdeb22d373e6281a19210e9a5a0ee6c -
Branch / Tag:
refs/heads/master - Owner: https://github.com/lazzyms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@6523c4cc5bdeb22d373e6281a19210e9a5a0ee6c -
Trigger Event:
workflow_dispatch
-
Statement type: