This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Dependency management uses uv. Python version is pinned in .python-version (3.13).
- Install deps:
uv sync --locked - Run the server locally:
uv run celerymon --broker-url=redis://localhost:6379/0 --queue=celery - Type check (matches CI):
uv run mypy celerymon/ - Pyright is also configured as a dev dep:
uv run pyright celerymon/ - Lint/format: handled by
pre-commit(ruff + ruff-format + pyupgrade). Runpre-commit run --all-filesor rely on the hook. - Build the Docker image:
docker build -t celerymon .(entrypoint iscelerymon).
There is no test suite in this repo — CI runs only pre-commit and mypy.
celerymon is a single-process Prometheus exporter for Celery. The CLI (celerymon/cli/__init__.py) wires three independent watchers into one collector and serves both /metrics and /healthz from a single wsgiref server.
The three watchers correspond to the three observation mechanisms described in the README, and each owns its own background thread:
RedisWatcher(redis_watcher.py) — polls Redis directly withLLENfor each(queue, priority)pair on aRepeatTimer. This is the only data source for queue length and is the reason the project is currently coupled to Redis as a broker. Priority queue keys follow Celery's convention (queue\x06\x166etc., see_format_key).WorkerWatcher(worker_watcher.py) — usesapp.control.inspect()to ask workers aboutactive,reserved, andscheduledtasks on aRepeatTimer.EventWatcher(event_watcher.py) — consumes the Celery event stream in a dedicated thread with auto-reconnect (exponential backoff up to 60s). A secondRepeatTimerperiodically callsapp.control.enable_events()so newly started workers begin emitting events. To keep memory bounded under high task churn, it maintains its own UUID→task-nameOrderedDictcache (_TASK_NAMES_CACHE_LIMIT = 100_000) instead of using Celery's built-inStateobject — this is intentional, do not replace it withState.
metrics.py defines a single Collector that is registered with a fresh CollectorRegistry. Its collect() reads from each watcher's public attributes. RedisWatcher and WorkerWatcher update fields atomically by reassignment, so no locking is needed for those; EventWatcher mutates its accumulators in-place from the event handler, so collect() takes defensive snapshots before iterating over them. Each watcher exposes a last_updated_timestamp / last_received_timestamp field; _check_health in the CLI returns 500 from /healthz if any of these is older than --healthz-unhealthy-threshold-sec.
timer.py is a tiny threading.Timer subclass that re-arms forever — used by every watcher.
- All metric names are prefixed
celerymon_and use one of three sub-namespaces (redis_,inspect_,events_) corresponding to the watcher that produces them. New metrics should follow this pattern. - Watchers are constructed via a
create_startedclassmethod that returns the instance with its background thread already running. The constructor itself does not start anything. - Files carry an
SPDX-License-Identifier: MITheader — preserve it when editing or creating files. - Ruff is configured (in
pyproject.toml) with isort enabled andcelerymonas the first-party package.