Files
2026-07-13 12:24:33 +08:00

13 KiB

Event Metadata Contracts

Each EventType has a documented metadata schema. Producers must populate these keys; subscribers may rely on them being present.

For the full list of event types see event.py. For metrics derived from these events see METRICS.md.


L1Manager Events

EventType Metadata keys Types
L1_READ_RESERVED keys list[ObjectKey]
L1_READ_FINISHED keys list[ObjectKey]
L1_WRITE_RESERVED keys list[ObjectKey]
L1_WRITE_FINISHED keys list[ObjectKey]
L1_WRITE_FINISHED_AND_READ_RESERVED keys list[ObjectKey]
L1_KEYS_EVICTED keys list[ObjectKey]
L1_EVICTION_LOOP_TICK usage, watermark, triggered float, float, bool

L1_EVICTION_LOOP_TICK fires once per L1EvictionController.eviction_loop iteration (default ~1Hz). triggered is True when usage >= watermark and the policy ran this cycle, False otherwise.


L1 Failure Events

Published at the caller of the L1 API — not inside L1Manager — so the caller context (during) can be attached without leaking caller identity into the manager. See LM-291 for the health-monitoring rationale.

Producers split keys by (during[, reason]) and publish one event per non-empty bucket; keys is the list of ObjectKeys that failed for that bucket. Subscribers bucket by ObjectKey.model_name to emit per-model counter increments.

EventType Metadata keys Types Vocabulary
L1_ALLOCATION_FAILED during, keys str, list[ObjectKey] during ∈ {l1_store, l2_prefetch}
L1_READ_FAILED during, reason, keys str, str, list[ObjectKey] during ∈ {l2_store, l1_retrieve}; reason ∈ {not_found, write_locked}

L1_READ_FAILED is a post-lookup anomaly event, not a cache-miss event: in MP mode every reserve_read / unsafe_read that raises one of these errors represents a lookup/reserve race or unexpected eviction. The counter should stay near zero in healthy operation.

Producers:

  • L1_ALLOCATION_FAILED(during=l1_store)StorageManager.reserve_write, on L1Error.OUT_OF_MEMORY.
  • L1_ALLOCATION_FAILED(during=l2_prefetch)PrefetchController._transition_to_load_phase, on L1Error.OUT_OF_MEMORY from L1 reservation.
  • L1_READ_FAILED(during=l1_retrieve)StorageManager.read_prefetched_results (unsafe_read failure). not_found = KEY_NOT_EXIST, write_locked = KEY_NOT_READABLE (read lock lost).
  • L1_READ_FAILED(during=l2_store)StoreController._process_new_keys (reserve_read failure on keys that just finished L1 write). not_found = KEY_NOT_EXIST, write_locked = KEY_NOT_READABLE.

StorageManager Events

EventType Metadata keys Types
SM_READ_PREFETCHED succeeded_keys, failed_keys list[ObjectKey], list[ObjectKey]
SM_READ_PREFETCHED_FINISHED succeeded_keys, failed_keys list[ObjectKey], list[ObjectKey]
SM_WRITE_RESERVED succeeded_keys, failed_keys list[ObjectKey], list[ObjectKey]
SM_WRITE_FINISHED succeeded_keys, failed_keys list[ObjectKey], list[ObjectKey]

L2 Store Controller Events

EventType Metadata keys Types
L2_STORE_SUBMITTED adapter_index, task_id, l2_name, key_count, total_bytes, key_count_per_salt int, int, str, int, int, dict[str, int]
L2_STORE_COMPLETED adapter_index, task_id, l2_name, bytes_transferred, succeeded_count, failed_count, key_count_per_salt int, int, str, int, int, int, dict[str, int]

key_count_per_salt maps each cache_salt to its key count, pre-grouped at the emit site so the drain thread iterates tenants (O(T)) not keys (O(N)). Store tasks can batch keys from multiple tenants; key_count_per_salt enables per-tenant attribution on the subscriber side. On the failure path of L2_STORE_COMPLETED, key_count_per_salt is absent (no succeeded keys to attribute).


L2 Prefetch Controller Events

