We are going to have basically 3 types of signals 1. Logs 2. Metrics 3. Traces
We can Say our observability Metrics Have to ansewr Following Questions Scheduler questions Why was this worker selected? Are workers actually balanced? Are we rejecting requests?
Worker questions Is decode slow or stalled? Is KV cache growing uncontrollably? Are we CPU-bound or network-bound?
Coordinator questions Are clients slow? Are buffers filling? Are we dropping sessions?
Metrics Emitted (every 10s):
worker.prefill.latency_ms- Last prefill latencyworker.decode.tokens_per_second- Current decode TPS (5s sliding window)worker.kv_cache.bytes- Total KV cache memoryworker.active_sessions- Current session countworker.decode.failures- Total decode failures
Logs:
session.start- When prefill completes (includes session_id, model, max_tokens, kv_cache_bytes, prefill_latency_ms)session.end- When decode completes (includes session_id, tokens_emitted, decode_duration_ms, decode_tps, reason)decode.early_termination- When decode stops early (includes reason: complete/client_disconnect/error)decode.session_not_found- When decode requested for unknown session
Metrics Tracked:
rejectedNoWorkers- Requests rejected due to no healthy workersrejectedAtCapacity- Requests rejected due to all workers at capacitytotalSelections- Total successful worker selections
Logs:
scheduler.select- Worker selection decision with scoring:selected_worker,selected_score,selected_sessions,selected_kv_bytesselected_session_pct,selected_kv_pct(capacity utilization)candidates_count,runner_up(for debugging balance)
scheduler.reject- Request rejection:reason:no_healthy_workers|all_at_capacityworkers_total,workers_alive,workers_available
Metrics Tracked:
activeSessions- Currently streaming sessionsterminatedByReason- Counts by: complete, client_disconnect, write_timeout, worker_error, buffer_overflowtotalBufferOverflows- Tokens dropped due to buffer limitpeakBufferFillRatio- Highest buffer utilization seenavgWriteLatencyMs- Average client write latencyslowClientCount- Clients exceeding 1s write latency
Logs:
stream.session_start- Session begins (session_id, worker_id, active_sessions)stream.session_end- Session ends (session_id, reason, duration_ms, tokens_received, tokens_written, peak_buffer_size)stream.slow_client- Client write exceeds threshold (session_id, write_latency_ms)stream.buffer_high- Buffer > 80% full (session_id, current_size, max_size, fill_ratio)stream.buffer_overflow- Token dropped (session_id, dropped_seq)stream.sequence_gap- Unexpected sequence number (session_id, expected_seq, actual_seq)stream.write_timeout- Client write deadline exceeded (session_id, token_seq, deadline_ms)
- Scheduler: It is responsible for selecting/rejecting worker, it is aware of load on worker, it can tell us capacity available for system and pressure on sysetm
Scheduler Metrics: scheduler.worker_alive.count scheduler.worker_load.active_sessions scheduler.worker_load.kv_bytes scheduler.request.rejected.count
-
Worker Metrics: worker.prefill.latency_ms worker.decode.tokens_per_second worker.kv_cache.bytes worker.active_sessions worker.decode.failures
-
Coordinator metrics coordinator.stream.buffer_fill_ratio coordinator.client_write_latency_ms coordinator.sessions.active coordinator.sessions.terminated
Every log line must have: 1. request_id 2. session_id 3. worker_id (if applicable)
Trace structure:
Client request → request_id
Prefill span
Decode span
Streaming span
Each span has:
start time
end time
component name
We must detect:
hallucination rate spikes
token throughput drops
abnormal KV growth
Signals
decode TPS sudden drop
prefill latency spike
KV cache growing faster than token count