Diagnostics Module

The diagnostics subsystem provides observer-only snapshots and a local dashboard for live RedisAllocator instances.

Python API

from redis import Redis
from redis_allocator import RedisAllocatorDiagnostics

redis = Redis.from_url("redis://localhost:6379/0", decode_responses=True)
diagnostics = RedisAllocatorDiagnostics(redis, prefix="myapp", suffix="allocator")
snapshot = diagnostics.snapshot()
print(snapshot.to_json(indent=2))

CLI

redis-allocator-diagnose --redis-url redis://localhost:6379/0 --prefix myapp --suffix allocator --json
redis-allocator-diagnose --redis-url redis://localhost:6379/0 --prefix myapp --suffix allocator --interval 1 --port 8765

Atomicity Boundary

The collector reads allocator pool structure and per-pool-key lock state in one Lua EVAL. That atomic snapshot includes:

  • the free-list head pointer

  • the free-list tail pointer

  • all pool hash entries

  • lock existence and TTL for each pool member

Advisory Redis metrics, soft bindings, and orphan-lock samples are collected separately and bounded by the configured scan_limit and sample_limit. Those advisory sections expose scanned and truncated fields so callers can tell when a dashboard refresh did not inspect every matching key.

Health States

healthy

No structural violations and no recommendations.

degraded

The allocator structure is intact, but advisory signals such as orphan locks, permanent locks, low free capacity, stale bindings, or truncated scans need attention.

corrupt

The atomic pool snapshot violates linked-list or lock invariants. Stop writers and capture diagnostics JSON before attempting manual repair.

Orphan-Lock Recovery

Orphan-lock samples are observer-only diagnostics. The diagnostics API and live dashboard never mutate allocator state.

Known orphan locks can be cleaned precisely by one of these paths:

  • call free_keys(key) on the allocator;

  • reintroduce that same key with extend([key]) or assign([... key ...]);

  • delete <prefix>|<suffix>:<key> directly in Redis.

clear() deletes only the pool hash and head/tail pointers. gc() scans pool metadata. Neither operation scans the lock namespace, so neither one repairs orphan locks whose key is already outside the pool. Permanent orphan locks (TTL == -1) remain until one of the explicit cleanup paths above is used.

Class Reference

class redis_allocator.diagnostics.RedisAllocatorDiagnostics[source]

Collect diagnostics for one RedisAllocator prefix/suffix pair.

__init__(redis, prefix, suffix='allocator', shared=False, sample_limit=20, scan_limit=1000)[source]
Parameters:
snapshot(previous=None)[source]

Return one diagnostics snapshot.

Parameters:

previous (AllocatorDiagnosticsSnapshot | None)

Return type:

AllocatorDiagnosticsSnapshot

snapshot_json(previous=None, indent=2)[source]

Return one diagnostics snapshot as JSON.

Parameters:
Return type:

str

class redis_allocator.diagnostics.models.AllocatorDiagnosticsSnapshot[source]

Complete allocator diagnostics snapshot.

timestamp: float
identity: IdentityDiagnostics
health: str
integrity: IntegrityDiagnostics
pool: PoolDiagnostics
bindings: BindingDiagnostics
orphans: OrphanDiagnostics
redis: RedisDiagnostics
pressure: PressureDiagnostics
recommendations: list[Recommendation]
__init__(timestamp, identity, health, integrity=<factory>, pool=<factory>, bindings=<factory>, orphans=<factory>, redis=<factory>, pressure=<factory>, recommendations=<factory>)
Parameters:
Return type:

None

class redis_allocator.diagnostics.models.PoolDiagnostics[source]

Pool capacity and lock counters.

total: int = 0
free: int = 0
allocated_markers: int = 0
locked: int = 0
expired_entries: int = 0
permanent_locks: int = 0
reclaimable: int = 0
__init__(total=0, free=0, allocated_markers=0, locked=0, expired_entries=0, permanent_locks=0, reclaimable=0)
Parameters:
  • total (int)

  • free (int)

  • allocated_markers (int)

  • locked (int)

  • expired_entries (int)

  • permanent_locks (int)

  • reclaimable (int)

Return type:

None

class redis_allocator.diagnostics.models.BindingDiagnostics[source]

Soft-binding counters and bounded samples.

total: int = 0
stale: int = 0
scanned: int = 0
truncated: bool = False
samples: list[dict[str, Any]]
__init__(total=0, stale=0, scanned=0, truncated=False, samples=<factory>)
Parameters:
Return type:

None

class redis_allocator.diagnostics.models.OrphanDiagnostics[source]

Lock keys that no longer belong to the allocator pool.

count: int = 0
scanned: int = 0
truncated: bool = False
samples: list[str]
__init__(count=0, scanned=0, truncated=False, samples=<factory>)
Parameters:
Return type:

None

class redis_allocator.diagnostics.models.PressureDiagnostics[source]

Pressure deltas between two diagnostics snapshots.

interval_seconds: float = 0.0
redis_ops_per_sec: float = 0.0
allocator_keyspace_delta: int = 0
command_deltas: dict[str, int]
__init__(interval_seconds=0.0, redis_ops_per_sec=0.0, allocator_keyspace_delta=0, command_deltas=<factory>)
Parameters:
  • interval_seconds (float)

  • redis_ops_per_sec (float)

  • allocator_keyspace_delta (int)

  • command_deltas (dict[str, int])

Return type:

None