EventType Metadata keys Types
L2_PREFETCH_LOOKUP_SUBMITTED request_id, key_count, adapter_count, key_count_per_salt int, int, int, dict[str, int]
L2_PREFETCH_LOOKUP_COMPLETED request_id, prefix_hit_count int, int
L2_PREFETCH_LOAD_SUBMITTED request_id, key_count, adapter_count, key_count_per_salt int, int, int, dict[str, int]
L2_PREFETCH_LOAD_COMPLETED request_id, loaded_count, failed_count, key_count_per_salt int, int, int, dict[str, int]
L2_LOAD_TASK_SUBMITTED request_id, adapter_index, task_id, l2_name, key_count, total_bytes int, int, int, str, int, int
L2_LOAD_TASK_COMPLETED request_id, adapter_index, task_id, l2_name int, int, int, str

key_count_per_salt on L2_PREFETCH_LOOKUP_SUBMITTED, L2_PREFETCH_LOAD_SUBMITTED, and L2_PREFETCH_LOAD_COMPLETED enables per-tenant metric attribution. key_count_per_salt on load-completed covers only loaded (succeeded) keys.

L2_LOAD_TASK_* events fire once per (request_id, adapter_index) pair — unlike the request-level L2_PREFETCH_LOAD_* events above, which aggregate across adapters. Throughput subscribers that need per-adapter attribution (e.g. L2ThroughputSubscriber) consume these task-level events; key-count counters continue to consume the request-level events.


L2 Eviction Controller Events

EventType Metadata keys Types
L2_KEYS_EVICTED key_count, key_count_per_salt int, dict[str, int]

Published by L2EvictionController._execute_eviction_action after adapter.delete() completes. Only emitted when at least one key was evicted. key_count_per_salt enables per-tenant eviction dashboards.


L2 Failure Events

Health-monitoring event for the L2 prefetch path. See LM-291.

EventType Metadata keys Types Vocabulary
L2_PREFETCH_FAILED reason, keys str, list[ObjectKey] reason ∈ {l1_oom, not_found}

Producers (both in PrefetchController):

  • reason=l1_oom — emitted when reserve_write into L1 returns OUT_OF_MEMORY during the transition-to-load phase. Published in parallel with L1_ALLOCATION_FAILED(during=l2_prefetch).
  • reason=not_found — emitted in _finalize_load for keys reserved in L1 but missing from the adapter's load bitmap (L2 reported the key present at lookup but produced no data).

The third reason serde_failure will be added as an additive, non-breaking extension once the serde PR lands and adapters can distinguish deserialization errors from missing objects. No dashboard migration needed when that happens.


Timeout Events

Cross-component health event. Published by LMCacheTimeoutError.__init__ (see lmcache/v1/mp_observability/errors.py) every time the exception is constructed, gated by is_observability_enabled() so it is a no-op outside the MP server process. The session_id on the Event dataclass correlates the timeout with the originating request when the raise site has it; it is empty otherwise.

EventType Metadata keys Types
TIMEOUT_RAISED message, exception_type, stacktrace str, str, str
  • message — the exception's message string (str(exc)).
  • exception_type — the class name (LMCacheTimeoutError or a subclass).
  • stacktrace — the formatted construction stack (traceback.format_stack() minus the __init__ frame), surfaced as the OTel exception.stacktrace attribute by the tracing subscriber.

Consumed by TimeoutMetricsSubscriber (counter), TimeoutLoggingSubscriber (warning log), and TimeoutTracingSubscriber (OTel span with an exception event + ERROR status).


MP Server Lifecycle Sentinels

CPU-synchronous sentinels published by server.py to bracket request scope. Published via EventBus.publish() (not publish_on_stream) so the drain thread processes them in strict order before any GPU-callback events.

EventType Metadata keys Types Published by / when
MP_REQUEST_START (none) MPServer.handle_request — at request arrival, before any GPU work
MP_STORE_SUBMITTED device str MPServer.store — CPU-synchronous, before the GPU store is enqueued
MP_RETRIEVE_SUBMITTED device str MPServer.retrieve — CPU-synchronous, before the GPU retrieve is enqueued
MP_REQUEST_END (none) MPServer.handle_request — after all CPU work; may precede GPU callbacks

MP Server Events

These events use session_id on the Event dataclass (not in metadata) to correlate START/END pairs.

