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,260 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
# TODO(baoloongmao): This module contains the control plane implementation
|
|
4
|
+
# for socket-based channels. Currently, it provides the base class with
|
|
5
|
+
# ZMQ-based handshake and initialization logic. In the future, this can
|
|
6
|
+
# be extended to a complete channel with data plane using native sockets.
|
|
7
|
+
|
|
8
|
+
# Standard
|
|
9
|
+
from typing import Optional, Union
|
|
10
|
+
import asyncio
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
# Third Party
|
|
15
|
+
import msgspec
|
|
16
|
+
import zmq
|
|
17
|
+
|
|
18
|
+
# First Party
|
|
19
|
+
from lmcache.logging import init_logger
|
|
20
|
+
from lmcache.v1.rpc_utils import get_zmq_context, get_zmq_socket
|
|
21
|
+
from lmcache.v1.transfer_channel.abstract import BaseTransferChannel
|
|
22
|
+
from lmcache.v1.transfer_channel.transfer_utils import (
|
|
23
|
+
InitSideMsgBase,
|
|
24
|
+
InitSideRetMsgBase,
|
|
25
|
+
SideMsg,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = init_logger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PySocketMsgBase(msgspec.Struct, tag=True):
|
|
32
|
+
"""Base class for all py-socket-related messages"""
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class PySocketInitRequest(PySocketMsgBase):
|
|
38
|
+
"""Initialization request, peer_init_url is used as peer identifier"""
|
|
39
|
+
|
|
40
|
+
peer_init_url: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PySocketInitResponse(PySocketMsgBase):
|
|
44
|
+
"""Initialization response"""
|
|
45
|
+
|
|
46
|
+
status: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
PySocketMsg = Union[
|
|
50
|
+
PySocketInitRequest,
|
|
51
|
+
PySocketInitResponse,
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class PySocketChannel(BaseTransferChannel):
|
|
56
|
+
"""
|
|
57
|
+
Base class for socket-based transfer channels.
|
|
58
|
+
|
|
59
|
+
Control plane: Uses ZMQ sockets for handshake and initialization.
|
|
60
|
+
Data plane: To be implemented by subclasses.
|
|
61
|
+
|
|
62
|
+
Provides common control plane logic for different channel types.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
async_mode: bool = False,
|
|
68
|
+
**kwargs,
|
|
69
|
+
):
|
|
70
|
+
assert "role" in kwargs
|
|
71
|
+
assert "buffer_ptr" in kwargs
|
|
72
|
+
assert "buffer_size" in kwargs
|
|
73
|
+
assert "align_bytes" in kwargs
|
|
74
|
+
assert "tp_rank" in kwargs
|
|
75
|
+
assert "peer_init_url" in kwargs
|
|
76
|
+
|
|
77
|
+
self.role = kwargs["role"]
|
|
78
|
+
self.buffer_ptr = kwargs["buffer_ptr"]
|
|
79
|
+
self.buffer_size = kwargs["buffer_size"]
|
|
80
|
+
self.align_bytes = kwargs["align_bytes"]
|
|
81
|
+
self.tp_rank = kwargs["tp_rank"]
|
|
82
|
+
|
|
83
|
+
self.peer_lookup_url = kwargs.get("peer_lookup_url", None)
|
|
84
|
+
|
|
85
|
+
self.running = True
|
|
86
|
+
self.remote_connections: dict[str, dict] = {}
|
|
87
|
+
|
|
88
|
+
self.side_channel: Optional[zmq.Socket] = None
|
|
89
|
+
self.running_threads: list[threading.Thread] = []
|
|
90
|
+
|
|
91
|
+
self.async_mode = async_mode
|
|
92
|
+
if self.async_mode:
|
|
93
|
+
self.zmq_context = get_zmq_context(use_asyncio=True)
|
|
94
|
+
else:
|
|
95
|
+
self.zmq_context = get_zmq_context(use_asyncio=False)
|
|
96
|
+
self.peer_init_url = kwargs["peer_init_url"]
|
|
97
|
+
self.event_loop = kwargs.get("event_loop", None)
|
|
98
|
+
|
|
99
|
+
self._init_side_channels()
|
|
100
|
+
|
|
101
|
+
############################################################
|
|
102
|
+
# Control plane: Initialization functions
|
|
103
|
+
############################################################
|
|
104
|
+
|
|
105
|
+
def lazy_init_peer_connection(
|
|
106
|
+
self,
|
|
107
|
+
local_id: str,
|
|
108
|
+
peer_id: str,
|
|
109
|
+
peer_init_url: str,
|
|
110
|
+
init_side_msg: Optional[InitSideMsgBase] = None,
|
|
111
|
+
) -> Optional[InitSideRetMsgBase]:
|
|
112
|
+
raise NotImplementedError("Sync mode not supported in PySocketChannel")
|
|
113
|
+
|
|
114
|
+
async def async_lazy_init_peer_connection(
|
|
115
|
+
self,
|
|
116
|
+
local_id: str,
|
|
117
|
+
peer_id: str,
|
|
118
|
+
peer_init_url: str,
|
|
119
|
+
init_side_msg: Optional[InitSideMsgBase] = None,
|
|
120
|
+
) -> Optional[InitSideRetMsgBase]:
|
|
121
|
+
"""
|
|
122
|
+
Initialize connection to a peer using ZMQ sockets for handshake.
|
|
123
|
+
|
|
124
|
+
Note: peer_id is expected to be peer_init_url in this implementation.
|
|
125
|
+
"""
|
|
126
|
+
init_tmp_socket = get_zmq_socket(
|
|
127
|
+
self.zmq_context,
|
|
128
|
+
peer_init_url,
|
|
129
|
+
"tcp",
|
|
130
|
+
zmq.REQ,
|
|
131
|
+
"connect",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
init_req = PySocketInitRequest(peer_init_url=self.peer_init_url)
|
|
135
|
+
await init_tmp_socket.send(msgspec.msgpack.encode(init_req))
|
|
136
|
+
|
|
137
|
+
init_resp_bytes = await init_tmp_socket.recv()
|
|
138
|
+
_ = msgspec.msgpack.decode(init_resp_bytes, type=PySocketMsg)
|
|
139
|
+
|
|
140
|
+
self.remote_connections[peer_id] = {
|
|
141
|
+
"peer_init_url": peer_init_url,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
init_ret_msg: Optional[InitSideRetMsgBase] = None
|
|
145
|
+
if init_side_msg is not None:
|
|
146
|
+
init_ret_msg = await self.async_send_init_side_msg(
|
|
147
|
+
init_tmp_socket,
|
|
148
|
+
init_side_msg,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
init_tmp_socket.close()
|
|
152
|
+
return init_ret_msg
|
|
153
|
+
|
|
154
|
+
def _init_side_channels(self):
|
|
155
|
+
"""Initialize side channel for handling incoming connections"""
|
|
156
|
+
if self.peer_init_url is None:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
self.side_channel = get_zmq_socket(
|
|
160
|
+
self.zmq_context,
|
|
161
|
+
self.peer_init_url,
|
|
162
|
+
"tcp",
|
|
163
|
+
zmq.REP,
|
|
164
|
+
"bind",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if self.async_mode:
|
|
168
|
+
asyncio.run_coroutine_threadsafe(self._async_init_loop(), self.event_loop)
|
|
169
|
+
else:
|
|
170
|
+
self.init_thread = threading.Thread(target=self._init_loop, daemon=True)
|
|
171
|
+
self.init_thread.start()
|
|
172
|
+
self.running_threads.append(self.init_thread)
|
|
173
|
+
|
|
174
|
+
def _handle_init_msg(
|
|
175
|
+
self, req: Union[PySocketMsg, InitSideMsgBase]
|
|
176
|
+
) -> Union[PySocketMsg, InitSideRetMsgBase]:
|
|
177
|
+
"""Handle initialization messages from peers"""
|
|
178
|
+
resp: Union[PySocketMsg, InitSideRetMsgBase]
|
|
179
|
+
if isinstance(req, PySocketInitRequest):
|
|
180
|
+
peer_url = req.peer_init_url
|
|
181
|
+
|
|
182
|
+
self.remote_connections[peer_url] = {
|
|
183
|
+
"peer_init_url": peer_url,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
self._on_peer_connected(peer_url)
|
|
187
|
+
|
|
188
|
+
resp = PySocketInitResponse(status="ok")
|
|
189
|
+
logger.info("Replying initialization response")
|
|
190
|
+
|
|
191
|
+
elif isinstance(req, InitSideMsgBase):
|
|
192
|
+
resp = self.handle_init_side_msg(req)
|
|
193
|
+
logger.info("Replying P2P init side response")
|
|
194
|
+
else:
|
|
195
|
+
raise ValueError(f"Unsupported InitMsg type: {type(req)}")
|
|
196
|
+
|
|
197
|
+
return resp
|
|
198
|
+
|
|
199
|
+
def _on_peer_connected(self, peer_url: str):
|
|
200
|
+
"""Hook for subclasses to perform additional setup when a peer connects"""
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
def _init_loop(self):
|
|
204
|
+
"""Synchronous initialization loop for handling incoming connections"""
|
|
205
|
+
while self.running:
|
|
206
|
+
try:
|
|
207
|
+
req_bytes = self.side_channel.recv()
|
|
208
|
+
|
|
209
|
+
logger.info("Received initialization request")
|
|
210
|
+
|
|
211
|
+
req = msgspec.msgpack.decode(
|
|
212
|
+
req_bytes, type=Union[PySocketMsg, SideMsg]
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
resp = self._handle_init_msg(req)
|
|
216
|
+
|
|
217
|
+
self.side_channel.send(msgspec.msgpack.encode(resp))
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logger.error("Failed to process initialization loop: %s", str(e))
|
|
221
|
+
if self.running:
|
|
222
|
+
time.sleep(0.01)
|
|
223
|
+
|
|
224
|
+
async def _async_init_loop(self):
|
|
225
|
+
"""Asynchronous initialization loop for handling incoming connections"""
|
|
226
|
+
logger.info("Starting async initialization loop")
|
|
227
|
+
|
|
228
|
+
while self.running:
|
|
229
|
+
try:
|
|
230
|
+
req_bytes = await self.side_channel.recv()
|
|
231
|
+
|
|
232
|
+
logger.info("Received initialization request")
|
|
233
|
+
|
|
234
|
+
req = msgspec.msgpack.decode(
|
|
235
|
+
req_bytes, type=Union[PySocketMsg, SideMsg]
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
resp = self._handle_init_msg(req)
|
|
239
|
+
|
|
240
|
+
await self.side_channel.send(msgspec.msgpack.encode(resp))
|
|
241
|
+
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.error("Failed to process initialization loop: %s", str(e))
|
|
244
|
+
if self.running:
|
|
245
|
+
await asyncio.sleep(0.01)
|
|
246
|
+
|
|
247
|
+
############################################################
|
|
248
|
+
# Cleanup-related functions
|
|
249
|
+
############################################################
|
|
250
|
+
|
|
251
|
+
def close(self):
|
|
252
|
+
"""Close all sockets and cleanup resources"""
|
|
253
|
+
self.running = False
|
|
254
|
+
for thread in self.running_threads:
|
|
255
|
+
thread.join()
|
|
256
|
+
|
|
257
|
+
if self.side_channel is not None:
|
|
258
|
+
self.side_channel.close()
|
|
259
|
+
|
|
260
|
+
self.zmq_context.term()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
# Third Party
|
|
6
|
+
import msgspec
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_correct_device(device: str, worker_id: int) -> str:
|
|
10
|
+
"""
|
|
11
|
+
Get the correct device based on the given device string.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
device (str): The device string, could be cpu or cuda.
|
|
15
|
+
worker_id (int): The worker id to determine the cuda device.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
str: The correct device string with device id.
|
|
19
|
+
"""
|
|
20
|
+
if device == "cpu":
|
|
21
|
+
return "cpu"
|
|
22
|
+
elif device.startswith("cuda"):
|
|
23
|
+
return f"cuda:{worker_id}"
|
|
24
|
+
else:
|
|
25
|
+
raise ValueError(f"Invalid device: {device}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SideMsgBase(msgspec.Struct, tag=True):
|
|
29
|
+
"""Base class for all side-related messages during initialization"""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Side messages during initialization
|
|
35
|
+
class InitSideMsgBase(SideMsgBase):
|
|
36
|
+
"""Base class for all side-related messages during initialization"""
|
|
37
|
+
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class P2PInitSideMsg(InitSideMsgBase):
|
|
42
|
+
"""P2P specific initialization message"""
|
|
43
|
+
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Side return messages during initialization
|
|
48
|
+
class InitSideRetMsgBase(SideMsgBase):
|
|
49
|
+
"""Base class for all side-related messages during initialization"""
|
|
50
|
+
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class P2PInitSideRetMsg(InitSideRetMsgBase):
|
|
55
|
+
"""P2P specific initialization return message"""
|
|
56
|
+
|
|
57
|
+
peer_lookup_url: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
SideMsg = Union[
|
|
61
|
+
P2PInitSideMsg,
|
|
62
|
+
P2PInitSideRetMsg,
|
|
63
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
# Standard
|
|
4
|
+
from typing import Union
|
|
5
|
+
import array
|
|
6
|
+
import hashlib
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
# First Party
|
|
10
|
+
from lmcache.logging import init_logger
|
|
11
|
+
|
|
12
|
+
logger = init_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BloomFilter:
|
|
16
|
+
"""Bloom Filter for memory-efficient."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self, expected_elements: int = 1000000, false_positive_rate: float = 0.01
|
|
20
|
+
):
|
|
21
|
+
self.size = self._optimal_size(expected_elements, false_positive_rate)
|
|
22
|
+
self.hash_count = self._optimal_hash_count(self.size, expected_elements)
|
|
23
|
+
array_size = (self.size + 31) // 32
|
|
24
|
+
self.bit_array = array.array("I", [0] * array_size)
|
|
25
|
+
self.item_count = 0
|
|
26
|
+
self.expected_elements = expected_elements
|
|
27
|
+
self.false_positive_rate = false_positive_rate
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def _optimal_size(n: int, p: float) -> int:
|
|
31
|
+
return int(-(n * math.log(p)) / (math.log(2) ** 2))
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _optimal_hash_count(m: int, n: int) -> int:
|
|
35
|
+
return max(1, int((m / n) * math.log(2)))
|
|
36
|
+
|
|
37
|
+
def _hashes(self, item: Union[str, int]) -> list[int]:
|
|
38
|
+
result = []
|
|
39
|
+
if isinstance(item, int):
|
|
40
|
+
for i in range(self.hash_count):
|
|
41
|
+
h = hashlib.sha256(
|
|
42
|
+
item.to_bytes((item.bit_length() + 7) // 8, "big", signed=False)
|
|
43
|
+
+ i.to_bytes(4, "big")
|
|
44
|
+
).digest()
|
|
45
|
+
result.append(int.from_bytes(h[:4], "big") % self.size)
|
|
46
|
+
else:
|
|
47
|
+
for i in range(self.hash_count):
|
|
48
|
+
h = hashlib.sha256(f"{item}_{i}".encode()).digest()
|
|
49
|
+
result.append(int.from_bytes(h[:4], "big") % self.size)
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
def add(self, item: Union[str, int]) -> None:
|
|
53
|
+
for pos in self._hashes(item):
|
|
54
|
+
self.bit_array[pos >> 5] |= 1 << (pos & 31)
|
|
55
|
+
self.item_count += 1
|
|
56
|
+
|
|
57
|
+
def add_batch_with_hashes_and_check(self, positions_list: list[list[int]]) -> int:
|
|
58
|
+
"""Add multiple sets of hash positions and return count of new items.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
positions_list: List of lists, where each inner list contains
|
|
62
|
+
hash positions for one item
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Number of new items that were actually added
|
|
66
|
+
"""
|
|
67
|
+
unique_count = 0
|
|
68
|
+
for positions in positions_list:
|
|
69
|
+
is_new = any(
|
|
70
|
+
not (self.bit_array[pos >> 5] & (1 << (pos & 31))) for pos in positions
|
|
71
|
+
)
|
|
72
|
+
if is_new:
|
|
73
|
+
for pos in positions:
|
|
74
|
+
self.bit_array[pos >> 5] |= 1 << (pos & 31)
|
|
75
|
+
unique_count += 1
|
|
76
|
+
self.item_count += unique_count
|
|
77
|
+
return unique_count
|
|
78
|
+
|
|
79
|
+
def contains(self, item: Union[str, int]) -> bool:
|
|
80
|
+
for pos in self._hashes(item):
|
|
81
|
+
if not (self.bit_array[pos >> 5] & (1 << (pos & 31))):
|
|
82
|
+
return False
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
def clear(self) -> None:
|
|
86
|
+
self.bit_array = array.array("I", [0] * ((self.size + 31) // 32))
|
|
87
|
+
self.item_count = 0
|
|
88
|
+
|
|
89
|
+
def get_memory_usage_bytes(self) -> int:
|
|
90
|
+
return self.size // 8
|
|
91
|
+
|
|
92
|
+
def get_bit_set(self) -> int:
|
|
93
|
+
return sum(val.bit_count() for val in self.bit_array)
|
|
94
|
+
|
|
95
|
+
def get_fill_rate(self) -> float:
|
|
96
|
+
bits_set = self.get_bit_set()
|
|
97
|
+
return bits_set / self.size if self.size > 0 else 0.0
|
|
98
|
+
|
|
99
|
+
def get_statistics(self) -> dict:
|
|
100
|
+
bits_set = self.get_bit_set()
|
|
101
|
+
return {
|
|
102
|
+
"size_mb": self.get_memory_usage_bytes() / 1024 / 1024,
|
|
103
|
+
"hash_count": self.hash_count,
|
|
104
|
+
"item_count": self.item_count,
|
|
105
|
+
"bits_set": bits_set,
|
|
106
|
+
"fill_rate": bits_set / self.size if self.size > 0 else 0.0,
|
|
107
|
+
"expected_elements": self.expected_elements,
|
|
108
|
+
"false_positive_rate": self.false_positive_rate,
|
|
109
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from typing import Callable, Generic, Optional, TypeVar
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TTLListCache(Generic[T]):
|
|
11
|
+
"""
|
|
12
|
+
Generic list cache with TTL (Time-To-Live) support.
|
|
13
|
+
|
|
14
|
+
This cache stores a list of items of type T and automatically refreshes
|
|
15
|
+
when the cache expires.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, timeout_seconds: float = 30.0):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the cache.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
timeout_seconds: Default TTL in seconds for cache entries.
|
|
24
|
+
"""
|
|
25
|
+
self.cache: list[T] = []
|
|
26
|
+
self.cache_time: float = 0.0
|
|
27
|
+
self.cache_lock = threading.Lock()
|
|
28
|
+
self.timeout_seconds = timeout_seconds
|
|
29
|
+
|
|
30
|
+
def get_cached(
|
|
31
|
+
self,
|
|
32
|
+
get_fresh_data: Callable[[], list[T]],
|
|
33
|
+
timeout_override: Optional[float] = None,
|
|
34
|
+
) -> list[T]:
|
|
35
|
+
"""
|
|
36
|
+
Get cached data, refreshing if expired.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
get_fresh_data: Function to get fresh data when cache is expired
|
|
40
|
+
timeout_override: Optional timeout override in seconds.
|
|
41
|
+
If None, use instance timeout_seconds.
|
|
42
|
+
If 0: cache immediately expires, always get fresh data and update cache.
|
|
43
|
+
If > 0: use specified timeout.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
List of cached items, either from cache or fresh data
|
|
47
|
+
"""
|
|
48
|
+
current_time = time.time()
|
|
49
|
+
|
|
50
|
+
# Fast path: check cache without lock
|
|
51
|
+
if not self.is_expired(timeout_override, current_time):
|
|
52
|
+
return self.cache
|
|
53
|
+
|
|
54
|
+
# Slow path: refresh cache with lock
|
|
55
|
+
with self.cache_lock:
|
|
56
|
+
# Double-check after acquiring lock
|
|
57
|
+
if not self.is_expired(timeout_override, current_time):
|
|
58
|
+
return self.cache
|
|
59
|
+
|
|
60
|
+
# Refresh cache
|
|
61
|
+
self.cache = get_fresh_data()
|
|
62
|
+
self.cache_time = current_time
|
|
63
|
+
return self.cache
|
|
64
|
+
|
|
65
|
+
def clear(self) -> None:
|
|
66
|
+
"""Clear the cache."""
|
|
67
|
+
with self.cache_lock:
|
|
68
|
+
self.cache.clear()
|
|
69
|
+
self.cache_time = 0.0
|
|
70
|
+
|
|
71
|
+
def is_expired(
|
|
72
|
+
self,
|
|
73
|
+
timeout_override: Optional[float] = None,
|
|
74
|
+
current_time: Optional[float] = None,
|
|
75
|
+
) -> bool:
|
|
76
|
+
"""
|
|
77
|
+
Check if the cache is expired without refreshing it.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
timeout_override: Optional timeout override in seconds.
|
|
81
|
+
If None, use instance timeout_seconds.
|
|
82
|
+
If 0: always considered expired.
|
|
83
|
+
If > 0: use specified timeout.
|
|
84
|
+
current_time: Optional current time. If None, use time.time().
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
True if cache is expired or uninitialized, False otherwise
|
|
88
|
+
"""
|
|
89
|
+
if self.cache_time == 0.0:
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
effective_timeout = (
|
|
93
|
+
timeout_override if timeout_override is not None else self.timeout_seconds
|
|
94
|
+
)
|
|
95
|
+
if current_time is None:
|
|
96
|
+
current_time = time.time()
|
|
97
|
+
|
|
98
|
+
return current_time - self.cache_time >= effective_timeout
|
|
99
|
+
|
|
100
|
+
def get_cache_age(self) -> float:
|
|
101
|
+
"""
|
|
102
|
+
Get the age of the cache in seconds.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Age in seconds. Returns float('inf') if cache is uninitialized.
|
|
106
|
+
"""
|
|
107
|
+
if self.cache_time == 0.0:
|
|
108
|
+
return float("inf")
|
|
109
|
+
return time.time() - self.cache_time
|
|
110
|
+
|
|
111
|
+
def __len__(self) -> int:
|
|
112
|
+
"""Get the number of items in cache (without checking expiration)."""
|
|
113
|
+
return len(self.cache)
|
|
114
|
+
|
|
115
|
+
def __repr__(self) -> str:
|
|
116
|
+
"""String representation of the cache."""
|
|
117
|
+
age = self.get_cache_age()
|
|
118
|
+
if age == float("inf"):
|
|
119
|
+
age_str = "uninitialized"
|
|
120
|
+
else:
|
|
121
|
+
age_str = f"{age:.2f}s old"
|
|
122
|
+
return (
|
|
123
|
+
f"TTLListCache(cache_items={len(self)}, age={age_str}, "
|
|
124
|
+
f"timeout={self.timeout_seconds}s)"
|
|
125
|
+
)
|