lmcache-cli 0.4.5.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lmcache/__init__.py +84 -0
- lmcache/_version.py +24 -0
- lmcache/cli/__init__.py +1 -0
- lmcache/cli/commands/__init__.py +34 -0
- lmcache/cli/commands/base.py +157 -0
- lmcache/cli/commands/bench/__init__.py +557 -0
- lmcache/cli/commands/bench/engine_bench/__init__.py +1 -0
- lmcache/cli/commands/bench/engine_bench/config.py +245 -0
- lmcache/cli/commands/bench/engine_bench/interactive/__init__.py +274 -0
- lmcache/cli/commands/bench/engine_bench/interactive/config.json +10 -0
- lmcache/cli/commands/bench/engine_bench/interactive/schema.py +352 -0
- lmcache/cli/commands/bench/engine_bench/interactive/state.py +327 -0
- lmcache/cli/commands/bench/engine_bench/interactive/terminal.py +291 -0
- lmcache/cli/commands/bench/engine_bench/progress.py +145 -0
- lmcache/cli/commands/bench/engine_bench/request_sender.py +232 -0
- lmcache/cli/commands/bench/engine_bench/stats.py +275 -0
- lmcache/cli/commands/bench/engine_bench/workloads/__init__.py +153 -0
- lmcache/cli/commands/bench/engine_bench/workloads/base.py +122 -0
- lmcache/cli/commands/bench/engine_bench/workloads/long_doc_permutator.py +435 -0
- lmcache/cli/commands/bench/engine_bench/workloads/long_doc_qa.py +281 -0
- lmcache/cli/commands/bench/engine_bench/workloads/multi_round_chat.py +337 -0
- lmcache/cli/commands/bench/engine_bench/workloads/random_prefill.py +178 -0
- lmcache/cli/commands/describe.py +310 -0
- lmcache/cli/commands/kvcache.py +133 -0
- lmcache/cli/commands/mock.py +75 -0
- lmcache/cli/commands/ping.py +113 -0
- lmcache/cli/commands/query/__init__.py +155 -0
- lmcache/cli/commands/query/prompt.py +134 -0
- lmcache/cli/commands/query/request.py +357 -0
- lmcache/cli/commands/server.py +99 -0
- lmcache/cli/commands/tool/__init__.py +63 -0
- lmcache/cli/commands/tool/cache_simulator.py +113 -0
- lmcache/cli/commands/trace/__init__.py +505 -0
- lmcache/cli/commands/trace/dispatch.py +249 -0
- lmcache/cli/commands/trace/driver.py +372 -0
- lmcache/cli/commands/trace/stats.py +289 -0
- lmcache/cli/documents/lmcache.txt +11 -0
- lmcache/cli/main.py +42 -0
- lmcache/cli/metrics/__init__.py +29 -0
- lmcache/cli/metrics/formatter.py +171 -0
- lmcache/cli/metrics/handler.py +94 -0
- lmcache/cli/metrics/metrics.py +161 -0
- lmcache/cli/metrics/section.py +77 -0
- lmcache/connections.py +173 -0
- lmcache/integration/__init__.py +2 -0
- lmcache/integration/base_service_factory.py +165 -0
- lmcache/integration/request_telemetry/__init__.py +1 -0
- lmcache/integration/request_telemetry/base.py +51 -0
- lmcache/integration/request_telemetry/factory.py +113 -0
- lmcache/integration/request_telemetry/fastapi.py +109 -0
- lmcache/integration/request_telemetry/noop.py +35 -0
- lmcache/integration/sglang/__init__.py +2 -0
- lmcache/integration/sglang/sglang_adapter.py +326 -0
- lmcache/integration/sglang/utils.py +39 -0
- lmcache/integration/vllm/__init__.py +1 -0
- lmcache/integration/vllm/lmcache_connector_v1.py +213 -0
- lmcache/integration/vllm/lmcache_connector_v1_085.py +150 -0
- lmcache/integration/vllm/lmcache_mp_connector_0180.py +1072 -0
- lmcache/integration/vllm/tests/test_mm_hash_utils.py +112 -0
- lmcache/integration/vllm/utils.py +433 -0
- lmcache/integration/vllm/vllm_multi_process_adapter.py +1090 -0
- lmcache/integration/vllm/vllm_service_factory.py +339 -0
- lmcache/integration/vllm/vllm_v1_adapter.py +1713 -0
- lmcache/logging.py +107 -0
- lmcache/native_storage_ops.pyi +230 -0
- lmcache/non_cuda_equivalents.py +1424 -0
- lmcache/observability.py +1958 -0
- lmcache/storage_backend/serde/__init__.py +1 -0
- lmcache/storage_backend/serde/cachegen_basics.py +210 -0
- lmcache/storage_backend/serde/cachegen_decoder.py +207 -0
- lmcache/storage_backend/serde/cachegen_encoder.py +394 -0
- lmcache/storage_backend/serde/serde.py +75 -0
- lmcache/tools/__init__.py +1 -0
- lmcache/tools/cache_simulator/README.md +392 -0
- lmcache/tools/cache_simulator/__init__.py +1 -0
- lmcache/tools/cache_simulator/docs/simulate_example.png +0 -0
- lmcache/tools/cache_simulator/docs/sweep_example.png +0 -0
- lmcache/tools/cache_simulator/gen_bench_dataset.py +360 -0
- lmcache/tools/cache_simulator/lru_cache.py +124 -0
- lmcache/tools/cache_simulator/plot_hit_rate.py +231 -0
- lmcache/tools/cache_simulator/simulator.py +795 -0
- lmcache/tools/controller_benchmark/README.md +161 -0
- lmcache/tools/controller_benchmark/__init__.py +1 -0
- lmcache/tools/controller_benchmark/__main__.py +331 -0
- lmcache/tools/controller_benchmark/benchmark.py +660 -0
- lmcache/tools/controller_benchmark/config.py +44 -0
- lmcache/tools/controller_benchmark/constants.py +10 -0
- lmcache/tools/controller_benchmark/handlers/__init__.py +46 -0
- lmcache/tools/controller_benchmark/handlers/admit.py +52 -0
- lmcache/tools/controller_benchmark/handlers/base.py +47 -0
- lmcache/tools/controller_benchmark/handlers/deregister.py +49 -0
- lmcache/tools/controller_benchmark/handlers/evict.py +52 -0
- lmcache/tools/controller_benchmark/handlers/heartbeat.py +56 -0
- lmcache/tools/controller_benchmark/handlers/p2p_lookup.py +47 -0
- lmcache/tools/controller_benchmark/handlers/register.py +56 -0
- lmcache/tools/mp_status_viewer/__init__.py +1 -0
- lmcache/tools/mp_status_viewer/__main__.py +95 -0
- lmcache/usage_context.py +417 -0
- lmcache/utils.py +665 -0
- lmcache/v1/__init__.py +2 -0
- lmcache/v1/api_server/__init__.py +2 -0
- lmcache/v1/api_server/__main__.py +537 -0
- lmcache/v1/basic_check.py +112 -0
- lmcache/v1/cache_controller/__init__.py +9 -0
- lmcache/v1/cache_controller/commands/__init__.py +15 -0
- lmcache/v1/cache_controller/commands/base.py +35 -0
- lmcache/v1/cache_controller/commands/full_sync.py +49 -0
- lmcache/v1/cache_controller/config.py +176 -0
- lmcache/v1/cache_controller/controller_manager.py +535 -0
- lmcache/v1/cache_controller/controllers/__init__.py +11 -0
- lmcache/v1/cache_controller/controllers/full_sync_tracker.py +473 -0
- lmcache/v1/cache_controller/controllers/kv_controller.py +439 -0
- lmcache/v1/cache_controller/controllers/registration_controller.py +282 -0
- lmcache/v1/cache_controller/executor.py +463 -0
- lmcache/v1/cache_controller/frontend/static/css/style.css +201 -0
- lmcache/v1/cache_controller/frontend/static/img/logo.png +0 -0
- lmcache/v1/cache_controller/frontend/static/index.html +234 -0
- lmcache/v1/cache_controller/frontend/static/js/controller_app.js +660 -0
- lmcache/v1/cache_controller/full_sync_sender.py +475 -0
- lmcache/v1/cache_controller/locks.py +149 -0
- lmcache/v1/cache_controller/message.py +828 -0
- lmcache/v1/cache_controller/observability.py +208 -0
- lmcache/v1/cache_controller/utils.py +679 -0
- lmcache/v1/cache_controller/worker.py +665 -0
- lmcache/v1/cache_engine.py +2058 -0
- lmcache/v1/cache_interface.py +19 -0
- lmcache/v1/check/__init__.py +74 -0
- lmcache/v1/check/check_mode_gen.py +86 -0
- lmcache/v1/check/check_mode_test_l2_adapter.py +284 -0
- lmcache/v1/check/check_mode_test_remote.py +155 -0
- lmcache/v1/check/check_mode_test_storage_manager.py +142 -0
- lmcache/v1/check/utils.py +571 -0
- lmcache/v1/compute/__init__.py +2 -0
- lmcache/v1/compute/attention/__init__.py +0 -0
- lmcache/v1/compute/attention/abstract.py +39 -0
- lmcache/v1/compute/attention/flash_attn.py +129 -0
- lmcache/v1/compute/attention/flash_infer_sparse.py +284 -0
- lmcache/v1/compute/attention/metadata.py +85 -0
- lmcache/v1/compute/attention/utils.py +14 -0
- lmcache/v1/compute/blend/__init__.py +7 -0
- lmcache/v1/compute/blend/blender.py +168 -0
- lmcache/v1/compute/blend/metadata.py +34 -0
- lmcache/v1/compute/blend/utils.py +63 -0
- lmcache/v1/compute/models/__init__.py +0 -0
- lmcache/v1/compute/models/base.py +141 -0
- lmcache/v1/compute/models/llama.py +9 -0
- lmcache/v1/compute/models/qwen3.py +24 -0
- lmcache/v1/compute/models/utils.py +68 -0
- lmcache/v1/compute/positional_encoding.py +199 -0
- lmcache/v1/config.py +848 -0
- lmcache/v1/config_base.py +848 -0
- lmcache/v1/distributed/api.py +248 -0
- lmcache/v1/distributed/config.py +321 -0
- lmcache/v1/distributed/error.py +64 -0
- lmcache/v1/distributed/eviction.py +192 -0
- lmcache/v1/distributed/eviction_policy/__init__.py +21 -0
- lmcache/v1/distributed/eviction_policy/factory.py +27 -0
- lmcache/v1/distributed/eviction_policy/lru.py +244 -0
- lmcache/v1/distributed/eviction_policy/noop.py +50 -0
- lmcache/v1/distributed/internal_api.py +170 -0
- lmcache/v1/distributed/l1_manager.py +835 -0
- lmcache/v1/distributed/l2_adapters/__init__.py +67 -0
- lmcache/v1/distributed/l2_adapters/base.py +360 -0
- lmcache/v1/distributed/l2_adapters/config.py +385 -0
- lmcache/v1/distributed/l2_adapters/factory.py +205 -0
- lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +747 -0
- lmcache/v1/distributed/l2_adapters/fs_native_l2_adapter.py +167 -0
- lmcache/v1/distributed/l2_adapters/mock_l2_adapter.py +516 -0
- lmcache/v1/distributed/l2_adapters/mooncake_store_l2_adapter.py +135 -0
- lmcache/v1/distributed/l2_adapters/native_connector_l2_adapter.py +468 -0
- lmcache/v1/distributed/l2_adapters/native_plugin_l2_adapter.py +199 -0
- lmcache/v1/distributed/l2_adapters/nixl_store_dynamic_l2_adapter.py +831 -0
- lmcache/v1/distributed/l2_adapters/nixl_store_l2_adapter.py +983 -0
- lmcache/v1/distributed/l2_adapters/plugin_l2_adapter.py +210 -0
- lmcache/v1/distributed/l2_adapters/resp_l2_adapter.py +176 -0
- lmcache/v1/distributed/memory_manager.py +179 -0
- lmcache/v1/distributed/storage_controller.py +39 -0
- lmcache/v1/distributed/storage_controllers/__init__.py +43 -0
- lmcache/v1/distributed/storage_controllers/eviction_controller.py +242 -0
- lmcache/v1/distributed/storage_controllers/prefetch_controller.py +830 -0
- lmcache/v1/distributed/storage_controllers/prefetch_policy.py +193 -0
- lmcache/v1/distributed/storage_controllers/store_controller.py +452 -0
- lmcache/v1/distributed/storage_controllers/store_policy.py +213 -0
- lmcache/v1/distributed/storage_manager.py +532 -0
- lmcache/v1/event_manager.py +145 -0
- lmcache/v1/exceptions/__init__.py +16 -0
- lmcache/v1/gpu_connector/__init__.py +126 -0
- lmcache/v1/gpu_connector/gpu_connectors.py +1906 -0
- lmcache/v1/gpu_connector/gpu_ops.py +85 -0
- lmcache/v1/gpu_connector/hpu_connector.py +326 -0
- lmcache/v1/gpu_connector/mock_gpu_connector.py +67 -0
- lmcache/v1/gpu_connector/utils.py +890 -0
- lmcache/v1/gpu_connector/xpu_connectors.py +916 -0
- lmcache/v1/health_monitor/__init__.py +1 -0
- lmcache/v1/health_monitor/base.py +587 -0
- lmcache/v1/health_monitor/checks/__init__.py +1 -0
- lmcache/v1/health_monitor/checks/remote_backend_check.py +304 -0
- lmcache/v1/health_monitor/constants.py +36 -0
- lmcache/v1/internal_api_server/__init__.py +0 -0
- lmcache/v1/internal_api_server/api_registry.py +59 -0
- lmcache/v1/internal_api_server/api_server.py +120 -0
- lmcache/v1/internal_api_server/common/__init__.py +1 -0
- lmcache/v1/internal_api_server/common/env_api.py +22 -0
- lmcache/v1/internal_api_server/common/loglevel_api.py +57 -0
- lmcache/v1/internal_api_server/common/metrics_api.py +29 -0
- lmcache/v1/internal_api_server/common/periodic_thread_api.py +138 -0
- lmcache/v1/internal_api_server/common/run_script_api.py +73 -0
- lmcache/v1/internal_api_server/common/thread_api.py +63 -0
- lmcache/v1/internal_api_server/controller/__init__.py +1 -0
- lmcache/v1/internal_api_server/controller/key_stats_api.py +81 -0
- lmcache/v1/internal_api_server/controller/worker_info_api.py +136 -0
- lmcache/v1/internal_api_server/utils.py +43 -0
- lmcache/v1/internal_api_server/vllm/__init__.py +1 -0
- lmcache/v1/internal_api_server/vllm/backend_api.py +221 -0
- lmcache/v1/internal_api_server/vllm/bypass_api.py +204 -0
- lmcache/v1/internal_api_server/vllm/cache_api.py +895 -0
- lmcache/v1/internal_api_server/vllm/chunk_statistics_api.py +141 -0
- lmcache/v1/internal_api_server/vllm/conf_api.py +147 -0
- lmcache/v1/internal_api_server/vllm/freeze_api.py +172 -0
- lmcache/v1/internal_api_server/vllm/hot_cache_api.py +184 -0
- lmcache/v1/internal_api_server/vllm/inference_api.py +65 -0
- lmcache/v1/internal_api_server/vllm/load_fs_chunks_api.py +320 -0
- lmcache/v1/internal_api_server/vllm/lookup_api.py +145 -0
- lmcache/v1/internal_api_server/vllm/version_api.py +25 -0
- lmcache/v1/kv_layer_groups.py +267 -0
- lmcache/v1/lazy_memory_allocator.py +284 -0
- lmcache/v1/lookup_client/__init__.py +25 -0
- lmcache/v1/lookup_client/abstract_client.py +77 -0
- lmcache/v1/lookup_client/async_lookup_message.py +50 -0
- lmcache/v1/lookup_client/chunk_statistics_lookup_client.py +200 -0
- lmcache/v1/lookup_client/factory.py +251 -0
- lmcache/v1/lookup_client/hit_limit_lookup_client.py +86 -0
- lmcache/v1/lookup_client/lmcache_async_lookup_client.py +407 -0
- lmcache/v1/lookup_client/lmcache_lookup_client.py +285 -0
- lmcache/v1/lookup_client/lmcache_lookup_client_bypass.py +99 -0
- lmcache/v1/lookup_client/mooncake_lookup_client.py +87 -0
- lmcache/v1/lookup_client/record_strategies/__init__.py +77 -0
- lmcache/v1/lookup_client/record_strategies/base.py +327 -0
- lmcache/v1/lookup_client/record_strategies/file_hash.py +130 -0
- lmcache/v1/lookup_client/record_strategies/memory_bloom_filter.py +81 -0
- lmcache/v1/manager.py +539 -0
- lmcache/v1/memory_management.py +2619 -0
- lmcache/v1/metadata.py +114 -0
- lmcache/v1/mp_observability/AGENTS.override.md +21 -0
- lmcache/v1/mp_observability/README.md +204 -0
- lmcache/v1/mp_observability/config.py +340 -0
- lmcache/v1/mp_observability/event.py +100 -0
- lmcache/v1/mp_observability/event_bus.py +313 -0
- lmcache/v1/mp_observability/otel_init.py +145 -0
- lmcache/v1/mp_observability/subscribers/__init__.py +28 -0
- lmcache/v1/mp_observability/subscribers/logging/__init__.py +19 -0
- lmcache/v1/mp_observability/subscribers/logging/l1.py +56 -0
- lmcache/v1/mp_observability/subscribers/logging/l2.py +73 -0
- lmcache/v1/mp_observability/subscribers/logging/lookup_hash.py +209 -0
- lmcache/v1/mp_observability/subscribers/logging/mp_server.py +90 -0
- lmcache/v1/mp_observability/subscribers/logging/sm.py +59 -0
- lmcache/v1/mp_observability/subscribers/metrics/__init__.py +20 -0
- lmcache/v1/mp_observability/subscribers/metrics/l0_lifecycle.py +290 -0
- lmcache/v1/mp_observability/subscribers/metrics/l1.py +55 -0
- lmcache/v1/mp_observability/subscribers/metrics/l1_lifecycle.py +166 -0
- lmcache/v1/mp_observability/subscribers/metrics/l2.py +121 -0
- lmcache/v1/mp_observability/subscribers/metrics/sm.py +69 -0
- lmcache/v1/mp_observability/subscribers/tracing/__init__.py +12 -0
- lmcache/v1/mp_observability/subscribers/tracing/mp_server.py +333 -0
- lmcache/v1/mp_observability/subscribers/tracing/span_registry.py +148 -0
- lmcache/v1/mp_observability/trace/__init__.py +50 -0
- lmcache/v1/mp_observability/trace/codecs.py +255 -0
- lmcache/v1/mp_observability/trace/decorator.py +147 -0
- lmcache/v1/mp_observability/trace/format.py +132 -0
- lmcache/v1/mp_observability/trace/lifecycle.py +83 -0
- lmcache/v1/mp_observability/trace/reader.py +167 -0
- lmcache/v1/mp_observability/trace/recorder.py +300 -0
- lmcache/v1/multiprocess/__init__.py +0 -0
- lmcache/v1/multiprocess/affinity_pool.py +102 -0
- lmcache/v1/multiprocess/blend_server_v2.py +891 -0
- lmcache/v1/multiprocess/config.py +253 -0
- lmcache/v1/multiprocess/custom_types.py +281 -0
- lmcache/v1/multiprocess/futures.py +194 -0
- lmcache/v1/multiprocess/gpu_context.py +511 -0
- lmcache/v1/multiprocess/http_server.py +235 -0
- lmcache/v1/multiprocess/mp_runtime_plugin_launcher.py +130 -0
- lmcache/v1/multiprocess/mq.py +732 -0
- lmcache/v1/multiprocess/protocol.py +86 -0
- lmcache/v1/multiprocess/protocols/README.md +213 -0
- lmcache/v1/multiprocess/protocols/__init__.py +127 -0
- lmcache/v1/multiprocess/protocols/base.py +89 -0
- lmcache/v1/multiprocess/protocols/blend.py +109 -0
- lmcache/v1/multiprocess/protocols/blend_v2.py +57 -0
- lmcache/v1/multiprocess/protocols/controller.py +53 -0
- lmcache/v1/multiprocess/protocols/debug.py +34 -0
- lmcache/v1/multiprocess/protocols/engine.py +146 -0
- lmcache/v1/multiprocess/protocols/observability.py +39 -0
- lmcache/v1/multiprocess/server.py +1134 -0
- lmcache/v1/multiprocess/session.py +190 -0
- lmcache/v1/multiprocess/token_hasher.py +441 -0
- lmcache/v1/offload_server/__init__.py +17 -0
- lmcache/v1/offload_server/abstract_server.py +37 -0
- lmcache/v1/offload_server/message.py +30 -0
- lmcache/v1/offload_server/zmq_server.py +122 -0
- lmcache/v1/periodic_thread.py +579 -0
- lmcache/v1/pin_monitor.py +246 -0
- lmcache/v1/plugin/__init__.py +0 -0
- lmcache/v1/plugin/runtime_plugin_launcher.py +211 -0
- lmcache/v1/protocol.py +317 -0
- lmcache/v1/rpc/__init__.py +17 -0
- lmcache/v1/rpc/transport.py +105 -0
- lmcache/v1/rpc/zmq_transport.py +213 -0
- lmcache/v1/rpc_utils.py +165 -0
- lmcache/v1/server/__init__.py +2 -0
- lmcache/v1/server/__main__.py +170 -0
- lmcache/v1/server/storage_backend/__init__.py +21 -0
- lmcache/v1/server/storage_backend/abstract_backend.py +80 -0
- lmcache/v1/server/storage_backend/local_backend.py +75 -0
- lmcache/v1/server/utils.py +21 -0
- lmcache/v1/standalone/__init__.py +1 -0
- lmcache/v1/standalone/__main__.py +583 -0
- lmcache/v1/standalone/manager.py +80 -0
- lmcache/v1/standalone/standalone_service_factory.py +86 -0
- lmcache/v1/storage_backend/__init__.py +313 -0
- lmcache/v1/storage_backend/abstract_backend.py +445 -0
- lmcache/v1/storage_backend/audit_backend.py +233 -0
- lmcache/v1/storage_backend/batched_message_sender.py +222 -0
- lmcache/v1/storage_backend/cache_policy/__init__.py +45 -0
- lmcache/v1/storage_backend/cache_policy/base_policy.py +87 -0
- lmcache/v1/storage_backend/cache_policy/fifo.py +58 -0
- lmcache/v1/storage_backend/cache_policy/lfu.py +105 -0
- lmcache/v1/storage_backend/cache_policy/lru.py +81 -0
- lmcache/v1/storage_backend/cache_policy/mru.py +61 -0
- lmcache/v1/storage_backend/connector/__init__.py +443 -0
- lmcache/v1/storage_backend/connector/audit_adapter.py +77 -0
- lmcache/v1/storage_backend/connector/audit_connector.py +320 -0
- lmcache/v1/storage_backend/connector/base_connector.py +379 -0
- lmcache/v1/storage_backend/connector/blackhole_adapter.py +21 -0
- lmcache/v1/storage_backend/connector/blackhole_connector.py +37 -0
- lmcache/v1/storage_backend/connector/eic_adapter.py +31 -0
- lmcache/v1/storage_backend/connector/eic_connector.py +757 -0
- lmcache/v1/storage_backend/connector/external_adapter.py +79 -0
- lmcache/v1/storage_backend/connector/fs_adapter.py +51 -0
- lmcache/v1/storage_backend/connector/fs_connector.py +403 -0
- lmcache/v1/storage_backend/connector/infinistore_adapter.py +56 -0
- lmcache/v1/storage_backend/connector/infinistore_connector.py +177 -0
- lmcache/v1/storage_backend/connector/instrumented_connector.py +219 -0
- lmcache/v1/storage_backend/connector/lm_adapter.py +31 -0
- lmcache/v1/storage_backend/connector/lm_connector.py +176 -0
- lmcache/v1/storage_backend/connector/mock_adapter.py +57 -0
- lmcache/v1/storage_backend/connector/mock_connector.py +349 -0
- lmcache/v1/storage_backend/connector/mooncakestore_adapter.py +43 -0
- lmcache/v1/storage_backend/connector/mooncakestore_connector.py +614 -0
- lmcache/v1/storage_backend/connector/redis_adapter.py +181 -0
- lmcache/v1/storage_backend/connector/redis_connector.py +828 -0
- lmcache/v1/storage_backend/connector/s3_adapter.py +59 -0
- lmcache/v1/storage_backend/connector/s3_connector.py +699 -0
- lmcache/v1/storage_backend/connector/sagemaker_hyperpod_adapter.py +233 -0
- lmcache/v1/storage_backend/connector/sagemaker_hyperpod_connector.py +987 -0
- lmcache/v1/storage_backend/connector/valkey_adapter.py +114 -0
- lmcache/v1/storage_backend/connector/valkey_connector.py +627 -0
- lmcache/v1/storage_backend/gds_backend.py +1199 -0
- lmcache/v1/storage_backend/job_executor/__init__.py +0 -0
- lmcache/v1/storage_backend/job_executor/base_executor.py +34 -0
- lmcache/v1/storage_backend/job_executor/pq_executor.py +235 -0
- lmcache/v1/storage_backend/local_cpu_backend.py +810 -0
- lmcache/v1/storage_backend/local_disk_backend.py +656 -0
- lmcache/v1/storage_backend/maru_backend.py +734 -0
- lmcache/v1/storage_backend/naive_serde/__init__.py +50 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_basics.py +133 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_decoder.py +135 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_encoder.py +83 -0
- lmcache/v1/storage_backend/naive_serde/kivi_serde.py +22 -0
- lmcache/v1/storage_backend/naive_serde/naive_serde.py +18 -0
- lmcache/v1/storage_backend/naive_serde/serde.py +37 -0
- lmcache/v1/storage_backend/native_clients/connector_client_base.py +165 -0
- lmcache/v1/storage_backend/native_clients/resp_client.py +35 -0
- lmcache/v1/storage_backend/nixl_storage_backend.py +1400 -0
- lmcache/v1/storage_backend/p2p_backend.py +788 -0
- lmcache/v1/storage_backend/path_sharder.py +117 -0
- lmcache/v1/storage_backend/pd_backend.py +646 -0
- lmcache/v1/storage_backend/plugins/dax_backend.py +1443 -0
- lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py +1361 -0
- lmcache/v1/storage_backend/remote_backend.py +624 -0
- lmcache/v1/storage_backend/resp_client.py +227 -0
- lmcache/v1/storage_backend/storage_backend_listener.py +19 -0
- lmcache/v1/storage_backend/storage_manager.py +1352 -0
- lmcache/v1/system_detection.py +110 -0
- lmcache/v1/token_database.py +551 -0
- lmcache/v1/transfer_channel/__init__.py +83 -0
- lmcache/v1/transfer_channel/abstract.py +285 -0
- lmcache/v1/transfer_channel/mock_memory_channel.py +156 -0
- lmcache/v1/transfer_channel/nixl_channel.py +639 -0
- lmcache/v1/transfer_channel/py_socket_channel.py +260 -0
- lmcache/v1/transfer_channel/transfer_utils.py +63 -0
- lmcache/v1/utils/__init__.py +1 -0
- lmcache/v1/utils/bloom_filter.py +109 -0
- lmcache/v1/utils/cache_utils.py +125 -0
- lmcache_cli-0.4.5.dev0.dist-info/METADATA +185 -0
- lmcache_cli-0.4.5.dev0.dist-info/RECORD +399 -0
- lmcache_cli-0.4.5.dev0.dist-info/WHEEL +5 -0
- lmcache_cli-0.4.5.dev0.dist-info/entry_points.txt +2 -0
- lmcache_cli-0.4.5.dev0.dist-info/licenses/LICENSE +201 -0
- lmcache_cli-0.4.5.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
"""Dispatcher mapping recorded ``qualname`` strings to live callables.
|
|
4
|
+
|
|
5
|
+
The recorder writes one :class:`~lmcache.v1.mp_observability.trace.format.Record`
|
|
6
|
+
per decorated call, tagged by the function's fully-qualified name.
|
|
7
|
+
The dispatcher translates those strings back into concrete calls on a
|
|
8
|
+
live :class:`~lmcache.v1.distributed.storage_manager.StorageManager`.
|
|
9
|
+
|
|
10
|
+
Adding support for a new traced operation is a two-line change: put
|
|
11
|
+
``@enable_tracing`` on the function, then register a handler here with
|
|
12
|
+
a matching ``qualname``. No per-op schemas, no new event types.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Future
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
# Standard
|
|
19
|
+
from collections import deque
|
|
20
|
+
from contextlib import AbstractContextManager
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import Any, Callable
|
|
23
|
+
|
|
24
|
+
# First Party
|
|
25
|
+
from lmcache.logging import init_logger
|
|
26
|
+
from lmcache.v1.distributed.api import ObjectKey
|
|
27
|
+
from lmcache.v1.distributed.storage_manager import StorageManager
|
|
28
|
+
|
|
29
|
+
logger = init_logger(__name__)
|
|
30
|
+
|
|
31
|
+
#: Fully-qualified name of the ``StorageManager`` class. Used to
|
|
32
|
+
#: build the qualnames of all its traced methods. Kept as a constant
|
|
33
|
+
#: so tests and dispatcher registrations stay in lock-step if the class
|
|
34
|
+
#: is ever renamed or moved.
|
|
35
|
+
_SM_PREFIX = "lmcache.v1.distributed.storage_manager.StorageManager"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class ReplayContext:
|
|
40
|
+
"""State carried across dispatcher invocations during one replay.
|
|
41
|
+
|
|
42
|
+
The driver creates exactly one ``ReplayContext`` per trace file,
|
|
43
|
+
hands it to every dispatched handler, and closes the StorageManager
|
|
44
|
+
when replay finishes.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
sm: The live StorageManager that receives replayed calls.
|
|
48
|
+
open_read_contexts: FIFO queue of
|
|
49
|
+
``read_prefetched_results`` contexts entered but not yet
|
|
50
|
+
exited. Matching ``__enter__``/``__exit__`` records are
|
|
51
|
+
popped in the order they were entered for the same
|
|
52
|
+
``keys`` tuple. A dict-of-deques keyed on
|
|
53
|
+
``tuple(keys)`` supports interleaved contexts across
|
|
54
|
+
different key sets.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
sm: StorageManager
|
|
58
|
+
open_read_contexts: dict[tuple[ObjectKey, ...], deque[AbstractContextManager]] = (
|
|
59
|
+
field(default_factory=dict)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
#: Type of a dispatcher handler: takes a :class:`ReplayContext` and an
|
|
64
|
+
#: already-decoded ``args`` dict (keys = parameter names, values =
|
|
65
|
+
#: native Python values restored by
|
|
66
|
+
#: :mod:`lmcache.v1.mp_observability.trace.codecs`). Handlers return
|
|
67
|
+
#: nothing; any return value from the live call is discarded.
|
|
68
|
+
Handler = Callable[[ReplayContext, dict[str, Any]], None]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class CallDispatcher:
|
|
72
|
+
"""Registry mapping recorded qualnames to replay handlers.
|
|
73
|
+
|
|
74
|
+
Handlers are plain callables — no per-op subclass hierarchy. The
|
|
75
|
+
default factory :func:`build_default_dispatcher` populates the
|
|
76
|
+
registry with every v1 ``StorageManager`` operation.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(self) -> None:
|
|
80
|
+
self._handlers: dict[str, Handler] = {}
|
|
81
|
+
|
|
82
|
+
def register(self, qualname: str, handler: Handler) -> None:
|
|
83
|
+
"""Register a handler for *qualname*.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
qualname: Fully-qualified call-site name exactly matching
|
|
87
|
+
the ``qualname`` field written by the recorder.
|
|
88
|
+
handler: Callable invoked on each matching record.
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
ValueError: If a handler is already registered for
|
|
92
|
+
*qualname*.
|
|
93
|
+
"""
|
|
94
|
+
if qualname in self._handlers:
|
|
95
|
+
raise ValueError(f"handler already registered for {qualname!r}")
|
|
96
|
+
self._handlers[qualname] = handler
|
|
97
|
+
|
|
98
|
+
def has(self, qualname: str) -> bool:
|
|
99
|
+
"""Return ``True`` if a handler is registered for *qualname*."""
|
|
100
|
+
return qualname in self._handlers
|
|
101
|
+
|
|
102
|
+
def registered_qualnames(self) -> list[str]:
|
|
103
|
+
"""Return the list of currently registered qualnames.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
A new list of qualname strings, suitable for inspection or
|
|
107
|
+
assertion in tests.
|
|
108
|
+
"""
|
|
109
|
+
return list(self._handlers)
|
|
110
|
+
|
|
111
|
+
def dispatch(
|
|
112
|
+
self,
|
|
113
|
+
qualname: str,
|
|
114
|
+
context: ReplayContext,
|
|
115
|
+
args: dict[str, Any],
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Invoke the handler for *qualname*.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
qualname: The recorded qualname.
|
|
121
|
+
context: Replay context passed through to the handler.
|
|
122
|
+
args: Decoded argument dict for the call.
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
KeyError: If no handler is registered for *qualname*. The
|
|
126
|
+
driver catches this and logs a warning so unknown
|
|
127
|
+
qualnames (e.g. from a future trace level) do not stop
|
|
128
|
+
replay.
|
|
129
|
+
"""
|
|
130
|
+
handler = self._handlers.get(qualname)
|
|
131
|
+
if handler is None:
|
|
132
|
+
raise KeyError(qualname)
|
|
133
|
+
handler(context, args)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# Default handlers for v1 StorageManager operations
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _call_sm_method(method_name: str) -> Handler:
|
|
142
|
+
"""Build a handler that forwards a record to ``sm.<method_name>``.
|
|
143
|
+
|
|
144
|
+
The returned callable invokes ``getattr(ctx.sm, method_name)(**args)``
|
|
145
|
+
and discards the result. Used for every "plain" traced method on
|
|
146
|
+
StorageManager — ``reserve_write``, ``finish_write``,
|
|
147
|
+
``submit_prefetch_task``, ``finish_read_prefetched``.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
method_name: Attribute name on the live StorageManager.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
A :data:`Handler` closure.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def _handler(ctx: ReplayContext, args: dict[str, Any]) -> None:
|
|
157
|
+
method = getattr(ctx.sm, method_name)
|
|
158
|
+
method(**args)
|
|
159
|
+
|
|
160
|
+
_handler.__name__ = f"_call_sm_{method_name}"
|
|
161
|
+
return _handler
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _enter_read_prefetched(ctx: ReplayContext, args: dict[str, Any]) -> None:
|
|
165
|
+
"""Handle a ``read_prefetched_results.__enter__`` record.
|
|
166
|
+
|
|
167
|
+
Enters the live context manager and stashes it under
|
|
168
|
+
``tuple(keys)``. The matching ``__exit__`` handler pops the top
|
|
169
|
+
entry for that key tuple, preserving FIFO order when multiple
|
|
170
|
+
contexts are simultaneously open for identical key lists.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
ctx: Active replay context.
|
|
174
|
+
args: Decoded record arguments. Must contain ``"keys"``.
|
|
175
|
+
"""
|
|
176
|
+
keys = args["keys"]
|
|
177
|
+
cm = ctx.sm.read_prefetched_results(keys)
|
|
178
|
+
cm.__enter__()
|
|
179
|
+
key_tuple = tuple(keys)
|
|
180
|
+
ctx.open_read_contexts.setdefault(key_tuple, deque()).append(cm)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _exit_read_prefetched(ctx: ReplayContext, args: dict[str, Any]) -> None:
|
|
184
|
+
"""Handle a ``read_prefetched_results.__exit__`` record.
|
|
185
|
+
|
|
186
|
+
Pops and exits the oldest context opened for ``tuple(keys)``. If
|
|
187
|
+
no matching open context exists — typically because the trace was
|
|
188
|
+
truncated between the enter and exit events — logs a warning and
|
|
189
|
+
continues so replay does not abort.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
ctx: Active replay context.
|
|
193
|
+
args: Decoded record arguments. Must contain ``"keys"``.
|
|
194
|
+
"""
|
|
195
|
+
keys = args["keys"]
|
|
196
|
+
key_tuple = tuple(keys)
|
|
197
|
+
pending = ctx.open_read_contexts.get(key_tuple)
|
|
198
|
+
if not pending:
|
|
199
|
+
logger.warning(
|
|
200
|
+
"trace replay: read_prefetched_results.__exit__ with no "
|
|
201
|
+
"matching __enter__ (keys=%d); ignoring",
|
|
202
|
+
len(keys),
|
|
203
|
+
)
|
|
204
|
+
return
|
|
205
|
+
cm = pending.popleft()
|
|
206
|
+
if not pending:
|
|
207
|
+
del ctx.open_read_contexts[key_tuple]
|
|
208
|
+
# Pass a clean exit — replay does not reproduce caller-side
|
|
209
|
+
# exceptions. The context manager's ``finally`` block runs
|
|
210
|
+
# regardless, so read locks are released.
|
|
211
|
+
cm.__exit__(None, None, None)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def build_default_dispatcher() -> CallDispatcher:
|
|
215
|
+
"""Return a :class:`CallDispatcher` populated with v1 handlers.
|
|
216
|
+
|
|
217
|
+
Covers every qualname the storage-level recorder emits:
|
|
218
|
+
|
|
219
|
+
* ``StorageManager.reserve_write``
|
|
220
|
+
* ``StorageManager.finish_write``
|
|
221
|
+
* ``StorageManager.submit_prefetch_task``
|
|
222
|
+
* ``StorageManager.finish_read_prefetched``
|
|
223
|
+
* ``StorageManager.read_prefetched_results.__enter__``
|
|
224
|
+
* ``StorageManager.read_prefetched_results.__exit__``
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
A ready-to-use dispatcher. Callers may further register
|
|
228
|
+
additional handlers on it for future trace levels.
|
|
229
|
+
"""
|
|
230
|
+
dispatcher = CallDispatcher()
|
|
231
|
+
for method_name in (
|
|
232
|
+
"reserve_write",
|
|
233
|
+
"finish_write",
|
|
234
|
+
"submit_prefetch_task",
|
|
235
|
+
"finish_read_prefetched",
|
|
236
|
+
):
|
|
237
|
+
dispatcher.register(
|
|
238
|
+
f"{_SM_PREFIX}.{method_name}",
|
|
239
|
+
_call_sm_method(method_name),
|
|
240
|
+
)
|
|
241
|
+
dispatcher.register(
|
|
242
|
+
f"{_SM_PREFIX}.read_prefetched_results.__enter__",
|
|
243
|
+
_enter_read_prefetched,
|
|
244
|
+
)
|
|
245
|
+
dispatcher.register(
|
|
246
|
+
f"{_SM_PREFIX}.read_prefetched_results.__exit__",
|
|
247
|
+
_exit_read_prefetched,
|
|
248
|
+
)
|
|
249
|
+
return dispatcher
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
"""High-level replay driver for storage-level traces.
|
|
4
|
+
|
|
5
|
+
Usage::
|
|
6
|
+
|
|
7
|
+
driver = StorageReplayDriver(sm_config, trace_path)
|
|
8
|
+
result = driver.run()
|
|
9
|
+
driver.close()
|
|
10
|
+
|
|
11
|
+
The driver:
|
|
12
|
+
|
|
13
|
+
1. Opens the trace file via :class:`TraceReader`.
|
|
14
|
+
2. Constructs a fresh :class:`StorageManager` from the supplied
|
|
15
|
+
:class:`StorageManagerConfig`. Notably, the replay-side config is
|
|
16
|
+
chosen by the *caller*, not copied from the trace's header. This
|
|
17
|
+
lets the same trace exercise different L1/L2 configurations —
|
|
18
|
+
e.g., record with a Redis L2 adapter and replay with a
|
|
19
|
+
local-filesystem adapter.
|
|
20
|
+
3. Iterates records, decodes their argument dicts via the trace
|
|
21
|
+
codec registry, and dispatches each to a live StorageManager call
|
|
22
|
+
through a :class:`CallDispatcher`. Each dispatch is aligned to
|
|
23
|
+
the recorded ``t_mono`` offset via ``time.sleep`` — the replay
|
|
24
|
+
never runs ahead of the recording. Dispatching as-fast-as-
|
|
25
|
+
possible is unsafe because reads and writes are async in
|
|
26
|
+
``StorageManager`` and carry cross-call dependencies; collapsing
|
|
27
|
+
the recorded gaps races the async queues.
|
|
28
|
+
4. Records per-qualname timings into a :class:`ReplayStatsCollector`.
|
|
29
|
+
|
|
30
|
+
Replay is deliberately single-threaded: the recorder captures calls
|
|
31
|
+
in the order the EventBus drained them, which is already a
|
|
32
|
+
linearization of the concurrent production calls. Replaying
|
|
33
|
+
in that same order preserves the observed interleaving without
|
|
34
|
+
needing to reconstruct thread identities.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Future
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
# Standard
|
|
41
|
+
from dataclasses import dataclass
|
|
42
|
+
from typing import TYPE_CHECKING, Callable
|
|
43
|
+
import hashlib
|
|
44
|
+
import json
|
|
45
|
+
import time
|
|
46
|
+
|
|
47
|
+
# First Party
|
|
48
|
+
from lmcache.cli.commands.trace.dispatch import (
|
|
49
|
+
CallDispatcher,
|
|
50
|
+
ReplayContext,
|
|
51
|
+
build_default_dispatcher,
|
|
52
|
+
)
|
|
53
|
+
from lmcache.cli.commands.trace.stats import ReplayStatsCollector
|
|
54
|
+
from lmcache.logging import init_logger
|
|
55
|
+
from lmcache.v1.distributed.config import StorageManagerConfig
|
|
56
|
+
from lmcache.v1.distributed.storage_manager import StorageManager
|
|
57
|
+
from lmcache.v1.mp_observability.config import (
|
|
58
|
+
ObservabilityConfig,
|
|
59
|
+
init_observability,
|
|
60
|
+
)
|
|
61
|
+
from lmcache.v1.mp_observability.trace import codecs
|
|
62
|
+
from lmcache.v1.mp_observability.trace.reader import TraceReader
|
|
63
|
+
from lmcache.v1.mp_observability.trace.recorder import safe_storage_config_dict
|
|
64
|
+
|
|
65
|
+
if TYPE_CHECKING:
|
|
66
|
+
# First Party
|
|
67
|
+
from lmcache.v1.mp_observability.event_bus import EventBus
|
|
68
|
+
|
|
69
|
+
logger = init_logger(__name__)
|
|
70
|
+
|
|
71
|
+
#: Default :class:`ObservabilityConfig` for replay sessions.
|
|
72
|
+
#:
|
|
73
|
+
#: Enables the EventBus and its logging subscribers so users can see
|
|
74
|
+
#: SM/L1/L2 log output during replay. Metrics and tracing are off:
|
|
75
|
+
#: the OTel / Prometheus pipelines bind a port, which would collide
|
|
76
|
+
#: across concurrent replays. Declared at module scope to avoid
|
|
77
|
+
#: Ruff B008 (mutable default argument).
|
|
78
|
+
DEFAULT_REPLAY_OBS_CONFIG: ObservabilityConfig = ObservabilityConfig(
|
|
79
|
+
enabled=True,
|
|
80
|
+
metrics_enabled=False,
|
|
81
|
+
logging_enabled=True,
|
|
82
|
+
tracing_enabled=False,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class ReplayResult:
|
|
88
|
+
"""Summary returned from :meth:`StorageReplayDriver.run`.
|
|
89
|
+
|
|
90
|
+
Attributes:
|
|
91
|
+
records_replayed: Successful dispatches.
|
|
92
|
+
records_skipped: Records with no registered handler (likely
|
|
93
|
+
from a newer trace level).
|
|
94
|
+
records_failed: Records whose handler raised.
|
|
95
|
+
stats: The per-qualname timing collector. Exposed so the
|
|
96
|
+
caller can export CSV/JSON or inspect individual
|
|
97
|
+
percentiles.
|
|
98
|
+
header_level: ``level`` field read from the trace header.
|
|
99
|
+
header_digest: ``sm_config_digest`` from the trace header.
|
|
100
|
+
replay_config_digest: SHA-256 of the replay-side
|
|
101
|
+
StorageManagerConfig, for mismatch comparisons. Empty
|
|
102
|
+
string if the driver could not compute it.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
records_replayed: int
|
|
106
|
+
records_skipped: int
|
|
107
|
+
records_failed: int
|
|
108
|
+
stats: ReplayStatsCollector
|
|
109
|
+
header_level: str
|
|
110
|
+
header_digest: str
|
|
111
|
+
replay_config_digest: str
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class StorageReplayDriver:
|
|
115
|
+
"""Replays a storage-level trace against a live StorageManager.
|
|
116
|
+
|
|
117
|
+
The driver owns the StorageManager for its lifetime; call
|
|
118
|
+
:meth:`close` (or use as a context manager) to shut it down.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(
|
|
122
|
+
self,
|
|
123
|
+
sm_config: StorageManagerConfig,
|
|
124
|
+
trace_path: str,
|
|
125
|
+
dispatcher: CallDispatcher | None = None,
|
|
126
|
+
obs_config: ObservabilityConfig = DEFAULT_REPLAY_OBS_CONFIG,
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Construct a driver.
|
|
129
|
+
|
|
130
|
+
Initializes the global observability EventBus **before**
|
|
131
|
+
constructing the StorageManager so internal events
|
|
132
|
+
(L0/L1/L2 lifecycle, eviction, etc.) flow through a live bus
|
|
133
|
+
during replay. This lets the same logging and monitoring
|
|
134
|
+
subscribers that run in the real server attach to the replay
|
|
135
|
+
session — e.g. operators can eyeball L1/L2 log output to
|
|
136
|
+
spot eviction churn or L2 bottlenecks.
|
|
137
|
+
|
|
138
|
+
Metrics (OTel / Prometheus) are **off by default** because
|
|
139
|
+
the metrics pipeline binds a Prometheus port; running two
|
|
140
|
+
replays concurrently with the same port would fail. Callers
|
|
141
|
+
who want metrics can pass their own ``obs_config``.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
sm_config: Replay-side StorageManager configuration.
|
|
145
|
+
Determines L1 size, eviction policy, and L2 adapters
|
|
146
|
+
used during replay. Typically different from the
|
|
147
|
+
recording-side config (the original deployment may
|
|
148
|
+
have used adapters unavailable on the replay host).
|
|
149
|
+
trace_path: Path to a ``.lct`` trace file written by
|
|
150
|
+
:class:`StorageTraceRecorder`.
|
|
151
|
+
dispatcher: Custom dispatcher. When omitted, a fresh
|
|
152
|
+
:func:`build_default_dispatcher` is used; passing one
|
|
153
|
+
explicitly is useful for tests that register extra
|
|
154
|
+
handlers.
|
|
155
|
+
obs_config: Observability configuration for the replay
|
|
156
|
+
session. Defaults to an enabled bus with logging
|
|
157
|
+
subscribers and no metrics/tracing. The driver
|
|
158
|
+
installs this config as the global singleton via
|
|
159
|
+
:func:`init_observability` and stops the resulting
|
|
160
|
+
bus on :meth:`close`.
|
|
161
|
+
"""
|
|
162
|
+
self._sm_config = sm_config
|
|
163
|
+
self._trace_path = trace_path
|
|
164
|
+
self._dispatcher = dispatcher or build_default_dispatcher()
|
|
165
|
+
self._closed = False
|
|
166
|
+
|
|
167
|
+
# Each resource is acquired under its own try/except so that a
|
|
168
|
+
# failure partway through __init__ still releases what has
|
|
169
|
+
# already been opened. Without this, ``__exit__`` / ``close``
|
|
170
|
+
# never runs (the caller never got a valid instance), and the
|
|
171
|
+
# reader / bus would leak — see Cursor bugbot comment on
|
|
172
|
+
# PR #3075.
|
|
173
|
+
reader: TraceReader | None = None
|
|
174
|
+
bus: EventBus | None = None
|
|
175
|
+
try:
|
|
176
|
+
reader = TraceReader(trace_path)
|
|
177
|
+
bus = init_observability(obs_config)
|
|
178
|
+
self._sm = StorageManager(sm_config)
|
|
179
|
+
except BaseException:
|
|
180
|
+
# ``BaseException`` to also cover KeyboardInterrupt /
|
|
181
|
+
# SystemExit raised from inside StorageManager setup —
|
|
182
|
+
# the resources below must still be released.
|
|
183
|
+
if bus is not None:
|
|
184
|
+
try:
|
|
185
|
+
bus.stop()
|
|
186
|
+
except Exception:
|
|
187
|
+
logger.warning(
|
|
188
|
+
"trace replay: error stopping bus during failed driver init",
|
|
189
|
+
exc_info=True,
|
|
190
|
+
)
|
|
191
|
+
if reader is not None:
|
|
192
|
+
try:
|
|
193
|
+
reader.close()
|
|
194
|
+
except Exception:
|
|
195
|
+
logger.warning(
|
|
196
|
+
"trace replay: error closing reader during failed driver init",
|
|
197
|
+
exc_info=True,
|
|
198
|
+
)
|
|
199
|
+
raise
|
|
200
|
+
|
|
201
|
+
self._reader = reader
|
|
202
|
+
self._bus = bus
|
|
203
|
+
|
|
204
|
+
def __enter__(self) -> StorageReplayDriver:
|
|
205
|
+
return self
|
|
206
|
+
|
|
207
|
+
def __exit__(self, *_exc: object) -> None:
|
|
208
|
+
self.close()
|
|
209
|
+
|
|
210
|
+
# ------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def trace_path(self) -> str:
|
|
214
|
+
"""Path of the trace file being replayed."""
|
|
215
|
+
return self._trace_path
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def storage_manager(self) -> StorageManager:
|
|
219
|
+
"""The live StorageManager driving replay.
|
|
220
|
+
|
|
221
|
+
Exposed primarily for tests that need to introspect residency
|
|
222
|
+
or eviction state after ``run()`` returns.
|
|
223
|
+
"""
|
|
224
|
+
return self._sm
|
|
225
|
+
|
|
226
|
+
def close(self) -> None:
|
|
227
|
+
"""Close the StorageManager, stop the bus, and close the reader.
|
|
228
|
+
|
|
229
|
+
Idempotent. The order matters: the StorageManager may
|
|
230
|
+
publish teardown events, so the bus outlives it briefly.
|
|
231
|
+
"""
|
|
232
|
+
if self._closed:
|
|
233
|
+
return
|
|
234
|
+
self._closed = True
|
|
235
|
+
try:
|
|
236
|
+
self._sm.close()
|
|
237
|
+
finally:
|
|
238
|
+
try:
|
|
239
|
+
self._bus.stop()
|
|
240
|
+
finally:
|
|
241
|
+
self._reader.close()
|
|
242
|
+
|
|
243
|
+
# ------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
def run(
|
|
246
|
+
self,
|
|
247
|
+
on_record: RecordCallback | None = None,
|
|
248
|
+
) -> ReplayResult:
|
|
249
|
+
"""Replay every record in the trace.
|
|
250
|
+
|
|
251
|
+
Dispatch is always paced to the recorded ``t_mono`` offsets
|
|
252
|
+
via ``time.sleep``: the replay never runs *ahead* of the
|
|
253
|
+
recording. Running ahead is unsafe because ``StorageManager``
|
|
254
|
+
reads and writes are async — collapsing the recorded gaps
|
|
255
|
+
races the async queues and leads to retrieve misses. If the
|
|
256
|
+
replay host is slower than recording, the loop simply lags
|
|
257
|
+
the recorded schedule.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
on_record: Optional per-record callback invoked after
|
|
261
|
+
dispatch with ``(qualname, latency_s, failed)``.
|
|
262
|
+
Used by the CLI's ``--jsonl-out`` feature.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
A :class:`ReplayResult` summarizing the run.
|
|
266
|
+
"""
|
|
267
|
+
stats = ReplayStatsCollector()
|
|
268
|
+
context = ReplayContext(sm=self._sm)
|
|
269
|
+
header = self._reader.header
|
|
270
|
+
t_start = time.time()
|
|
271
|
+
stats.mark_start(t_start)
|
|
272
|
+
|
|
273
|
+
replayed = skipped = failed = 0
|
|
274
|
+
t_wall_origin = time.monotonic()
|
|
275
|
+
|
|
276
|
+
for record in self._reader.records():
|
|
277
|
+
# Sleep just long enough to align to the recorded
|
|
278
|
+
# offset from the start of replay. No speedup — if
|
|
279
|
+
# the replay machine is slower than recording, the
|
|
280
|
+
# loop simply runs behind.
|
|
281
|
+
target = t_wall_origin + record.t_mono
|
|
282
|
+
now = time.monotonic()
|
|
283
|
+
if now < target:
|
|
284
|
+
time.sleep(target - now)
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
decoded_args = codecs.decode_args(record.args)
|
|
288
|
+
except Exception:
|
|
289
|
+
skipped += 1
|
|
290
|
+
logger.warning(
|
|
291
|
+
"trace replay: failed to decode args for %s; skipping",
|
|
292
|
+
record.qualname,
|
|
293
|
+
exc_info=True,
|
|
294
|
+
)
|
|
295
|
+
if on_record is not None:
|
|
296
|
+
on_record(record.qualname, 0.0, True)
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
if not self._dispatcher.has(record.qualname):
|
|
300
|
+
skipped += 1
|
|
301
|
+
logger.warning(
|
|
302
|
+
"trace replay: no handler for qualname %r; skipping",
|
|
303
|
+
record.qualname,
|
|
304
|
+
)
|
|
305
|
+
if on_record is not None:
|
|
306
|
+
on_record(record.qualname, 0.0, True)
|
|
307
|
+
continue
|
|
308
|
+
|
|
309
|
+
t0 = time.monotonic()
|
|
310
|
+
try:
|
|
311
|
+
self._dispatcher.dispatch(record.qualname, context, decoded_args)
|
|
312
|
+
latency = time.monotonic() - t0
|
|
313
|
+
stats.record(record.qualname, latency, failed=False)
|
|
314
|
+
replayed += 1
|
|
315
|
+
if on_record is not None:
|
|
316
|
+
on_record(record.qualname, latency, False)
|
|
317
|
+
except Exception:
|
|
318
|
+
latency = time.monotonic() - t0
|
|
319
|
+
stats.record(record.qualname, latency, failed=True)
|
|
320
|
+
failed += 1
|
|
321
|
+
logger.warning(
|
|
322
|
+
"trace replay: handler for %s raised",
|
|
323
|
+
record.qualname,
|
|
324
|
+
exc_info=True,
|
|
325
|
+
)
|
|
326
|
+
if on_record is not None:
|
|
327
|
+
on_record(record.qualname, latency, True)
|
|
328
|
+
|
|
329
|
+
# Close any contexts the trace left open (truncated trace,
|
|
330
|
+
# or missing __exit__ records). Releasing these keeps the
|
|
331
|
+
# StorageManager in a consistent state for a follow-up run
|
|
332
|
+
# or inspection.
|
|
333
|
+
for key_tuple, pending in list(context.open_read_contexts.items()):
|
|
334
|
+
while pending:
|
|
335
|
+
cm = pending.popleft()
|
|
336
|
+
try:
|
|
337
|
+
cm.__exit__(None, None, None)
|
|
338
|
+
except Exception:
|
|
339
|
+
logger.warning(
|
|
340
|
+
"trace replay: forced exit of dangling "
|
|
341
|
+
"read_prefetched_results raised (keys=%d)",
|
|
342
|
+
len(key_tuple),
|
|
343
|
+
exc_info=True,
|
|
344
|
+
)
|
|
345
|
+
context.open_read_contexts.pop(key_tuple, None)
|
|
346
|
+
|
|
347
|
+
stats.mark_end(time.time())
|
|
348
|
+
|
|
349
|
+
# Digest of the replay-side config so callers can compare
|
|
350
|
+
# against ``header.sm_config_digest``. Same hashing used by
|
|
351
|
+
# the recorder — see :func:`safe_storage_config_dict`.
|
|
352
|
+
safe = safe_storage_config_dict(self._sm_config)
|
|
353
|
+
replay_digest = hashlib.sha256(
|
|
354
|
+
json.dumps(safe, sort_keys=True).encode("utf-8")
|
|
355
|
+
).hexdigest()
|
|
356
|
+
|
|
357
|
+
return ReplayResult(
|
|
358
|
+
records_replayed=replayed,
|
|
359
|
+
records_skipped=skipped,
|
|
360
|
+
records_failed=failed,
|
|
361
|
+
stats=stats,
|
|
362
|
+
header_level=header.level,
|
|
363
|
+
header_digest=header.sm_config_digest,
|
|
364
|
+
replay_config_digest=replay_digest,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
#: Callback signature for per-record hooks during replay. Arguments
|
|
369
|
+
#: are ``(qualname, latency_seconds, failed)``. Declared at module
|
|
370
|
+
#: scope so callers can type-annotate their hooks without importing
|
|
371
|
+
#: the driver class.
|
|
372
|
+
RecordCallback = Callable[[str, float, bool], None]
|