EventType Metadata keys Types
MP_STORE_START device, engine_id, model_name str, int, str
MP_STORE_END device, stored_count, engine_id, model_name, total_bytes str, int, int, str, int
MP_RETRIEVE_START device, engine_id, model_name str, int, str
MP_RETRIEVE_END device, retrieved_count, engine_id, model_name, cache_salt, total_bytes str, int, int, str, str, int
MP_LOOKUP_PREFETCH_START (none)
MP_LOOKUP_PREFETCH_END found_count, requested_tokens, hit_tokens, model_name, cache_salt int, int, int, str, str
MP_LOOKUP request_id, chunk_hashes, model_name, chunk_size, seq_len, dtypes, shapes str, list[str], str, int, int, list[str], list[list[int]]
MP_VLLM_BLOCK_ALLOCATION instance_id, model_name, records int, str, list[BlockAllocationRecord] (each has req_id: str, new_block_ids: list[int], new_token_ids: list[int])
MP_VLLM_END_SESSION request_id str

MP_LOOKUP_PREFETCH_END metadata

found_count is the contiguous prefix hit at chunk granularity, already divided by world_size at the emit site. requested_tokens and hit_tokens are denormalized token-level counts so subscribers need not know chunk_size:

  • requested_tokens = len(chunk_hashes) * chunk_size on the happy path; 0 on the two early-exit paths in lookup() (no matching GPU context, empty chunk_hashes). Sub-chunk trailing tokens are excluded — they cannot hit at chunk granularity.
  • hit_tokens = found_count * chunk_size.
  • model_name and cache_salt are captured at lookup time from IPCCacheServerKey and surface as OTel attributes on the lmcache_mp.lookup_*_tokens counters so the hit rate can be sliced per model and per tenant / isolation domain on the dashboard. cache_salt may have high cardinality (e.g. one entry per tenant); operators can drop the label at scrape time with a metric_relabel_configs rule if storage cost matters.

Together they drive the lmcache_mp.lookup_*_tokens counters used to compute the L1+L2 token-level hit rate. See L1_L2_HIT_RATE_PLAN.md for the design.


Trace Recording Events

A single unified event used by the @enable_tracing decorator (see trace.md). All instrumented call sites publish the same EventType regardless of which method or layer; the qualname field inside metadata discriminates ops.

EventType Metadata keys Types
TRACE_CALL qualname, args str, dict[str, Any] (codec-encoded; see lmcache.v1.mp_observability.trace.codecs)

Blend Server Lifecycle Sentinels

CPU-synchronous sentinels published by blend_server_v2.py to bracket request scope and guard GPU callback races. Published via EventBus.publish() (not publish_on_stream).

EventType Metadata keys Types Published by / when
CB_REQUEST_START (none) BlendEngineV2.cb_lookup_pre_computed — at request arrival
CB_STORE_PRE_COMPUTED_SUBMITTED instance_id int BlendEngineV2.cb_store_pre_computed — before GPU store enqueue
CB_RETRIEVE_SUBMITTED instance_id int BlendEngineV2.cb_retrieve_pre_computed — before GPU retrieve enqueue
CB_STORE_FINAL_SUBMITTED instance_id int BlendEngineV2.cb_store_final — before GPU store enqueue
CB_REQUEST_END (none) BlendEngineV2.cb_lookup_pre_computed (early return: no matches or no GPU context) or BlendEngineV2.cb_store_final — after SUBMITTED, before GPU work

Blend Server Events

These events use session_id on the Event dataclass (sourced from IPCCacheServerKey.request_id) to correlate START/END pairs.

EventType Metadata keys Types
CB_LOOKUP_START num_tokens int
CB_LOOKUP_END num_tokens, requested_tokens, hit_tokens, fingerprint_hits, storage_hits, stale_chunks, no_gpu_context int, int, int, int, int, int, bool
CB_STORE_PRE_COMPUTED_START instance_id, num_tokens int, int
CB_STORE_PRE_COMPUTED_END instance_id, num_tokens, stored_chunks, success int, int, int, bool
CB_RETRIEVE_START instance_id, num_chunks int, int
CB_RETRIEVE_END instance_id, num_chunks, success int, int, bool
CB_STORE_FINAL_START instance_id, num_tokens int, int
CB_STORE_FINAL_END instance_id, num_tokens, stored_chunks, success int, int, int, bool
CB_FINGERPRINTS_REGISTERED num_chunks, num_tokens int, int
CB_CHUNKS_EVICTED num_chunks int