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.
Files changed (399) hide show
  1. lmcache/__init__.py +84 -0
  2. lmcache/_version.py +24 -0
  3. lmcache/cli/__init__.py +1 -0
  4. lmcache/cli/commands/__init__.py +34 -0
  5. lmcache/cli/commands/base.py +157 -0
  6. lmcache/cli/commands/bench/__init__.py +557 -0
  7. lmcache/cli/commands/bench/engine_bench/__init__.py +1 -0
  8. lmcache/cli/commands/bench/engine_bench/config.py +245 -0
  9. lmcache/cli/commands/bench/engine_bench/interactive/__init__.py +274 -0
  10. lmcache/cli/commands/bench/engine_bench/interactive/config.json +10 -0
  11. lmcache/cli/commands/bench/engine_bench/interactive/schema.py +352 -0
  12. lmcache/cli/commands/bench/engine_bench/interactive/state.py +327 -0
  13. lmcache/cli/commands/bench/engine_bench/interactive/terminal.py +291 -0
  14. lmcache/cli/commands/bench/engine_bench/progress.py +145 -0
  15. lmcache/cli/commands/bench/engine_bench/request_sender.py +232 -0
  16. lmcache/cli/commands/bench/engine_bench/stats.py +275 -0
  17. lmcache/cli/commands/bench/engine_bench/workloads/__init__.py +153 -0
  18. lmcache/cli/commands/bench/engine_bench/workloads/base.py +122 -0
  19. lmcache/cli/commands/bench/engine_bench/workloads/long_doc_permutator.py +435 -0
  20. lmcache/cli/commands/bench/engine_bench/workloads/long_doc_qa.py +281 -0
  21. lmcache/cli/commands/bench/engine_bench/workloads/multi_round_chat.py +337 -0
  22. lmcache/cli/commands/bench/engine_bench/workloads/random_prefill.py +178 -0
  23. lmcache/cli/commands/describe.py +310 -0
  24. lmcache/cli/commands/kvcache.py +133 -0
  25. lmcache/cli/commands/mock.py +75 -0
  26. lmcache/cli/commands/ping.py +113 -0
  27. lmcache/cli/commands/query/__init__.py +155 -0
  28. lmcache/cli/commands/query/prompt.py +134 -0
  29. lmcache/cli/commands/query/request.py +357 -0
  30. lmcache/cli/commands/server.py +99 -0
  31. lmcache/cli/commands/tool/__init__.py +63 -0
  32. lmcache/cli/commands/tool/cache_simulator.py +113 -0
  33. lmcache/cli/commands/trace/__init__.py +505 -0
  34. lmcache/cli/commands/trace/dispatch.py +249 -0
  35. lmcache/cli/commands/trace/driver.py +372 -0
  36. lmcache/cli/commands/trace/stats.py +289 -0
  37. lmcache/cli/documents/lmcache.txt +11 -0
  38. lmcache/cli/main.py +42 -0
  39. lmcache/cli/metrics/__init__.py +29 -0
  40. lmcache/cli/metrics/formatter.py +171 -0
  41. lmcache/cli/metrics/handler.py +94 -0
  42. lmcache/cli/metrics/metrics.py +161 -0
  43. lmcache/cli/metrics/section.py +77 -0
  44. lmcache/connections.py +173 -0
  45. lmcache/integration/__init__.py +2 -0
  46. lmcache/integration/base_service_factory.py +165 -0
  47. lmcache/integration/request_telemetry/__init__.py +1 -0
  48. lmcache/integration/request_telemetry/base.py +51 -0
  49. lmcache/integration/request_telemetry/factory.py +113 -0
  50. lmcache/integration/request_telemetry/fastapi.py +109 -0
  51. lmcache/integration/request_telemetry/noop.py +35 -0
  52. lmcache/integration/sglang/__init__.py +2 -0
  53. lmcache/integration/sglang/sglang_adapter.py +326 -0
  54. lmcache/integration/sglang/utils.py +39 -0
  55. lmcache/integration/vllm/__init__.py +1 -0
  56. lmcache/integration/vllm/lmcache_connector_v1.py +213 -0
  57. lmcache/integration/vllm/lmcache_connector_v1_085.py +150 -0
  58. lmcache/integration/vllm/lmcache_mp_connector_0180.py +1072 -0
  59. lmcache/integration/vllm/tests/test_mm_hash_utils.py +112 -0
  60. lmcache/integration/vllm/utils.py +433 -0
  61. lmcache/integration/vllm/vllm_multi_process_adapter.py +1090 -0
  62. lmcache/integration/vllm/vllm_service_factory.py +339 -0
  63. lmcache/integration/vllm/vllm_v1_adapter.py +1713 -0
  64. lmcache/logging.py +107 -0
  65. lmcache/native_storage_ops.pyi +230 -0
  66. lmcache/non_cuda_equivalents.py +1424 -0
  67. lmcache/observability.py +1958 -0
  68. lmcache/storage_backend/serde/__init__.py +1 -0
  69. lmcache/storage_backend/serde/cachegen_basics.py +210 -0
  70. lmcache/storage_backend/serde/cachegen_decoder.py +207 -0
  71. lmcache/storage_backend/serde/cachegen_encoder.py +394 -0
  72. lmcache/storage_backend/serde/serde.py +75 -0
  73. lmcache/tools/__init__.py +1 -0
  74. lmcache/tools/cache_simulator/README.md +392 -0
  75. lmcache/tools/cache_simulator/__init__.py +1 -0
  76. lmcache/tools/cache_simulator/docs/simulate_example.png +0 -0
  77. lmcache/tools/cache_simulator/docs/sweep_example.png +0 -0
  78. lmcache/tools/cache_simulator/gen_bench_dataset.py +360 -0
  79. lmcache/tools/cache_simulator/lru_cache.py +124 -0
  80. lmcache/tools/cache_simulator/plot_hit_rate.py +231 -0
  81. lmcache/tools/cache_simulator/simulator.py +795 -0
  82. lmcache/tools/controller_benchmark/README.md +161 -0
  83. lmcache/tools/controller_benchmark/__init__.py +1 -0
  84. lmcache/tools/controller_benchmark/__main__.py +331 -0
  85. lmcache/tools/controller_benchmark/benchmark.py +660 -0
  86. lmcache/tools/controller_benchmark/config.py +44 -0
  87. lmcache/tools/controller_benchmark/constants.py +10 -0
  88. lmcache/tools/controller_benchmark/handlers/__init__.py +46 -0
  89. lmcache/tools/controller_benchmark/handlers/admit.py +52 -0
  90. lmcache/tools/controller_benchmark/handlers/base.py +47 -0
  91. lmcache/tools/controller_benchmark/handlers/deregister.py +49 -0
  92. lmcache/tools/controller_benchmark/handlers/evict.py +52 -0
  93. lmcache/tools/controller_benchmark/handlers/heartbeat.py +56 -0
  94. lmcache/tools/controller_benchmark/handlers/p2p_lookup.py +47 -0
  95. lmcache/tools/controller_benchmark/handlers/register.py +56 -0
  96. lmcache/tools/mp_status_viewer/__init__.py +1 -0
  97. lmcache/tools/mp_status_viewer/__main__.py +95 -0
  98. lmcache/usage_context.py +417 -0
  99. lmcache/utils.py +665 -0
  100. lmcache/v1/__init__.py +2 -0
  101. lmcache/v1/api_server/__init__.py +2 -0
  102. lmcache/v1/api_server/__main__.py +537 -0
  103. lmcache/v1/basic_check.py +112 -0
  104. lmcache/v1/cache_controller/__init__.py +9 -0
  105. lmcache/v1/cache_controller/commands/__init__.py +15 -0
  106. lmcache/v1/cache_controller/commands/base.py +35 -0
  107. lmcache/v1/cache_controller/commands/full_sync.py +49 -0
  108. lmcache/v1/cache_controller/config.py +176 -0
  109. lmcache/v1/cache_controller/controller_manager.py +535 -0
  110. lmcache/v1/cache_controller/controllers/__init__.py +11 -0
  111. lmcache/v1/cache_controller/controllers/full_sync_tracker.py +473 -0
  112. lmcache/v1/cache_controller/controllers/kv_controller.py +439 -0
  113. lmcache/v1/cache_controller/controllers/registration_controller.py +282 -0
  114. lmcache/v1/cache_controller/executor.py +463 -0
  115. lmcache/v1/cache_controller/frontend/static/css/style.css +201 -0
  116. lmcache/v1/cache_controller/frontend/static/img/logo.png +0 -0
  117. lmcache/v1/cache_controller/frontend/static/index.html +234 -0
  118. lmcache/v1/cache_controller/frontend/static/js/controller_app.js +660 -0
  119. lmcache/v1/cache_controller/full_sync_sender.py +475 -0
  120. lmcache/v1/cache_controller/locks.py +149 -0
  121. lmcache/v1/cache_controller/message.py +828 -0
  122. lmcache/v1/cache_controller/observability.py +208 -0
  123. lmcache/v1/cache_controller/utils.py +679 -0
  124. lmcache/v1/cache_controller/worker.py +665 -0
  125. lmcache/v1/cache_engine.py +2058 -0
  126. lmcache/v1/cache_interface.py +19 -0
  127. lmcache/v1/check/__init__.py +74 -0
  128. lmcache/v1/check/check_mode_gen.py +86 -0
  129. lmcache/v1/check/check_mode_test_l2_adapter.py +284 -0
  130. lmcache/v1/check/check_mode_test_remote.py +155 -0
  131. lmcache/v1/check/check_mode_test_storage_manager.py +142 -0
  132. lmcache/v1/check/utils.py +571 -0
  133. lmcache/v1/compute/__init__.py +2 -0
  134. lmcache/v1/compute/attention/__init__.py +0 -0
  135. lmcache/v1/compute/attention/abstract.py +39 -0
  136. lmcache/v1/compute/attention/flash_attn.py +129 -0
  137. lmcache/v1/compute/attention/flash_infer_sparse.py +284 -0
  138. lmcache/v1/compute/attention/metadata.py +85 -0
  139. lmcache/v1/compute/attention/utils.py +14 -0
  140. lmcache/v1/compute/blend/__init__.py +7 -0
  141. lmcache/v1/compute/blend/blender.py +168 -0
  142. lmcache/v1/compute/blend/metadata.py +34 -0
  143. lmcache/v1/compute/blend/utils.py +63 -0
  144. lmcache/v1/compute/models/__init__.py +0 -0
  145. lmcache/v1/compute/models/base.py +141 -0
  146. lmcache/v1/compute/models/llama.py +9 -0
  147. lmcache/v1/compute/models/qwen3.py +24 -0
  148. lmcache/v1/compute/models/utils.py +68 -0
  149. lmcache/v1/compute/positional_encoding.py +199 -0
  150. lmcache/v1/config.py +848 -0
  151. lmcache/v1/config_base.py +848 -0
  152. lmcache/v1/distributed/api.py +248 -0
  153. lmcache/v1/distributed/config.py +321 -0
  154. lmcache/v1/distributed/error.py +64 -0
  155. lmcache/v1/distributed/eviction.py +192 -0
  156. lmcache/v1/distributed/eviction_policy/__init__.py +21 -0
  157. lmcache/v1/distributed/eviction_policy/factory.py +27 -0
  158. lmcache/v1/distributed/eviction_policy/lru.py +244 -0
  159. lmcache/v1/distributed/eviction_policy/noop.py +50 -0
  160. lmcache/v1/distributed/internal_api.py +170 -0
  161. lmcache/v1/distributed/l1_manager.py +835 -0
  162. lmcache/v1/distributed/l2_adapters/__init__.py +67 -0
  163. lmcache/v1/distributed/l2_adapters/base.py +360 -0
  164. lmcache/v1/distributed/l2_adapters/config.py +385 -0
  165. lmcache/v1/distributed/l2_adapters/factory.py +205 -0
  166. lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +747 -0
  167. lmcache/v1/distributed/l2_adapters/fs_native_l2_adapter.py +167 -0
  168. lmcache/v1/distributed/l2_adapters/mock_l2_adapter.py +516 -0
  169. lmcache/v1/distributed/l2_adapters/mooncake_store_l2_adapter.py +135 -0
  170. lmcache/v1/distributed/l2_adapters/native_connector_l2_adapter.py +468 -0
  171. lmcache/v1/distributed/l2_adapters/native_plugin_l2_adapter.py +199 -0
  172. lmcache/v1/distributed/l2_adapters/nixl_store_dynamic_l2_adapter.py +831 -0
  173. lmcache/v1/distributed/l2_adapters/nixl_store_l2_adapter.py +983 -0
  174. lmcache/v1/distributed/l2_adapters/plugin_l2_adapter.py +210 -0
  175. lmcache/v1/distributed/l2_adapters/resp_l2_adapter.py +176 -0
  176. lmcache/v1/distributed/memory_manager.py +179 -0
  177. lmcache/v1/distributed/storage_controller.py +39 -0
  178. lmcache/v1/distributed/storage_controllers/__init__.py +43 -0
  179. lmcache/v1/distributed/storage_controllers/eviction_controller.py +242 -0
  180. lmcache/v1/distributed/storage_controllers/prefetch_controller.py +830 -0
  181. lmcache/v1/distributed/storage_controllers/prefetch_policy.py +193 -0
  182. lmcache/v1/distributed/storage_controllers/store_controller.py +452 -0
  183. lmcache/v1/distributed/storage_controllers/store_policy.py +213 -0
  184. lmcache/v1/distributed/storage_manager.py +532 -0
  185. lmcache/v1/event_manager.py +145 -0
  186. lmcache/v1/exceptions/__init__.py +16 -0
  187. lmcache/v1/gpu_connector/__init__.py +126 -0
  188. lmcache/v1/gpu_connector/gpu_connectors.py +1906 -0
  189. lmcache/v1/gpu_connector/gpu_ops.py +85 -0
  190. lmcache/v1/gpu_connector/hpu_connector.py +326 -0
  191. lmcache/v1/gpu_connector/mock_gpu_connector.py +67 -0
  192. lmcache/v1/gpu_connector/utils.py +890 -0
  193. lmcache/v1/gpu_connector/xpu_connectors.py +916 -0
  194. lmcache/v1/health_monitor/__init__.py +1 -0
  195. lmcache/v1/health_monitor/base.py +587 -0
  196. lmcache/v1/health_monitor/checks/__init__.py +1 -0
  197. lmcache/v1/health_monitor/checks/remote_backend_check.py +304 -0
  198. lmcache/v1/health_monitor/constants.py +36 -0
  199. lmcache/v1/internal_api_server/__init__.py +0 -0
  200. lmcache/v1/internal_api_server/api_registry.py +59 -0
  201. lmcache/v1/internal_api_server/api_server.py +120 -0
  202. lmcache/v1/internal_api_server/common/__init__.py +1 -0
  203. lmcache/v1/internal_api_server/common/env_api.py +22 -0
  204. lmcache/v1/internal_api_server/common/loglevel_api.py +57 -0
  205. lmcache/v1/internal_api_server/common/metrics_api.py +29 -0
  206. lmcache/v1/internal_api_server/common/periodic_thread_api.py +138 -0
  207. lmcache/v1/internal_api_server/common/run_script_api.py +73 -0
  208. lmcache/v1/internal_api_server/common/thread_api.py +63 -0
  209. lmcache/v1/internal_api_server/controller/__init__.py +1 -0
  210. lmcache/v1/internal_api_server/controller/key_stats_api.py +81 -0
  211. lmcache/v1/internal_api_server/controller/worker_info_api.py +136 -0
  212. lmcache/v1/internal_api_server/utils.py +43 -0
  213. lmcache/v1/internal_api_server/vllm/__init__.py +1 -0
  214. lmcache/v1/internal_api_server/vllm/backend_api.py +221 -0
  215. lmcache/v1/internal_api_server/vllm/bypass_api.py +204 -0
  216. lmcache/v1/internal_api_server/vllm/cache_api.py +895 -0
  217. lmcache/v1/internal_api_server/vllm/chunk_statistics_api.py +141 -0
  218. lmcache/v1/internal_api_server/vllm/conf_api.py +147 -0
  219. lmcache/v1/internal_api_server/vllm/freeze_api.py +172 -0
  220. lmcache/v1/internal_api_server/vllm/hot_cache_api.py +184 -0
  221. lmcache/v1/internal_api_server/vllm/inference_api.py +65 -0
  222. lmcache/v1/internal_api_server/vllm/load_fs_chunks_api.py +320 -0
  223. lmcache/v1/internal_api_server/vllm/lookup_api.py +145 -0
  224. lmcache/v1/internal_api_server/vllm/version_api.py +25 -0
  225. lmcache/v1/kv_layer_groups.py +267 -0
  226. lmcache/v1/lazy_memory_allocator.py +284 -0
  227. lmcache/v1/lookup_client/__init__.py +25 -0
  228. lmcache/v1/lookup_client/abstract_client.py +77 -0
  229. lmcache/v1/lookup_client/async_lookup_message.py +50 -0
  230. lmcache/v1/lookup_client/chunk_statistics_lookup_client.py +200 -0
  231. lmcache/v1/lookup_client/factory.py +251 -0
  232. lmcache/v1/lookup_client/hit_limit_lookup_client.py +86 -0
  233. lmcache/v1/lookup_client/lmcache_async_lookup_client.py +407 -0
  234. lmcache/v1/lookup_client/lmcache_lookup_client.py +285 -0
  235. lmcache/v1/lookup_client/lmcache_lookup_client_bypass.py +99 -0
  236. lmcache/v1/lookup_client/mooncake_lookup_client.py +87 -0
  237. lmcache/v1/lookup_client/record_strategies/__init__.py +77 -0
  238. lmcache/v1/lookup_client/record_strategies/base.py +327 -0
  239. lmcache/v1/lookup_client/record_strategies/file_hash.py +130 -0
  240. lmcache/v1/lookup_client/record_strategies/memory_bloom_filter.py +81 -0
  241. lmcache/v1/manager.py +539 -0
  242. lmcache/v1/memory_management.py +2619 -0
  243. lmcache/v1/metadata.py +114 -0
  244. lmcache/v1/mp_observability/AGENTS.override.md +21 -0
  245. lmcache/v1/mp_observability/README.md +204 -0
  246. lmcache/v1/mp_observability/config.py +340 -0
  247. lmcache/v1/mp_observability/event.py +100 -0
  248. lmcache/v1/mp_observability/event_bus.py +313 -0
  249. lmcache/v1/mp_observability/otel_init.py +145 -0
  250. lmcache/v1/mp_observability/subscribers/__init__.py +28 -0
  251. lmcache/v1/mp_observability/subscribers/logging/__init__.py +19 -0
  252. lmcache/v1/mp_observability/subscribers/logging/l1.py +56 -0
  253. lmcache/v1/mp_observability/subscribers/logging/l2.py +73 -0
  254. lmcache/v1/mp_observability/subscribers/logging/lookup_hash.py +209 -0
  255. lmcache/v1/mp_observability/subscribers/logging/mp_server.py +90 -0
  256. lmcache/v1/mp_observability/subscribers/logging/sm.py +59 -0
  257. lmcache/v1/mp_observability/subscribers/metrics/__init__.py +20 -0
  258. lmcache/v1/mp_observability/subscribers/metrics/l0_lifecycle.py +290 -0
  259. lmcache/v1/mp_observability/subscribers/metrics/l1.py +55 -0
  260. lmcache/v1/mp_observability/subscribers/metrics/l1_lifecycle.py +166 -0
  261. lmcache/v1/mp_observability/subscribers/metrics/l2.py +121 -0
  262. lmcache/v1/mp_observability/subscribers/metrics/sm.py +69 -0
  263. lmcache/v1/mp_observability/subscribers/tracing/__init__.py +12 -0
  264. lmcache/v1/mp_observability/subscribers/tracing/mp_server.py +333 -0
  265. lmcache/v1/mp_observability/subscribers/tracing/span_registry.py +148 -0
  266. lmcache/v1/mp_observability/trace/__init__.py +50 -0
  267. lmcache/v1/mp_observability/trace/codecs.py +255 -0
  268. lmcache/v1/mp_observability/trace/decorator.py +147 -0
  269. lmcache/v1/mp_observability/trace/format.py +132 -0
  270. lmcache/v1/mp_observability/trace/lifecycle.py +83 -0
  271. lmcache/v1/mp_observability/trace/reader.py +167 -0
  272. lmcache/v1/mp_observability/trace/recorder.py +300 -0
  273. lmcache/v1/multiprocess/__init__.py +0 -0
  274. lmcache/v1/multiprocess/affinity_pool.py +102 -0
  275. lmcache/v1/multiprocess/blend_server_v2.py +891 -0
  276. lmcache/v1/multiprocess/config.py +253 -0
  277. lmcache/v1/multiprocess/custom_types.py +281 -0
  278. lmcache/v1/multiprocess/futures.py +194 -0
  279. lmcache/v1/multiprocess/gpu_context.py +511 -0
  280. lmcache/v1/multiprocess/http_server.py +235 -0
  281. lmcache/v1/multiprocess/mp_runtime_plugin_launcher.py +130 -0
  282. lmcache/v1/multiprocess/mq.py +732 -0
  283. lmcache/v1/multiprocess/protocol.py +86 -0
  284. lmcache/v1/multiprocess/protocols/README.md +213 -0
  285. lmcache/v1/multiprocess/protocols/__init__.py +127 -0
  286. lmcache/v1/multiprocess/protocols/base.py +89 -0
  287. lmcache/v1/multiprocess/protocols/blend.py +109 -0
  288. lmcache/v1/multiprocess/protocols/blend_v2.py +57 -0
  289. lmcache/v1/multiprocess/protocols/controller.py +53 -0
  290. lmcache/v1/multiprocess/protocols/debug.py +34 -0
  291. lmcache/v1/multiprocess/protocols/engine.py +146 -0
  292. lmcache/v1/multiprocess/protocols/observability.py +39 -0
  293. lmcache/v1/multiprocess/server.py +1134 -0
  294. lmcache/v1/multiprocess/session.py +190 -0
  295. lmcache/v1/multiprocess/token_hasher.py +441 -0
  296. lmcache/v1/offload_server/__init__.py +17 -0
  297. lmcache/v1/offload_server/abstract_server.py +37 -0
  298. lmcache/v1/offload_server/message.py +30 -0
  299. lmcache/v1/offload_server/zmq_server.py +122 -0
  300. lmcache/v1/periodic_thread.py +579 -0
  301. lmcache/v1/pin_monitor.py +246 -0
  302. lmcache/v1/plugin/__init__.py +0 -0
  303. lmcache/v1/plugin/runtime_plugin_launcher.py +211 -0
  304. lmcache/v1/protocol.py +317 -0
  305. lmcache/v1/rpc/__init__.py +17 -0
  306. lmcache/v1/rpc/transport.py +105 -0
  307. lmcache/v1/rpc/zmq_transport.py +213 -0
  308. lmcache/v1/rpc_utils.py +165 -0
  309. lmcache/v1/server/__init__.py +2 -0
  310. lmcache/v1/server/__main__.py +170 -0
  311. lmcache/v1/server/storage_backend/__init__.py +21 -0
  312. lmcache/v1/server/storage_backend/abstract_backend.py +80 -0
  313. lmcache/v1/server/storage_backend/local_backend.py +75 -0
  314. lmcache/v1/server/utils.py +21 -0
  315. lmcache/v1/standalone/__init__.py +1 -0
  316. lmcache/v1/standalone/__main__.py +583 -0
  317. lmcache/v1/standalone/manager.py +80 -0
  318. lmcache/v1/standalone/standalone_service_factory.py +86 -0
  319. lmcache/v1/storage_backend/__init__.py +313 -0
  320. lmcache/v1/storage_backend/abstract_backend.py +445 -0
  321. lmcache/v1/storage_backend/audit_backend.py +233 -0
  322. lmcache/v1/storage_backend/batched_message_sender.py +222 -0
  323. lmcache/v1/storage_backend/cache_policy/__init__.py +45 -0
  324. lmcache/v1/storage_backend/cache_policy/base_policy.py +87 -0
  325. lmcache/v1/storage_backend/cache_policy/fifo.py +58 -0
  326. lmcache/v1/storage_backend/cache_policy/lfu.py +105 -0
  327. lmcache/v1/storage_backend/cache_policy/lru.py +81 -0
  328. lmcache/v1/storage_backend/cache_policy/mru.py +61 -0
  329. lmcache/v1/storage_backend/connector/__init__.py +443 -0
  330. lmcache/v1/storage_backend/connector/audit_adapter.py +77 -0
  331. lmcache/v1/storage_backend/connector/audit_connector.py +320 -0
  332. lmcache/v1/storage_backend/connector/base_connector.py +379 -0
  333. lmcache/v1/storage_backend/connector/blackhole_adapter.py +21 -0
  334. lmcache/v1/storage_backend/connector/blackhole_connector.py +37 -0
  335. lmcache/v1/storage_backend/connector/eic_adapter.py +31 -0
  336. lmcache/v1/storage_backend/connector/eic_connector.py +757 -0
  337. lmcache/v1/storage_backend/connector/external_adapter.py +79 -0
  338. lmcache/v1/storage_backend/connector/fs_adapter.py +51 -0
  339. lmcache/v1/storage_backend/connector/fs_connector.py +403 -0
  340. lmcache/v1/storage_backend/connector/infinistore_adapter.py +56 -0
  341. lmcache/v1/storage_backend/connector/infinistore_connector.py +177 -0
  342. lmcache/v1/storage_backend/connector/instrumented_connector.py +219 -0
  343. lmcache/v1/storage_backend/connector/lm_adapter.py +31 -0
  344. lmcache/v1/storage_backend/connector/lm_connector.py +176 -0
  345. lmcache/v1/storage_backend/connector/mock_adapter.py +57 -0
  346. lmcache/v1/storage_backend/connector/mock_connector.py +349 -0
  347. lmcache/v1/storage_backend/connector/mooncakestore_adapter.py +43 -0
  348. lmcache/v1/storage_backend/connector/mooncakestore_connector.py +614 -0
  349. lmcache/v1/storage_backend/connector/redis_adapter.py +181 -0
  350. lmcache/v1/storage_backend/connector/redis_connector.py +828 -0
  351. lmcache/v1/storage_backend/connector/s3_adapter.py +59 -0
  352. lmcache/v1/storage_backend/connector/s3_connector.py +699 -0
  353. lmcache/v1/storage_backend/connector/sagemaker_hyperpod_adapter.py +233 -0
  354. lmcache/v1/storage_backend/connector/sagemaker_hyperpod_connector.py +987 -0
  355. lmcache/v1/storage_backend/connector/valkey_adapter.py +114 -0
  356. lmcache/v1/storage_backend/connector/valkey_connector.py +627 -0
  357. lmcache/v1/storage_backend/gds_backend.py +1199 -0
  358. lmcache/v1/storage_backend/job_executor/__init__.py +0 -0
  359. lmcache/v1/storage_backend/job_executor/base_executor.py +34 -0
  360. lmcache/v1/storage_backend/job_executor/pq_executor.py +235 -0
  361. lmcache/v1/storage_backend/local_cpu_backend.py +810 -0
  362. lmcache/v1/storage_backend/local_disk_backend.py +656 -0
  363. lmcache/v1/storage_backend/maru_backend.py +734 -0
  364. lmcache/v1/storage_backend/naive_serde/__init__.py +50 -0
  365. lmcache/v1/storage_backend/naive_serde/cachegen_basics.py +133 -0
  366. lmcache/v1/storage_backend/naive_serde/cachegen_decoder.py +135 -0
  367. lmcache/v1/storage_backend/naive_serde/cachegen_encoder.py +83 -0
  368. lmcache/v1/storage_backend/naive_serde/kivi_serde.py +22 -0
  369. lmcache/v1/storage_backend/naive_serde/naive_serde.py +18 -0
  370. lmcache/v1/storage_backend/naive_serde/serde.py +37 -0
  371. lmcache/v1/storage_backend/native_clients/connector_client_base.py +165 -0
  372. lmcache/v1/storage_backend/native_clients/resp_client.py +35 -0
  373. lmcache/v1/storage_backend/nixl_storage_backend.py +1400 -0
  374. lmcache/v1/storage_backend/p2p_backend.py +788 -0
  375. lmcache/v1/storage_backend/path_sharder.py +117 -0
  376. lmcache/v1/storage_backend/pd_backend.py +646 -0
  377. lmcache/v1/storage_backend/plugins/dax_backend.py +1443 -0
  378. lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py +1361 -0
  379. lmcache/v1/storage_backend/remote_backend.py +624 -0
  380. lmcache/v1/storage_backend/resp_client.py +227 -0
  381. lmcache/v1/storage_backend/storage_backend_listener.py +19 -0
  382. lmcache/v1/storage_backend/storage_manager.py +1352 -0
  383. lmcache/v1/system_detection.py +110 -0
  384. lmcache/v1/token_database.py +551 -0
  385. lmcache/v1/transfer_channel/__init__.py +83 -0
  386. lmcache/v1/transfer_channel/abstract.py +285 -0
  387. lmcache/v1/transfer_channel/mock_memory_channel.py +156 -0
  388. lmcache/v1/transfer_channel/nixl_channel.py +639 -0
  389. lmcache/v1/transfer_channel/py_socket_channel.py +260 -0
  390. lmcache/v1/transfer_channel/transfer_utils.py +63 -0
  391. lmcache/v1/utils/__init__.py +1 -0
  392. lmcache/v1/utils/bloom_filter.py +109 -0
  393. lmcache/v1/utils/cache_utils.py +125 -0
  394. lmcache_cli-0.4.5.dev0.dist-info/METADATA +185 -0
  395. lmcache_cli-0.4.5.dev0.dist-info/RECORD +399 -0
  396. lmcache_cli-0.4.5.dev0.dist-info/WHEEL +5 -0
  397. lmcache_cli-0.4.5.dev0.dist-info/entry_points.txt +2 -0
  398. lmcache_cli-0.4.5.dev0.dist-info/licenses/LICENSE +201 -0
  399. lmcache_cli-0.4.5.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1958 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Standard
3
+ from contextlib import contextmanager
4
+ from dataclasses import dataclass, field
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ Any,
8
+ Dict,
9
+ Iterable,
10
+ List,
11
+ Optional,
12
+ Sequence,
13
+ Union,
14
+ )
15
+ import os
16
+ import threading
17
+ import time
18
+
19
+ # Third Party
20
+ from prometheus_client import REGISTRY
21
+ import prometheus_client
22
+
23
+ # First Party
24
+ from lmcache.logging import init_logger
25
+ from lmcache.usage_context import ContinuousUsageContext
26
+ from lmcache.utils import thread_safe
27
+ from lmcache.v1.metadata import LMCacheMetadata
28
+
29
+ if TYPE_CHECKING:
30
+ # First Party
31
+ from lmcache.v1.config import LMCacheEngineConfig
32
+
33
+ logger = init_logger(__name__)
34
+
35
+
36
+ @dataclass
37
+ class LMCacheStats:
38
+ # Counter (Note that these are incremental values,
39
+ # which will accumulate over time in Counter)
40
+ interval_retrieve_requests: int
41
+ interval_store_requests: int
42
+ interval_lookup_requests: int
43
+ interval_requested_tokens: int
44
+ interval_hit_tokens: int
45
+ interval_stored_tokens: int
46
+ interval_lookup_tokens: int
47
+ interval_lookup_hits: int
48
+ interval_vllm_hit_tokens: int
49
+ interval_prompt_tokens: int
50
+
51
+ interval_num_slow_retrieval_by_time: int
52
+ interval_num_slow_retrieval_by_speed: int
53
+
54
+ interval_remote_read_requests: int
55
+ interval_remote_read_bytes: int
56
+ interval_remote_write_requests: int
57
+ interval_remote_write_bytes: int
58
+
59
+ interval_remote_time_to_get: List[float]
60
+ interval_remote_time_to_put: List[float]
61
+ interval_remote_time_to_get_sync: List[float]
62
+
63
+ interval_remote_ping_latency: float # Ping latency in milliseconds
64
+ interval_remote_ping_errors: int # Number of ping errors
65
+ interval_remote_ping_success: int # Number of ping successes
66
+ interval_remote_ping_error_code: int # Latest ping error code
67
+
68
+ interval_local_cpu_evict_count: int # evict count
69
+ interval_local_cpu_evict_keys_count: int # evict keys count
70
+ interval_local_cpu_evict_failed_count: int # evict failed count
71
+
72
+ interval_forced_unpin_count: int # forced unpin count due to timeout
73
+
74
+ # Real time value measurements (will be reset after each log)
75
+ retrieve_hit_rate: float
76
+ lookup_hit_rate: float
77
+
78
+ local_cache_usage_bytes: int # Size of the used local cache in bytes
79
+ remote_cache_usage_bytes: int # Size of the used remote cache in bytes
80
+ local_storage_usage_bytes: int # Size of the used local storage in bytes
81
+
82
+ active_memory_objs_count: int # the number of active memory objects
83
+ pinned_memory_objs_count: int # the number of pinned memory objects
84
+
85
+ # Distribution measurements
86
+ time_to_retrieve: List[float]
87
+ time_to_store: List[float]
88
+ time_to_lookup: List[float]
89
+ retrieve_speed: List[float] # Tokens per second
90
+ store_speed: List[float] # Tokens per second
91
+
92
+ # Granular profiling measurements
93
+ retrieve_process_tokens_time: List[float]
94
+ retrieve_broadcast_time: List[float]
95
+ retrieve_to_gpu_time: List[float]
96
+ remote_backend_batched_get_blocking_time: List[float]
97
+ instrumented_connector_batched_get_time: List[float]
98
+ store_process_tokens_time: List[float]
99
+ store_from_gpu_time: List[float]
100
+ store_put_time: List[float]
101
+
102
+ # P2P transfer metrics
103
+ interval_p2p_requests: int
104
+ interval_p2p_transferred_tokens: int
105
+ p2p_time_to_transfer: List[float]
106
+ p2p_transfer_speed: List[float] # Tokens per second
107
+
108
+ # request lookup hit rates
109
+ # use bucket of interval_lookup_hit_rates to represents non-0 hit requests
110
+ # use interval_lookup_0_hit_requests to represents 0 hit requests
111
+ interval_lookup_hit_rates: List[float]
112
+ interval_lookup_0_hit_requests: int
113
+
114
+ interval_request_cache_lifespan: List[float] # cache lifespan in minutes
115
+
116
+
117
+ @dataclass
118
+ class LookupRequestStats:
119
+ request_id: int
120
+ num_tokens: int
121
+ hit_tokens: int
122
+ is_finished: bool
123
+ start_time: float = 0
124
+ end_time: float = 0
125
+
126
+ def time_to_lookup(self):
127
+ if self.end_time == 0:
128
+ return 0
129
+ return self.end_time - self.start_time
130
+
131
+ def hit_rate(self):
132
+ if self.num_tokens == 0:
133
+ return 0
134
+ return self.hit_tokens / self.num_tokens
135
+
136
+
137
+ @dataclass
138
+ class RetrieveRequestStats:
139
+ request_id: int
140
+ num_tokens: int
141
+ local_hit_tokens: int
142
+ remote_hit_tokens: int # Not used for now
143
+ start_time: float
144
+ end_time: float
145
+ process_tokens_time: float = 0
146
+ broadcast_time: float = 0
147
+ to_gpu_time: float = 0
148
+ detailed_metrics: Dict[str, Any] = field(default_factory=dict)
149
+
150
+ def time_to_retrieve(self):
151
+ if self.end_time == 0:
152
+ return 0
153
+ return self.end_time - self.start_time
154
+
155
+ def retrieve_speed(self):
156
+ if self.time_to_retrieve() == 0:
157
+ return 0
158
+ return (
159
+ self.local_hit_tokens + self.remote_hit_tokens
160
+ ) / self.time_to_retrieve()
161
+
162
+ @contextmanager
163
+ def profile_process_tokens(self):
164
+ start = time.perf_counter()
165
+ try:
166
+ yield
167
+ finally:
168
+ self.process_tokens_time += time.perf_counter() - start
169
+
170
+ @contextmanager
171
+ def profile_broadcast(self):
172
+ start = time.perf_counter()
173
+ try:
174
+ yield
175
+ finally:
176
+ self.broadcast_time += time.perf_counter() - start
177
+
178
+ @contextmanager
179
+ def profile_to_gpu(self):
180
+ start = time.perf_counter()
181
+ try:
182
+ yield
183
+ finally:
184
+ self.to_gpu_time += time.perf_counter() - start
185
+
186
+
187
+ @dataclass
188
+ class StoreRequestStats:
189
+ request_id: int
190
+ num_tokens: int
191
+ start_time: float
192
+ end_time: float
193
+ process_tokens_time: float = 0
194
+ from_gpu_time: float = 0
195
+ put_time: float = 0
196
+
197
+ def time_to_store(self):
198
+ if self.end_time == 0:
199
+ return 0
200
+ return self.end_time - self.start_time
201
+
202
+ def store_speed(self):
203
+ if self.time_to_store() == 0:
204
+ return 0
205
+ return self.num_tokens / self.time_to_store()
206
+
207
+ @contextmanager
208
+ def profile_process_tokens(self):
209
+ start = time.perf_counter()
210
+ try:
211
+ yield
212
+ finally:
213
+ self.process_tokens_time += time.perf_counter() - start
214
+
215
+ @contextmanager
216
+ def profile_from_gpu(self):
217
+ start = time.perf_counter()
218
+ try:
219
+ yield
220
+ finally:
221
+ self.from_gpu_time += time.perf_counter() - start
222
+
223
+ @contextmanager
224
+ def profile_put(self):
225
+ start = time.perf_counter()
226
+ try:
227
+ yield
228
+ finally:
229
+ self.put_time += time.perf_counter() - start
230
+
231
+
232
+ @dataclass
233
+ class P2PTransferRequestStats:
234
+ num_tokens: int
235
+ start_time: float
236
+ end_time: float
237
+
238
+ def time_to_transfer(self):
239
+ if self.end_time == 0:
240
+ return 0
241
+ return self.end_time - self.start_time
242
+
243
+ def transfer_speed(self):
244
+ if self.time_to_transfer() == 0:
245
+ return 0
246
+ return self.num_tokens / self.time_to_transfer()
247
+
248
+
249
+ class LMCStatsMonitor:
250
+ def __init__(self):
251
+ # Interval metrics that will be reset after each log
252
+ # Accumulate incremental values in the Prometheus Counter
253
+ self.interval_retrieve_requests = 0
254
+ self.interval_store_requests = 0
255
+ self.interval_lookup_requests = 0
256
+ self.interval_requested_tokens = 0 # total requested tokens retrieve
257
+ self.interval_hit_tokens = 0 # total hit tokens retrieve
258
+ self.interval_stored_tokens = 0 # total tokens tored in LMCache
259
+ self.interval_lookup_tokens = 0 # total requested tokens lookup
260
+ self.interval_lookup_hits = 0 # total hit tokens lookup
261
+ self.interval_vllm_hit_tokens = 0 # total hit tokens in vllm
262
+ self.interval_prompt_tokens = 0 # total prompt tokens
263
+ self.interval_lookup_0_hit_requests = 0
264
+
265
+ self.interval_num_slow_retrieval_by_time = 0
266
+ self.interval_num_slow_retrieval_by_speed = 0
267
+
268
+ # P2P transfer metrics
269
+ self.interval_p2p_requests = 0
270
+ self.interval_p2p_transferred_tokens = 0
271
+ self.p2p_requests: Dict[int, P2PTransferRequestStats] = {}
272
+ self.p2p_request_id = 0
273
+
274
+ # remote backends read/write metrics
275
+ self.interval_remote_read_requests = 0
276
+ self.interval_remote_read_bytes = 0
277
+ self.interval_remote_write_requests = 0
278
+ self.interval_remote_write_bytes = 0
279
+
280
+ # remote backends get/put cost time metrics
281
+ self.interval_remote_time_to_get: List[float] = []
282
+ self.interval_remote_time_to_put: List[float] = []
283
+ # the time of get value from remote backends synchronously,
284
+ # which includes rpc and schedule time
285
+ self.interval_remote_time_to_get_sync: List[float] = []
286
+
287
+ self.interval_remote_ping_latency = 0
288
+ self.interval_remote_ping_errors = 0
289
+ self.interval_remote_ping_success = 0
290
+ self.interval_remote_ping_error_code = 0 # 0 means success
291
+
292
+ self.interval_local_cpu_evict_count = 0
293
+ self.interval_local_cpu_evict_keys_count = 0
294
+ self.interval_local_cpu_evict_failed_count = 0
295
+
296
+ self.interval_forced_unpin_count = 0
297
+
298
+ self.local_cache_usage_bytes = 0
299
+ self.remote_cache_usage_bytes = 0
300
+ self.local_storage_usage_bytes = 0
301
+
302
+ self.active_memory_objs_count = 0
303
+ self.pinned_memory_objs_count = 0
304
+
305
+ self.retrieve_requests: Dict[int, RetrieveRequestStats] = {}
306
+ self.store_requests: Dict[int, StoreRequestStats] = {}
307
+ self.lookup_requests: Dict[int, LookupRequestStats] = {}
308
+
309
+ self.retrieve_request_id = 0
310
+ self.store_request_id = 0
311
+ self.lookup_request_id = 0
312
+
313
+ self.interval_request_cache_lifespan: Dict[int, float] = {}
314
+ self.reuse_chunk_id = 0
315
+
316
+ self._current_retrieve_stats: Optional[RetrieveRequestStats] = None
317
+ self.retrieve_time_threshold: float = 1e9
318
+ self.retrieve_token_speed_threshold: float = -1.0
319
+ self.last_retrieve_warning_time: float = 0.0
320
+ self.skipped_retrieve_warning_count: int = 0
321
+
322
+ def set_current_retrieve_stats(self, stats: RetrieveRequestStats):
323
+ self._current_retrieve_stats = stats
324
+
325
+ def get_current_retrieve_stats(self) -> Optional[RetrieveRequestStats]:
326
+ return self._current_retrieve_stats
327
+
328
+ def clear_current_retrieve_stats(self):
329
+ self._current_retrieve_stats = None
330
+
331
+ @thread_safe
332
+ def on_lookup_request(self, num_tokens: int) -> LookupRequestStats:
333
+ """
334
+ This function is called when a lookup request is sent to the cache.
335
+ It will record the number of tokens requested.
336
+ """
337
+ curr_time = time.perf_counter()
338
+ lookup_stats = LookupRequestStats(
339
+ request_id=self.lookup_request_id,
340
+ num_tokens=num_tokens,
341
+ hit_tokens=0,
342
+ is_finished=False,
343
+ start_time=curr_time,
344
+ )
345
+ self.interval_lookup_requests += 1
346
+ self.interval_lookup_tokens += num_tokens
347
+ self.lookup_requests[self.lookup_request_id] = lookup_stats
348
+ self.lookup_request_id += 1
349
+ return lookup_stats
350
+
351
+ @thread_safe
352
+ def on_lookup_finished(
353
+ self,
354
+ stats: LookupRequestStats,
355
+ num_hit_tokens: int,
356
+ ):
357
+ """
358
+ This function is called when a lookup request is finished.
359
+ It will record the number of tokens hit and track by node type.
360
+
361
+ Args:
362
+ stats: LookupRequestStats object
363
+ num_hit_tokens: Total number of tokens found in lookup
364
+ """
365
+ curr_time = time.perf_counter()
366
+ assert stats.request_id in self.lookup_requests
367
+ stats.hit_tokens = num_hit_tokens
368
+ stats.is_finished = True
369
+ if stats.end_time == 0:
370
+ stats.end_time = curr_time
371
+ self.interval_lookup_hits += num_hit_tokens
372
+ if num_hit_tokens == 0:
373
+ self.interval_lookup_0_hit_requests += 1
374
+
375
+ @thread_safe
376
+ def on_retrieve_request(self, num_tokens: int) -> RetrieveRequestStats:
377
+ """
378
+ Returns the internal "request id" that will be used in
379
+ on_retrieve_finished
380
+ """
381
+ curr_time = time.perf_counter()
382
+ retrieve_stats = RetrieveRequestStats(
383
+ request_id=self.retrieve_request_id,
384
+ num_tokens=num_tokens,
385
+ local_hit_tokens=0,
386
+ remote_hit_tokens=0,
387
+ start_time=curr_time,
388
+ end_time=0,
389
+ )
390
+ self.interval_requested_tokens += num_tokens
391
+ self.interval_retrieve_requests += 1
392
+ self.retrieve_requests[self.retrieve_request_id] = retrieve_stats
393
+ self.retrieve_request_id += 1
394
+ self.set_current_retrieve_stats(retrieve_stats)
395
+ return retrieve_stats
396
+
397
+ @thread_safe
398
+ def on_retrieve_finished(
399
+ self,
400
+ retrieve_stats: RetrieveRequestStats,
401
+ num_retrieved_tokens: int,
402
+ ):
403
+ curr_time = time.perf_counter()
404
+ assert retrieve_stats.request_id in self.retrieve_requests
405
+ retrieve_stats.local_hit_tokens = num_retrieved_tokens
406
+ if retrieve_stats.end_time == 0:
407
+ retrieve_stats.end_time = curr_time
408
+ self.interval_hit_tokens += num_retrieved_tokens
409
+ self.clear_current_retrieve_stats()
410
+
411
+ time_to_retrieve = retrieve_stats.time_to_retrieve()
412
+ retrieve_speed = retrieve_stats.retrieve_speed()
413
+ if time_to_retrieve > self.retrieve_time_threshold:
414
+ self.interval_num_slow_retrieval_by_time += 1
415
+ if 0 < retrieve_speed < self.retrieve_token_speed_threshold:
416
+ self.interval_num_slow_retrieval_by_speed += 1
417
+
418
+ # Log a warning if the retrieval performance is below defined thresholds:
419
+ # 1. Total time taken (time_to_retrieve) exceeds the maximum allowed time.
420
+ # 2. Retrieval speed (retrieve_speed) falls below the minimum required tokens/s.
421
+ # The warnings are rate-limited to once every 10 seconds to avoid log flooding.
422
+ if (
423
+ time_to_retrieve > self.retrieve_time_threshold
424
+ or 0 < retrieve_speed < self.retrieve_token_speed_threshold
425
+ ):
426
+ if curr_time - self.last_retrieve_warning_time > 10.0:
427
+ logger.warning(
428
+ "Retrieve request %d surpassed threshold: "
429
+ "time_to_retrieve=%.4f s (threshold=%.4f s), "
430
+ "retrieve_speed=%.2f tokens/s (threshold=%.2f tokens/s). "
431
+ "Skipped %d slow retrieval logs in the last %.1f seconds. "
432
+ "Detailed metrics: "
433
+ "num_tokens=%d, local_hit_tokens=%d, remote_hit_tokens=%d, "
434
+ "process_tokens_time=%.5f s, "
435
+ "broadcast_time=%.5f s, "
436
+ "to_gpu_time=%.5f s, "
437
+ "detailed_metrics=%s",
438
+ retrieve_stats.request_id,
439
+ time_to_retrieve,
440
+ self.retrieve_time_threshold,
441
+ retrieve_speed,
442
+ self.retrieve_token_speed_threshold,
443
+ self.skipped_retrieve_warning_count,
444
+ curr_time - self.last_retrieve_warning_time,
445
+ retrieve_stats.num_tokens,
446
+ retrieve_stats.local_hit_tokens,
447
+ retrieve_stats.remote_hit_tokens,
448
+ retrieve_stats.process_tokens_time,
449
+ retrieve_stats.broadcast_time,
450
+ retrieve_stats.to_gpu_time,
451
+ retrieve_stats.detailed_metrics,
452
+ )
453
+ self.last_retrieve_warning_time = curr_time
454
+ self.skipped_retrieve_warning_count = 0
455
+ else:
456
+ self.skipped_retrieve_warning_count += 1
457
+
458
+ @thread_safe
459
+ def on_store_request(self, num_tokens: int) -> StoreRequestStats:
460
+ """
461
+ Returns the internal "request id" that will be used in on_store_finished
462
+ """
463
+ curr_time = time.perf_counter()
464
+ store_stats = StoreRequestStats(
465
+ request_id=self.store_request_id,
466
+ num_tokens=num_tokens,
467
+ start_time=curr_time,
468
+ end_time=0,
469
+ )
470
+ self.interval_store_requests += 1
471
+ self.interval_stored_tokens += num_tokens
472
+ self.store_requests[self.store_request_id] = store_stats
473
+ self.store_request_id += 1
474
+ return store_stats
475
+
476
+ @thread_safe
477
+ def on_store_finished(
478
+ self,
479
+ store_stats: StoreRequestStats,
480
+ num_stored_tokens: int = -1,
481
+ ):
482
+ curr_time = time.perf_counter()
483
+ assert store_stats.request_id in self.store_requests
484
+ if store_stats.end_time == 0:
485
+ store_stats.end_time = curr_time
486
+ if num_stored_tokens >= 0:
487
+ store_stats.num_tokens = num_stored_tokens
488
+
489
+ @thread_safe
490
+ def on_p2p_transfer_request(self, num_tokens: int) -> int:
491
+ curr_time = time.time()
492
+ self.interval_p2p_requests += 1
493
+ self.p2p_requests[self.p2p_request_id] = P2PTransferRequestStats(
494
+ num_tokens=num_tokens,
495
+ start_time=curr_time,
496
+ end_time=0,
497
+ )
498
+ self.p2p_request_id += 1
499
+ return self.p2p_request_id - 1
500
+
501
+ @thread_safe
502
+ def on_p2p_transfer_finished(self, request_id: int):
503
+ curr_time = time.time()
504
+ assert request_id in self.p2p_requests
505
+ p2p_stats = self.p2p_requests[request_id]
506
+ self.interval_p2p_transferred_tokens += p2p_stats.num_tokens
507
+ p2p_stats.end_time = curr_time
508
+
509
+ @thread_safe
510
+ def on_chunk_reuse(self, time_interval: float):
511
+ """
512
+ time_interval: float or int, in seconds
513
+ """
514
+ self.interval_request_cache_lifespan[self.reuse_chunk_id] = time_interval / 60.0
515
+ self.reuse_chunk_id += 1
516
+
517
+ @thread_safe
518
+ def update_local_cache_usage(self, usage: int):
519
+ self.local_cache_usage_bytes = usage
520
+
521
+ @thread_safe
522
+ def update_remote_cache_usage(self, usage: int):
523
+ self.remote_cache_usage_bytes = usage
524
+
525
+ @thread_safe
526
+ def update_local_storage_usage(self, usage: int):
527
+ self.local_storage_usage_bytes = usage
528
+
529
+ @thread_safe
530
+ def update_interval_remote_read_metrics(self, read_bytes: int):
531
+ self.interval_remote_read_requests += 1
532
+ self.interval_remote_read_bytes += read_bytes
533
+
534
+ @thread_safe
535
+ def update_interval_remote_write_metrics(self, write_bytes: int):
536
+ self.interval_remote_write_requests += 1
537
+ self.interval_remote_write_bytes += write_bytes
538
+
539
+ @thread_safe
540
+ def update_interval_remote_time_to_get(self, get_time: float):
541
+ self.interval_remote_time_to_get.append(get_time)
542
+
543
+ @thread_safe
544
+ def update_interval_remote_time_to_put(self, put_time: float):
545
+ self.interval_remote_time_to_put.append(put_time)
546
+
547
+ @thread_safe
548
+ def update_interval_remote_time_to_get_sync(self, get_time_sync: float):
549
+ self.interval_remote_time_to_get_sync.append(get_time_sync)
550
+
551
+ @thread_safe
552
+ def update_remote_ping_latency(self, latency: float):
553
+ self.interval_remote_ping_latency = latency
554
+
555
+ @thread_safe
556
+ def update_remote_ping_error_code(self, error_code: int):
557
+ """Update ping error code"""
558
+ self.interval_remote_ping_error_code = error_code
559
+ if error_code != 0:
560
+ self.interval_remote_ping_errors += 1
561
+ else:
562
+ self.interval_remote_ping_success += 1
563
+
564
+ @thread_safe
565
+ def update_local_cpu_evict_metrics(self, evict_keys_count: int):
566
+ self.interval_local_cpu_evict_count += 1
567
+ self.interval_local_cpu_evict_keys_count += evict_keys_count
568
+
569
+ @thread_safe
570
+ def update_local_cpu_evict_failed_count(self, evict_failed_count: int):
571
+ self.interval_local_cpu_evict_failed_count += evict_failed_count
572
+
573
+ @thread_safe
574
+ def update_forced_unpin_count(self, delta: int):
575
+ self.interval_forced_unpin_count += delta
576
+
577
+ @thread_safe
578
+ def update_active_memory_objs_count(self, active_memory_objs_count: int):
579
+ self.active_memory_objs_count = active_memory_objs_count
580
+
581
+ @thread_safe
582
+ def update_pinned_memory_objs_count(self, delta: int):
583
+ self.pinned_memory_objs_count += delta
584
+
585
+ @thread_safe
586
+ def update_interval_vllm_hit_tokens(self, delta: int):
587
+ self.interval_vllm_hit_tokens += delta
588
+
589
+ @thread_safe
590
+ def update_interval_prompt_tokens(self, delta: int):
591
+ self.interval_prompt_tokens += delta
592
+
593
+ def _clear(self):
594
+ """
595
+ Clear all the distribution stats
596
+ """
597
+ self.interval_retrieve_requests = 0
598
+ self.interval_store_requests = 0
599
+ self.interval_lookup_requests = 0
600
+
601
+ self.interval_requested_tokens = 0
602
+ self.interval_hit_tokens = 0
603
+ self.interval_stored_tokens = 0
604
+ self.interval_lookup_tokens = 0
605
+ self.interval_lookup_hits = 0
606
+ self.interval_vllm_hit_tokens = 0
607
+ self.interval_prompt_tokens = 0
608
+
609
+ self.interval_num_slow_retrieval_by_time = 0
610
+ self.interval_num_slow_retrieval_by_speed = 0
611
+
612
+ self.interval_remote_read_requests = 0
613
+ self.interval_remote_read_bytes = 0
614
+ self.interval_remote_write_requests = 0
615
+ self.interval_remote_write_bytes = 0
616
+
617
+ self.interval_remote_time_to_get.clear()
618
+ self.interval_remote_time_to_put.clear()
619
+ self.interval_remote_time_to_get_sync.clear()
620
+
621
+ self.interval_remote_ping_latency = 0
622
+ self.interval_remote_ping_errors = 0
623
+ self.interval_remote_ping_success = 0
624
+ self.interval_remote_ping_error_code = 0
625
+
626
+ self.interval_local_cpu_evict_count = 0
627
+ self.interval_local_cpu_evict_keys_count = 0
628
+ self.interval_local_cpu_evict_failed_count = 0
629
+
630
+ self.interval_forced_unpin_count = 0
631
+
632
+ self.interval_p2p_requests = 0
633
+ self.interval_p2p_transferred_tokens = 0
634
+
635
+ self.interval_lookup_0_hit_requests = 0
636
+
637
+ new_retrieve_requests = {}
638
+ for request_id, retrieve_stats in self.retrieve_requests.items():
639
+ if retrieve_stats.end_time == 0:
640
+ new_retrieve_requests[request_id] = retrieve_stats
641
+ self.retrieve_requests = new_retrieve_requests
642
+
643
+ new_store_requests = {}
644
+ for request_id, store_stats in self.store_requests.items():
645
+ if store_stats.end_time == 0:
646
+ new_store_requests[request_id] = store_stats
647
+ self.store_requests = new_store_requests
648
+
649
+ new_p2p_requests = {}
650
+ for request_id, p2p_stats in self.p2p_requests.items():
651
+ if p2p_stats.end_time == 0:
652
+ new_p2p_requests[request_id] = p2p_stats
653
+ self.p2p_requests = new_p2p_requests
654
+
655
+ new_lookup_requests = {}
656
+ for request_id, lookup_stats in self.lookup_requests.items():
657
+ if not lookup_stats.is_finished:
658
+ new_lookup_requests[request_id] = lookup_stats
659
+ self.lookup_requests = new_lookup_requests
660
+
661
+ self.interval_request_cache_lifespan.clear()
662
+ self.reuse_chunk_id = 0
663
+
664
+ @thread_safe
665
+ def get_stats_and_clear(self) -> LMCacheStats:
666
+ """
667
+ This function should be called with by prometheus adapter with
668
+ a specific interval.
669
+ The function will return the latest states between the current
670
+ call and the previous call.
671
+ """
672
+ # Calculate retrieve hit rate based on requests finished in this interval
673
+ finished_retrieve_stats = [
674
+ s for s in self.retrieve_requests.values() if s.end_time != 0
675
+ ]
676
+ sum_finished_retrieve_requested_tokens = sum(
677
+ s.num_tokens for s in finished_retrieve_stats
678
+ )
679
+ sum_finished_retrieve_hit_tokens = sum(
680
+ s.local_hit_tokens + s.remote_hit_tokens for s in finished_retrieve_stats
681
+ )
682
+ retrieve_hit_rate = (
683
+ 1
684
+ if len(finished_retrieve_stats) == 0
685
+ or sum_finished_retrieve_requested_tokens == 0
686
+ else sum_finished_retrieve_hit_tokens
687
+ / sum_finished_retrieve_requested_tokens
688
+ )
689
+
690
+ # Calculate lookup hit rate based on requests finished in this interval
691
+ finished_lookup_stats = [
692
+ s for s in self.lookup_requests.values() if s.is_finished
693
+ ]
694
+ sum_finished_lookup_requested = sum(s.num_tokens for s in finished_lookup_stats)
695
+ sum_finished_lookup_hit = sum(s.hit_tokens for s in finished_lookup_stats)
696
+ lookup_hit_rate = (
697
+ 0
698
+ if sum_finished_lookup_requested == 0
699
+ else sum_finished_lookup_hit / sum_finished_lookup_requested
700
+ )
701
+
702
+ def filter_out_zeros(stats: Iterable[float]) -> List[float]:
703
+ return [x for x in stats if x != 0]
704
+
705
+ time_to_retrieve = filter_out_zeros(
706
+ stats.time_to_retrieve() for stats in self.retrieve_requests.values()
707
+ )
708
+
709
+ time_to_store = filter_out_zeros(
710
+ stats.time_to_store() for stats in self.store_requests.values()
711
+ )
712
+
713
+ time_to_lookup = filter_out_zeros(
714
+ stats.time_to_lookup() for stats in self.lookup_requests.values()
715
+ )
716
+
717
+ retrieve_speed = filter_out_zeros(
718
+ stats.retrieve_speed() for stats in self.retrieve_requests.values()
719
+ )
720
+
721
+ store_speed = filter_out_zeros(
722
+ stats.store_speed() for stats in self.store_requests.values()
723
+ )
724
+
725
+ # Granular profiling measurements
726
+ retrieve_process_tokens_time = filter_out_zeros(
727
+ stats.process_tokens_time for stats in self.retrieve_requests.values()
728
+ )
729
+ retrieve_broadcast_time = filter_out_zeros(
730
+ stats.broadcast_time for stats in self.retrieve_requests.values()
731
+ )
732
+ retrieve_to_gpu_time = filter_out_zeros(
733
+ stats.to_gpu_time for stats in self.retrieve_requests.values()
734
+ )
735
+ remote_backend_batched_get_blocking_time = filter_out_zeros(
736
+ stats.detailed_metrics.get("remote_backend_batched_get_blocking_time", 0.0)
737
+ for stats in self.retrieve_requests.values()
738
+ )
739
+ instrumented_connector_batched_get_time = filter_out_zeros(
740
+ stats.detailed_metrics.get("instrumented_connector_batched_get_time", 0.0)
741
+ for stats in self.retrieve_requests.values()
742
+ )
743
+ store_process_tokens_time = filter_out_zeros(
744
+ stats.process_tokens_time for stats in self.store_requests.values()
745
+ )
746
+ store_from_gpu_time = filter_out_zeros(
747
+ stats.from_gpu_time for stats in self.store_requests.values()
748
+ )
749
+ store_put_time = filter_out_zeros(
750
+ stats.put_time for stats in self.store_requests.values()
751
+ )
752
+
753
+ p2p_time_to_transfer = filter_out_zeros(
754
+ stats.time_to_transfer() for stats in self.p2p_requests.values()
755
+ )
756
+
757
+ p2p_transfer_speed = filter_out_zeros(
758
+ stats.transfer_speed() for stats in self.p2p_requests.values()
759
+ )
760
+
761
+ request_lookup_hit_rates = filter_out_zeros(
762
+ stats.hit_rate()
763
+ for stats in self.lookup_requests.values()
764
+ if stats.is_finished
765
+ )
766
+
767
+ request_lifespan = list(self.interval_request_cache_lifespan.values())
768
+
769
+ ret = LMCacheStats(
770
+ interval_retrieve_requests=self.interval_retrieve_requests,
771
+ interval_store_requests=self.interval_store_requests,
772
+ interval_lookup_requests=self.interval_lookup_requests,
773
+ interval_requested_tokens=self.interval_requested_tokens,
774
+ interval_hit_tokens=self.interval_hit_tokens,
775
+ interval_stored_tokens=self.interval_stored_tokens,
776
+ interval_lookup_tokens=self.interval_lookup_tokens,
777
+ interval_lookup_hits=self.interval_lookup_hits,
778
+ interval_remote_read_requests=self.interval_remote_read_requests,
779
+ interval_remote_read_bytes=self.interval_remote_read_bytes,
780
+ interval_remote_write_requests=self.interval_remote_write_requests,
781
+ interval_remote_write_bytes=self.interval_remote_write_bytes,
782
+ interval_remote_time_to_get=self.interval_remote_time_to_get.copy(),
783
+ interval_remote_time_to_put=self.interval_remote_time_to_put.copy(),
784
+ interval_remote_time_to_get_sync=self.interval_remote_time_to_get_sync.copy(),
785
+ interval_remote_ping_latency=self.interval_remote_ping_latency,
786
+ interval_remote_ping_errors=self.interval_remote_ping_errors,
787
+ interval_remote_ping_success=self.interval_remote_ping_success,
788
+ interval_remote_ping_error_code=self.interval_remote_ping_error_code,
789
+ retrieve_hit_rate=retrieve_hit_rate,
790
+ lookup_hit_rate=lookup_hit_rate,
791
+ interval_local_cpu_evict_count=self.interval_local_cpu_evict_count,
792
+ interval_local_cpu_evict_keys_count=self.interval_local_cpu_evict_keys_count,
793
+ interval_local_cpu_evict_failed_count=self.interval_local_cpu_evict_failed_count,
794
+ interval_forced_unpin_count=self.interval_forced_unpin_count,
795
+ local_cache_usage_bytes=self.local_cache_usage_bytes,
796
+ remote_cache_usage_bytes=self.remote_cache_usage_bytes,
797
+ local_storage_usage_bytes=self.local_storage_usage_bytes,
798
+ active_memory_objs_count=self.active_memory_objs_count,
799
+ pinned_memory_objs_count=self.pinned_memory_objs_count,
800
+ time_to_retrieve=time_to_retrieve,
801
+ time_to_store=time_to_store,
802
+ time_to_lookup=time_to_lookup,
803
+ retrieve_speed=retrieve_speed,
804
+ store_speed=store_speed,
805
+ retrieve_process_tokens_time=retrieve_process_tokens_time,
806
+ retrieve_broadcast_time=retrieve_broadcast_time,
807
+ retrieve_to_gpu_time=retrieve_to_gpu_time,
808
+ remote_backend_batched_get_blocking_time=remote_backend_batched_get_blocking_time, # noqa: E501
809
+ instrumented_connector_batched_get_time=instrumented_connector_batched_get_time, # noqa: E501
810
+ store_process_tokens_time=store_process_tokens_time,
811
+ store_from_gpu_time=store_from_gpu_time,
812
+ store_put_time=store_put_time,
813
+ interval_vllm_hit_tokens=self.interval_vllm_hit_tokens,
814
+ interval_p2p_requests=self.interval_p2p_requests,
815
+ interval_num_slow_retrieval_by_time=self.interval_num_slow_retrieval_by_time,
816
+ interval_num_slow_retrieval_by_speed=self.interval_num_slow_retrieval_by_speed,
817
+ interval_p2p_transferred_tokens=self.interval_p2p_transferred_tokens,
818
+ p2p_time_to_transfer=p2p_time_to_transfer,
819
+ p2p_transfer_speed=p2p_transfer_speed,
820
+ interval_lookup_hit_rates=request_lookup_hit_rates,
821
+ interval_request_cache_lifespan=request_lifespan,
822
+ interval_prompt_tokens=self.interval_prompt_tokens,
823
+ interval_lookup_0_hit_requests=self.interval_lookup_0_hit_requests,
824
+ )
825
+ self._clear()
826
+ return ret
827
+
828
+ _instance = None
829
+
830
+ @staticmethod
831
+ def GetOrCreate() -> "LMCStatsMonitor":
832
+ if LMCStatsMonitor._instance is None:
833
+ LMCStatsMonitor._instance = LMCStatsMonitor()
834
+ return LMCStatsMonitor._instance
835
+
836
+ @staticmethod
837
+ def DestroyInstance():
838
+ LMCStatsMonitor._instance = None
839
+
840
+ @staticmethod
841
+ def unregister_all_metrics():
842
+ collectors = list(REGISTRY._collector_to_names.keys())
843
+ for collector in collectors:
844
+ try:
845
+ REGISTRY.unregister(collector)
846
+ except KeyError:
847
+ pass
848
+
849
+
850
+ class PrometheusLogger:
851
+ _gauge_cls = prometheus_client.Gauge
852
+ _counter_cls = prometheus_client.Counter
853
+ _histogram_cls = prometheus_client.Histogram
854
+
855
+ def _create_counter(
856
+ self, name: str, documentation: str, labelnames: List[str]
857
+ ) -> prometheus_client.Counter:
858
+ """Create a Counter and register it for reset_counters()."""
859
+ counter = self._counter_cls(
860
+ name=name, documentation=documentation, labelnames=labelnames
861
+ )
862
+ self._counters.append(counter)
863
+ return counter
864
+
865
+ def _create_histogram(
866
+ self,
867
+ name: str,
868
+ documentation: str,
869
+ labelnames: List[str],
870
+ buckets: Sequence[float],
871
+ ) -> prometheus_client.Histogram:
872
+ """Create a Histogram and register it for reset_histograms().
873
+
874
+ If the ``config`` object's extra_config contains a key
875
+ ``histogram_bucket_<short_name>`` (where ``<short_name>``
876
+ is the metric name without the ``lmcache:`` prefix),
877
+ the value will be used as the bucket list, overriding
878
+ the default *buckets* argument.
879
+ """
880
+ short_name = name.split(":", 1)[-1]
881
+ config_key = "histogram_bucket_%s" % short_name
882
+ custom = (
883
+ self.config.get_extra_config_value(config_key)
884
+ if self.config is not None
885
+ else None
886
+ )
887
+ if custom is not None:
888
+ buckets = custom
889
+ logger.info(
890
+ "Using custom buckets for histogram %s from extra_config key '%s'",
891
+ name,
892
+ config_key,
893
+ )
894
+ histogram = self._histogram_cls(
895
+ name=name,
896
+ documentation=documentation,
897
+ labelnames=labelnames,
898
+ buckets=buckets,
899
+ )
900
+ self._histograms.append(histogram)
901
+ return histogram
902
+
903
+ def __init__(
904
+ self,
905
+ metadata: LMCacheMetadata,
906
+ config: Optional["LMCacheEngineConfig"] = None,
907
+ ):
908
+ # Ensure PROMETHEUS_MULTIPROC_DIR is set before any metric registration
909
+ if "PROMETHEUS_MULTIPROC_DIR" not in os.environ:
910
+ default_dir = "/tmp/lmcache_prometheus"
911
+ os.environ["PROMETHEUS_MULTIPROC_DIR"] = default_dir
912
+ if not os.path.exists(default_dir):
913
+ os.makedirs(default_dir, exist_ok=True)
914
+
915
+ self.metadata = metadata
916
+ self.config = config
917
+
918
+ self.labels = self._metadata_to_labels(metadata)
919
+ labelnames = list(self.labels.keys())
920
+
921
+ # List to track all counters/histograms for reset methods
922
+ self._counters: List[prometheus_client.Counter] = []
923
+ self._histograms: List[prometheus_client.Histogram] = []
924
+
925
+ self.counter_num_retrieve_requests = self._create_counter(
926
+ name="lmcache:num_retrieve_requests",
927
+ documentation="Total number of retrieve requests sent to lmcache",
928
+ labelnames=labelnames,
929
+ )
930
+
931
+ self.counter_num_store_requests = self._create_counter(
932
+ name="lmcache:num_store_requests",
933
+ documentation="Total number of store requests sent to lmcache",
934
+ labelnames=labelnames,
935
+ )
936
+
937
+ self.counter_num_lookup_requests = self._create_counter(
938
+ name="lmcache:num_lookup_requests",
939
+ documentation="Total number of lookup requests sent to lmcache",
940
+ labelnames=labelnames,
941
+ )
942
+
943
+ self.counter_num_requested_tokens = self._create_counter(
944
+ name="lmcache:num_requested_tokens",
945
+ documentation="Total number of tokens requested from lmcache",
946
+ labelnames=labelnames,
947
+ )
948
+
949
+ self.counter_num_hit_tokens = self._create_counter(
950
+ name="lmcache:num_hit_tokens",
951
+ documentation="Total number of tokens hit in lmcache",
952
+ labelnames=labelnames,
953
+ )
954
+
955
+ self.counter_num_stored_tokens = self._create_counter(
956
+ name="lmcache:num_stored_tokens",
957
+ documentation=(
958
+ "Total number of tokens stored in lmcache including evicted ones"
959
+ ),
960
+ labelnames=labelnames,
961
+ )
962
+
963
+ self.counter_num_lookup_tokens = self._create_counter(
964
+ name="lmcache:num_lookup_tokens",
965
+ documentation="Total number of tokens requested in lookup from lmcache",
966
+ labelnames=labelnames,
967
+ )
968
+
969
+ self.counter_num_lookup_hits = self._create_counter(
970
+ name="lmcache:num_lookup_hits",
971
+ documentation="Total number of tokens hit in lookup from lmcache",
972
+ labelnames=labelnames,
973
+ )
974
+
975
+ self.counter_num_vllm_hit_tokens = self._create_counter(
976
+ name="lmcache:num_vllm_hit_tokens",
977
+ documentation="Number of hit tokens in vllm",
978
+ labelnames=labelnames,
979
+ )
980
+
981
+ self.counter_num_prompt_tokens = self._create_counter(
982
+ name="lmcache:num_prompt_tokens",
983
+ documentation="Number of prompt tokens in lmcache",
984
+ labelnames=labelnames,
985
+ )
986
+
987
+ self.counter_num_remote_read_requests = self._create_counter(
988
+ name="lmcache:num_remote_read_requests",
989
+ documentation="Total number of requests read from "
990
+ "remote backends in lmcache",
991
+ labelnames=labelnames,
992
+ )
993
+
994
+ self.counter_num_remote_read_bytes = self._create_counter(
995
+ name="lmcache:num_remote_read_bytes",
996
+ documentation="Total number of bytes read from remote backends in lmcache",
997
+ labelnames=labelnames,
998
+ )
999
+
1000
+ self.counter_num_remote_write_requests = self._create_counter(
1001
+ name="lmcache:num_remote_write_requests",
1002
+ documentation="Total number of requests write to "
1003
+ "remote backends in lmcache",
1004
+ labelnames=labelnames,
1005
+ )
1006
+
1007
+ self.counter_num_remote_write_bytes = self._create_counter(
1008
+ name="lmcache:num_remote_write_bytes",
1009
+ documentation="Total number of bytes write to remote backends in lmcache",
1010
+ labelnames=labelnames,
1011
+ )
1012
+
1013
+ self.counter_local_cpu_evict_count = self._create_counter(
1014
+ name="lmcache:local_cpu_evict_count",
1015
+ documentation="Total number of evict in local cpu backend",
1016
+ labelnames=labelnames,
1017
+ )
1018
+
1019
+ self.counter_local_cpu_evict_keys_count = self._create_counter(
1020
+ name="lmcache:local_cpu_evict_keys_count",
1021
+ documentation="Total number of evict keys in local cpu backend",
1022
+ labelnames=labelnames,
1023
+ )
1024
+
1025
+ self.counter_local_cpu_evict_failed_count = self._create_counter(
1026
+ name="lmcache:local_cpu_evict_failed_count",
1027
+ documentation="Total number of failed eviction in local cpu backend",
1028
+ labelnames=labelnames,
1029
+ )
1030
+
1031
+ self.counter_forced_unpin_count = self._create_counter(
1032
+ name="lmcache:forced_unpin_count",
1033
+ documentation="Total number of forced unpin due to timeout",
1034
+ labelnames=labelnames,
1035
+ )
1036
+
1037
+ self.counter_lookup_0_hit_requests = self._create_counter(
1038
+ name="lmcache:lookup_0_hit_requests",
1039
+ documentation="Total number of 0 hit lookup requests",
1040
+ labelnames=labelnames,
1041
+ )
1042
+
1043
+ self.counter_num_slow_retrieval_by_time = self._create_counter(
1044
+ name="lmcache:num_slow_retrieval_by_time",
1045
+ documentation="Total number of slow retrievals by time threshold",
1046
+ labelnames=labelnames,
1047
+ )
1048
+
1049
+ self.counter_num_slow_retrieval_by_speed = self._create_counter(
1050
+ name="lmcache:num_slow_retrieval_by_speed",
1051
+ documentation="Total number of slow retrievals by speed threshold",
1052
+ labelnames=labelnames,
1053
+ )
1054
+
1055
+ self.gauge_retrieve_hit_rate = self._gauge_cls(
1056
+ name="lmcache:retrieve_hit_rate",
1057
+ documentation="Hit rate of lmcache retrieve requests since last log",
1058
+ labelnames=labelnames,
1059
+ multiprocess_mode="livemostrecent",
1060
+ )
1061
+
1062
+ self.gauge_lookup_hit_rate = self._gauge_cls(
1063
+ name="lmcache:lookup_hit_rate",
1064
+ documentation="Hit rate of lmcache lookup requests since last log",
1065
+ labelnames=labelnames,
1066
+ multiprocess_mode="livemostrecent",
1067
+ )
1068
+
1069
+ self.gauge_local_cache_usage = self._gauge_cls(
1070
+ name="lmcache:local_cache_usage",
1071
+ documentation="Local cache usage (bytes) of lmcache",
1072
+ labelnames=labelnames,
1073
+ multiprocess_mode="sum",
1074
+ )
1075
+
1076
+ self.gauge_remote_cache_usage = self._gauge_cls(
1077
+ name="lmcache:remote_cache_usage",
1078
+ documentation="Remote cache usage (bytes) of lmcache",
1079
+ labelnames=labelnames,
1080
+ multiprocess_mode="sum",
1081
+ )
1082
+
1083
+ self.gauge_local_storage_usage = self._gauge_cls(
1084
+ name="lmcache:local_storage_usage",
1085
+ documentation="Local storage usage (bytes) of lmcache",
1086
+ labelnames=labelnames,
1087
+ multiprocess_mode="sum",
1088
+ )
1089
+
1090
+ self.gauge_active_memory_objs_count = self._gauge_cls(
1091
+ name="lmcache:active_memory_objs_count",
1092
+ documentation="The number of active memory objects",
1093
+ labelnames=labelnames,
1094
+ multiprocess_mode="sum",
1095
+ )
1096
+
1097
+ self.gauge_pinned_memory_objs_count = self._gauge_cls(
1098
+ name="lmcache:pinned_memory_objs_count",
1099
+ documentation="The number of pinned memory objects",
1100
+ labelnames=labelnames,
1101
+ multiprocess_mode="sum",
1102
+ )
1103
+
1104
+ time_to_retrieve_buckets = [
1105
+ 0.001,
1106
+ 0.005,
1107
+ 0.01,
1108
+ 0.02,
1109
+ 0.04,
1110
+ 0.06,
1111
+ 0.08,
1112
+ 0.1,
1113
+ 0.25,
1114
+ 0.5,
1115
+ 0.75,
1116
+ 1.0,
1117
+ 2.5,
1118
+ 5.0,
1119
+ 7.5,
1120
+ 10.0,
1121
+ ]
1122
+ self.histogram_time_to_retrieve = self._create_histogram(
1123
+ name="lmcache:time_to_retrieve",
1124
+ documentation="Time to retrieve from lmcache (seconds)",
1125
+ labelnames=labelnames,
1126
+ buckets=time_to_retrieve_buckets,
1127
+ )
1128
+
1129
+ time_to_store_buckets = [
1130
+ 0.001,
1131
+ 0.005,
1132
+ 0.01,
1133
+ 0.02,
1134
+ 0.04,
1135
+ 0.06,
1136
+ 0.08,
1137
+ 0.1,
1138
+ 0.25,
1139
+ 0.5,
1140
+ 0.75,
1141
+ 1.0,
1142
+ 2.5,
1143
+ 5.0,
1144
+ 7.5,
1145
+ 10.0,
1146
+ ]
1147
+ self.histogram_time_to_store = self._create_histogram(
1148
+ name="lmcache:time_to_store",
1149
+ documentation="Time to store to lmcache (seconds)",
1150
+ labelnames=labelnames,
1151
+ buckets=time_to_store_buckets,
1152
+ )
1153
+
1154
+ time_to_lookup_buckets = [
1155
+ 0.00001 * 2**i for i in range(20)
1156
+ ] # 0.01 ms to 5000 ms
1157
+ self.histogram_time_to_lookup = self._create_histogram(
1158
+ name="lmcache:time_to_lookup",
1159
+ documentation="Time to lookup in lmcache (seconds)",
1160
+ labelnames=labelnames,
1161
+ buckets=time_to_lookup_buckets,
1162
+ )
1163
+
1164
+ profiling_buckets = [0.00001 * 2**i for i in range(20)] # 0.01 ms to 5000 ms
1165
+ self.histogram_retrieve_process_tokens_time = self._create_histogram(
1166
+ name="lmcache:retrieve_process_tokens_time",
1167
+ documentation="Time to process tokens in retrieve (seconds)",
1168
+ labelnames=labelnames,
1169
+ buckets=profiling_buckets,
1170
+ )
1171
+ self.histogram_retrieve_broadcast_time = self._create_histogram(
1172
+ name="lmcache:retrieve_broadcast_time",
1173
+ documentation="Time to broadcast memory objects in retrieve (seconds)",
1174
+ labelnames=labelnames,
1175
+ buckets=profiling_buckets,
1176
+ )
1177
+ self.histogram_retrieve_to_gpu_time = self._create_histogram(
1178
+ name="lmcache:retrieve_to_gpu_time",
1179
+ documentation="Time to move data to GPU in retrieve (seconds)",
1180
+ labelnames=labelnames,
1181
+ buckets=profiling_buckets,
1182
+ )
1183
+ self.histogram_remote_backend_batched_get_blocking_time = (
1184
+ self._create_histogram(
1185
+ name="lmcache:remote_backend_batched_get_blocking_time",
1186
+ documentation="Time to get data from remote backend (seconds)",
1187
+ labelnames=labelnames,
1188
+ buckets=profiling_buckets,
1189
+ )
1190
+ )
1191
+ self.histogram_instrumented_connector_batched_get_time = self._create_histogram(
1192
+ name="lmcache:instrumented_connector_batched_get_time",
1193
+ documentation="Time used by the connector (seconds)",
1194
+ labelnames=labelnames,
1195
+ buckets=profiling_buckets,
1196
+ )
1197
+ self.histogram_store_process_tokens_time = self._create_histogram(
1198
+ name="lmcache:store_process_tokens_time",
1199
+ documentation="Time to process tokens in store (seconds)",
1200
+ labelnames=labelnames,
1201
+ buckets=profiling_buckets,
1202
+ )
1203
+ self.histogram_store_from_gpu_time = self._create_histogram(
1204
+ name="lmcache:store_from_gpu_time",
1205
+ documentation="Time to move data from GPU in store (seconds)",
1206
+ labelnames=labelnames,
1207
+ buckets=profiling_buckets,
1208
+ )
1209
+ self.histogram_store_put_time = self._create_histogram(
1210
+ name="lmcache:store_put_time",
1211
+ documentation="Time to put data to storage in store (seconds)",
1212
+ labelnames=labelnames,
1213
+ buckets=profiling_buckets,
1214
+ )
1215
+
1216
+ retrieve_speed_buckets = [
1217
+ 1,
1218
+ 8,
1219
+ 16,
1220
+ 32,
1221
+ 64,
1222
+ 128,
1223
+ 256,
1224
+ 512,
1225
+ 1024,
1226
+ 2048,
1227
+ 4096,
1228
+ 8192,
1229
+ 16384,
1230
+ 32768,
1231
+ 65536,
1232
+ ]
1233
+ self.histogram_retrieve_speed = self._create_histogram(
1234
+ name="lmcache:retrieve_speed",
1235
+ documentation="Retrieve speed of lmcache (tokens per second)",
1236
+ labelnames=labelnames,
1237
+ buckets=retrieve_speed_buckets,
1238
+ )
1239
+
1240
+ store_speed_buckets = [
1241
+ 1,
1242
+ 8,
1243
+ 16,
1244
+ 32,
1245
+ 64,
1246
+ 128,
1247
+ 256,
1248
+ 512,
1249
+ 1024,
1250
+ 2048,
1251
+ 4096,
1252
+ 8192,
1253
+ 16384,
1254
+ 32768,
1255
+ 65536,
1256
+ ]
1257
+ self.histogram_store_speed = self._create_histogram(
1258
+ name="lmcache:store_speed",
1259
+ documentation="Store speed of lmcache (tokens per second)",
1260
+ labelnames=labelnames,
1261
+ buckets=store_speed_buckets,
1262
+ )
1263
+
1264
+ # P2P transfer metrics
1265
+ p2p_time_buckets = [
1266
+ 0.001, # 1ms
1267
+ 0.005, # 5ms
1268
+ 0.01, # 10ms
1269
+ 0.02, # 20ms
1270
+ 0.04, # 40ms
1271
+ 0.06, # 60ms
1272
+ 0.08, # 80ms
1273
+ 0.1, # 100ms
1274
+ 0.25, # 250ms
1275
+ 0.5, # 500ms
1276
+ 0.75, # 750ms
1277
+ 1.0, # 1s
1278
+ 2.5, # 2.5s
1279
+ 5.0, # 5s
1280
+ 7.5, # 7.5s
1281
+ 10.0, # 10s
1282
+ ]
1283
+ self.histogram_p2p_time_to_transfer = self._create_histogram(
1284
+ name="lmcache:p2p_time_to_transfer",
1285
+ documentation="Time to transfer via P2P (seconds)",
1286
+ labelnames=labelnames,
1287
+ buckets=p2p_time_buckets,
1288
+ )
1289
+
1290
+ p2p_speed_buckets = [
1291
+ 1,
1292
+ 8,
1293
+ 16,
1294
+ 32,
1295
+ 64,
1296
+ 128,
1297
+ 256,
1298
+ 512,
1299
+ 1024,
1300
+ 2048,
1301
+ 4096,
1302
+ 8192,
1303
+ 16384,
1304
+ 32768,
1305
+ 65536,
1306
+ ]
1307
+ self.histogram_p2p_transfer_speed = self._create_histogram(
1308
+ name="lmcache:p2p_transfer_speed",
1309
+ documentation="P2P transfer speed (tokens per second)",
1310
+ labelnames=labelnames,
1311
+ buckets=p2p_speed_buckets,
1312
+ )
1313
+
1314
+ remote_time_to_get = [
1315
+ 1,
1316
+ 5,
1317
+ 10,
1318
+ 20,
1319
+ 40,
1320
+ 60,
1321
+ 80,
1322
+ 100,
1323
+ 250,
1324
+ 500,
1325
+ 750,
1326
+ 1000,
1327
+ 2500,
1328
+ 5000,
1329
+ 7500,
1330
+ 10000,
1331
+ ]
1332
+ self.histogram_remote_time_to_get = self._create_histogram(
1333
+ name="lmcache:remote_time_to_get",
1334
+ documentation="Time to get from remote backends (ms)",
1335
+ labelnames=labelnames,
1336
+ buckets=remote_time_to_get,
1337
+ )
1338
+
1339
+ remote_time_to_put = [
1340
+ 1,
1341
+ 5,
1342
+ 10,
1343
+ 20,
1344
+ 40,
1345
+ 60,
1346
+ 80,
1347
+ 100,
1348
+ 250,
1349
+ 500,
1350
+ 750,
1351
+ 1000,
1352
+ 2500,
1353
+ 5000,
1354
+ 7500,
1355
+ 10000,
1356
+ ]
1357
+ self.histogram_remote_time_to_put = self._create_histogram(
1358
+ name="lmcache:remote_time_to_put",
1359
+ documentation="Time to put to remote backends (ms)",
1360
+ labelnames=labelnames,
1361
+ buckets=remote_time_to_put,
1362
+ )
1363
+
1364
+ remote_time_to_get_sync = [
1365
+ 1,
1366
+ 5,
1367
+ 10,
1368
+ 20,
1369
+ 40,
1370
+ 60,
1371
+ 80,
1372
+ 100,
1373
+ 250,
1374
+ 500,
1375
+ 750,
1376
+ 1000,
1377
+ 2500,
1378
+ 5000,
1379
+ 7500,
1380
+ 10000,
1381
+ ]
1382
+ self.histogram_remote_time_to_get_sync = self._create_histogram(
1383
+ name="lmcache:remote_time_to_get_sync",
1384
+ documentation="Time to get from remote backends synchronously(ms)",
1385
+ labelnames=labelnames,
1386
+ buckets=remote_time_to_get_sync,
1387
+ )
1388
+
1389
+ request_cache_hit_rate = [
1390
+ 0.1,
1391
+ 0.2,
1392
+ 0.3,
1393
+ 0.4,
1394
+ 0.5,
1395
+ 0.6,
1396
+ 0.7,
1397
+ 0.8,
1398
+ 0.9,
1399
+ 1.0,
1400
+ ]
1401
+ self.histogram_request_cache_hit_rate = self._create_histogram(
1402
+ name="lmcache:request_cache_hit_rate",
1403
+ documentation="Request cache hit rate",
1404
+ labelnames=labelnames,
1405
+ buckets=request_cache_hit_rate,
1406
+ )
1407
+
1408
+ request_cache_lifespan_buckets = [
1409
+ 0,
1410
+ 1,
1411
+ 5,
1412
+ 10,
1413
+ 20,
1414
+ 40,
1415
+ 60,
1416
+ 80,
1417
+ 100,
1418
+ 250,
1419
+ 500,
1420
+ 750,
1421
+ 1000,
1422
+ 2500,
1423
+ 5000,
1424
+ ]
1425
+ self.histogram_request_cache_lifespan = self._create_histogram(
1426
+ name="lmcache:request_cache_lifespan",
1427
+ documentation="Request cache lifespan in minutes",
1428
+ labelnames=labelnames,
1429
+ buckets=request_cache_lifespan_buckets,
1430
+ )
1431
+
1432
+ # Ping latency metrics: use a gauge to record the latest ping latency
1433
+ self.gauge_remote_ping_latency = self._gauge_cls(
1434
+ name="lmcache:remote_ping_latency",
1435
+ documentation="Latest ping latency to remote backends (ms)",
1436
+ labelnames=labelnames,
1437
+ multiprocess_mode="livemostrecent",
1438
+ )
1439
+ self.counter_remote_ping_errors = self._create_counter(
1440
+ name="lmcache:remote_ping_errors",
1441
+ documentation="Number of ping errors to remote backends",
1442
+ labelnames=labelnames,
1443
+ )
1444
+ self.counter_remote_ping_successes = self._create_counter(
1445
+ name="lmcache:remote_ping_successes",
1446
+ documentation="Number of ping successes to remote backends",
1447
+ labelnames=labelnames,
1448
+ )
1449
+ self.gauge_remote_ping_error_code = self._gauge_cls(
1450
+ name="lmcache:remote_ping_error_code",
1451
+ documentation="Latest ping error code to remote backends",
1452
+ labelnames=labelnames,
1453
+ multiprocess_mode="livemostrecent",
1454
+ )
1455
+ self._dynamic_metrics(labelnames)
1456
+
1457
+ def _dynamic_metrics(self, labelnames):
1458
+ """
1459
+ Dynamically get value by lambda function while capture
1460
+ """
1461
+ self.local_cpu_hot_cache_count = self._gauge_cls(
1462
+ name="lmcache:local_cpu_hot_cache_count",
1463
+ documentation="The size of the hot_cache",
1464
+ labelnames=labelnames,
1465
+ multiprocess_mode="livemostrecent",
1466
+ ).labels(**self.labels)
1467
+ self.local_cpu_keys_in_request_count = self._gauge_cls(
1468
+ name="lmcache:local_cpu_keys_in_request_count",
1469
+ documentation="The size of the keys_in_request",
1470
+ labelnames=labelnames,
1471
+ multiprocess_mode="livemostrecent",
1472
+ ).labels(**self.labels)
1473
+ self.kv_msg_queue_size = self._gauge_cls(
1474
+ name="lmcache:kv_msg_queue_size",
1475
+ documentation="The size of the KV message queue in BatchedMessageSender",
1476
+ labelnames=labelnames,
1477
+ multiprocess_mode="livemostrecent",
1478
+ ).labels(**self.labels)
1479
+ self.remote_put_task_num = self._gauge_cls(
1480
+ name="lmcache:remote_put_task_num",
1481
+ documentation="The number of remote put tasks",
1482
+ labelnames=labelnames,
1483
+ multiprocess_mode="livemostrecent",
1484
+ ).labels(**self.labels)
1485
+ self.pin_monitor_pinned_objects_count = self._gauge_cls(
1486
+ name="lmcache:pin_monitor_pinned_objects_count",
1487
+ documentation="The number of pinned objects in PinMonitor",
1488
+ labelnames=labelnames,
1489
+ multiprocess_mode="livemostrecent",
1490
+ ).labels(**self.labels)
1491
+ self.lmcache_is_healthy = self._gauge_cls(
1492
+ name="lmcache:lmcache_is_healthy",
1493
+ documentation="The health status of LMCache (1=healthy, 0=unhealthy)",
1494
+ labelnames=labelnames,
1495
+ multiprocess_mode="livemostrecent",
1496
+ ).labels(**self.labels)
1497
+ self.get_blocking_failed_count = self._gauge_cls(
1498
+ name="lmcache:get_blocking_failed_count",
1499
+ documentation="The number of get blocking failed",
1500
+ labelnames=labelnames,
1501
+ multiprocess_mode="livemostrecent",
1502
+ ).labels(**self.labels)
1503
+ self.put_failed_count = self._gauge_cls(
1504
+ name="lmcache:put_failed_count",
1505
+ documentation="The number of put failed",
1506
+ labelnames=labelnames,
1507
+ multiprocess_mode="livemostrecent",
1508
+ ).labels(**self.labels)
1509
+
1510
+ event_statuses = ["ongoing", "done", "not_found"]
1511
+ for status in event_statuses:
1512
+ metric_name = f"storage_events_{status}_count"
1513
+ gauge = self._gauge_cls(
1514
+ name=f"lmcache:{metric_name}",
1515
+ documentation=f"The number of {status.replace('_', ' ')} events",
1516
+ labelnames=labelnames,
1517
+ multiprocess_mode="sum",
1518
+ ).labels(**self.labels)
1519
+ setattr(self, metric_name, gauge)
1520
+
1521
+ # Chunk statistics metrics (dynamic)
1522
+ self.chunk_statistics_enabled = self._gauge_cls(
1523
+ name="lmcache:chunk_statistics_enabled",
1524
+ documentation="Whether chunk statistics collection is enabled",
1525
+ labelnames=labelnames,
1526
+ multiprocess_mode="livemostrecent",
1527
+ ).labels(**self.labels)
1528
+ self.chunk_statistics_total_requests = self._gauge_cls(
1529
+ name="lmcache:chunk_statistics_total_requests",
1530
+ documentation="Total number of requests processed by chunk statistics",
1531
+ labelnames=labelnames,
1532
+ multiprocess_mode="livemostrecent",
1533
+ ).labels(**self.labels)
1534
+ self.chunk_statistics_total_chunks = self._gauge_cls(
1535
+ name="lmcache:chunk_statistics_total_chunks",
1536
+ documentation="Total number of chunks processed",
1537
+ labelnames=labelnames,
1538
+ multiprocess_mode="livemostrecent",
1539
+ ).labels(**self.labels)
1540
+ self.chunk_statistics_unique_chunks = self._gauge_cls(
1541
+ name="lmcache:chunk_statistics_unique_chunks",
1542
+ documentation="Number of unique chunks (estimated)",
1543
+ labelnames=labelnames,
1544
+ multiprocess_mode="livemostrecent",
1545
+ ).labels(**self.labels)
1546
+ self.chunk_statistics_reuse_rate = self._gauge_cls(
1547
+ name="lmcache:chunk_statistics_reuse_rate",
1548
+ documentation="Chunk reuse rate (0.0 to 1.0)",
1549
+ labelnames=labelnames,
1550
+ multiprocess_mode="livemostrecent",
1551
+ ).labels(**self.labels)
1552
+ self.chunk_statistics_bloom_filter_size_mb = self._gauge_cls(
1553
+ name="lmcache:chunk_statistics_bloom_filter_size_mb",
1554
+ documentation="Bloom Filter memory usage in MB",
1555
+ labelnames=labelnames,
1556
+ multiprocess_mode="livemostrecent",
1557
+ ).labels(**self.labels)
1558
+ self.chunk_statistics_bloom_filter_fill_rate = self._gauge_cls(
1559
+ name="lmcache:chunk_statistics_bloom_filter_fill_rate",
1560
+ documentation="Bloom Filter fill rate (0.0 to 1.0)",
1561
+ labelnames=labelnames,
1562
+ multiprocess_mode="livemostrecent",
1563
+ ).labels(**self.labels)
1564
+ self.chunk_statistics_file_count = self._gauge_cls(
1565
+ name="lmcache:chunk_statistics_file_count",
1566
+ documentation="Number of files created for file_hash strategy",
1567
+ labelnames=labelnames,
1568
+ multiprocess_mode="livemostrecent",
1569
+ ).labels(**self.labels)
1570
+ self.chunk_statistics_current_file_size = self._gauge_cls(
1571
+ name="lmcache:chunk_statistics_current_file_size",
1572
+ documentation="Current file size in bytes for file_hash strategy",
1573
+ labelnames=labelnames,
1574
+ multiprocess_mode="livemostrecent",
1575
+ ).labels(**self.labels)
1576
+
1577
+ # Connector metrics
1578
+ connector_metrics = [
1579
+ "scheduler_unfinished_requests_count",
1580
+ "connector_load_specs_count",
1581
+ "connector_request_trackers_count",
1582
+ "connector_kv_caches_count",
1583
+ "connector_layerwise_retrievers_count",
1584
+ "connector_invalid_block_ids_count",
1585
+ "connector_requests_priority_count",
1586
+ ]
1587
+
1588
+ for metric_name in connector_metrics:
1589
+ gauge = self._gauge_cls(
1590
+ name=f"lmcache:{metric_name}",
1591
+ documentation=f"The count of {metric_name.replace('_', ' ')}",
1592
+ labelnames=labelnames,
1593
+ multiprocess_mode="livemostrecent",
1594
+ ).labels(**self.labels)
1595
+ setattr(self, metric_name, gauge)
1596
+
1597
+ # PeriodicThread metrics
1598
+ self.periodic_threads_total_count = self._gauge_cls(
1599
+ name="lmcache:periodic_threads_total_count",
1600
+ documentation="Total number of registered periodic threads",
1601
+ labelnames=labelnames,
1602
+ multiprocess_mode="livemostrecent",
1603
+ ).labels(**self.labels)
1604
+ self.periodic_threads_running_count = self._gauge_cls(
1605
+ name="lmcache:periodic_threads_running_count",
1606
+ documentation="Number of running periodic threads",
1607
+ labelnames=labelnames,
1608
+ multiprocess_mode="livemostrecent",
1609
+ ).labels(**self.labels)
1610
+ self.periodic_threads_active_count = self._gauge_cls(
1611
+ name="lmcache:periodic_threads_active_count",
1612
+ documentation="Number of active periodic threads (recently executed)",
1613
+ labelnames=labelnames,
1614
+ multiprocess_mode="livemostrecent",
1615
+ ).labels(**self.labels)
1616
+
1617
+ # Per-level metrics for periodic threads
1618
+ for level_name in ["critical", "high", "medium", "low"]:
1619
+ total_gauge = self._gauge_cls(
1620
+ name=f"lmcache:periodic_threads_{level_name}_total",
1621
+ documentation=f"Total number of {level_name} level periodic threads",
1622
+ labelnames=labelnames,
1623
+ multiprocess_mode="livemostrecent",
1624
+ ).labels(**self.labels)
1625
+ setattr(self, f"periodic_threads_{level_name}_total", total_gauge)
1626
+
1627
+ running_gauge = self._gauge_cls(
1628
+ name=f"lmcache:periodic_threads_{level_name}_running",
1629
+ documentation=f"Number of running {level_name} level periodic threads",
1630
+ labelnames=labelnames,
1631
+ multiprocess_mode="livemostrecent",
1632
+ ).labels(**self.labels)
1633
+ setattr(self, f"periodic_threads_{level_name}_running", running_gauge)
1634
+
1635
+ active_gauge = self._gauge_cls(
1636
+ name=f"lmcache:periodic_threads_{level_name}_active",
1637
+ documentation=f"Number of active {level_name} level periodic threads",
1638
+ labelnames=labelnames,
1639
+ multiprocess_mode="livemostrecent",
1640
+ ).labels(**self.labels)
1641
+ setattr(self, f"periodic_threads_{level_name}_active", active_gauge)
1642
+
1643
+ def _log_gauge(self, gauge, data: Union[int, float]) -> None:
1644
+ # Convenience function for logging to gauge.
1645
+ gauge.labels(**self.labels).set(data)
1646
+
1647
+ def _log_counter(self, counter, data: Union[int, float]) -> None:
1648
+ # Convenience function for logging to counter.
1649
+ # Prevent ValueError from negative increment
1650
+ if data < 0:
1651
+ return
1652
+ counter.labels(**self.labels).inc(data)
1653
+
1654
+ def _log_histogram(self, histogram, data: Union[List[int], List[float]]) -> None:
1655
+ # Convenience function for logging to histogram.
1656
+ for value in data:
1657
+ histogram.labels(**self.labels).observe(value)
1658
+
1659
+ def log_prometheus(self, stats: LMCacheStats):
1660
+ self._log_counter(
1661
+ self.counter_num_retrieve_requests, stats.interval_retrieve_requests
1662
+ )
1663
+ self._log_counter(
1664
+ self.counter_num_store_requests, stats.interval_store_requests
1665
+ )
1666
+ self._log_counter(
1667
+ self.counter_num_lookup_requests, stats.interval_lookup_requests
1668
+ )
1669
+
1670
+ self._log_counter(
1671
+ self.counter_num_requested_tokens, stats.interval_requested_tokens
1672
+ )
1673
+ self._log_counter(self.counter_num_hit_tokens, stats.interval_hit_tokens)
1674
+ self._log_counter(self.counter_num_stored_tokens, stats.interval_stored_tokens)
1675
+ self._log_counter(self.counter_num_lookup_tokens, stats.interval_lookup_tokens)
1676
+ self._log_counter(self.counter_num_lookup_hits, stats.interval_lookup_hits)
1677
+ self._log_counter(self.counter_num_prompt_tokens, stats.interval_prompt_tokens)
1678
+ self._log_counter(
1679
+ self.counter_num_vllm_hit_tokens, stats.interval_vllm_hit_tokens
1680
+ )
1681
+
1682
+ self._log_counter(
1683
+ self.counter_num_remote_read_requests,
1684
+ stats.interval_remote_read_requests,
1685
+ )
1686
+ self._log_counter(
1687
+ self.counter_num_remote_read_bytes, stats.interval_remote_read_bytes
1688
+ )
1689
+ self._log_counter(
1690
+ self.counter_num_remote_write_requests,
1691
+ stats.interval_remote_write_requests,
1692
+ )
1693
+ self._log_counter(
1694
+ self.counter_num_remote_write_bytes,
1695
+ stats.interval_remote_write_bytes,
1696
+ )
1697
+ self._log_counter(
1698
+ self.counter_local_cpu_evict_count,
1699
+ stats.interval_local_cpu_evict_count,
1700
+ )
1701
+ self._log_counter(
1702
+ self.counter_local_cpu_evict_keys_count,
1703
+ stats.interval_local_cpu_evict_keys_count,
1704
+ )
1705
+ self._log_counter(
1706
+ self.counter_local_cpu_evict_failed_count,
1707
+ stats.interval_local_cpu_evict_failed_count,
1708
+ )
1709
+ self._log_counter(
1710
+ self.counter_forced_unpin_count,
1711
+ stats.interval_forced_unpin_count,
1712
+ )
1713
+ self._log_counter(
1714
+ self.counter_lookup_0_hit_requests,
1715
+ stats.interval_lookup_0_hit_requests,
1716
+ )
1717
+ self._log_counter(
1718
+ self.counter_num_slow_retrieval_by_time,
1719
+ stats.interval_num_slow_retrieval_by_time,
1720
+ )
1721
+ self._log_counter(
1722
+ self.counter_num_slow_retrieval_by_speed,
1723
+ stats.interval_num_slow_retrieval_by_speed,
1724
+ )
1725
+
1726
+ self._log_gauge(self.gauge_retrieve_hit_rate, stats.retrieve_hit_rate)
1727
+
1728
+ self._log_gauge(self.gauge_lookup_hit_rate, stats.lookup_hit_rate)
1729
+
1730
+ self._log_gauge(self.gauge_local_cache_usage, stats.local_cache_usage_bytes)
1731
+
1732
+ self._log_gauge(self.gauge_remote_cache_usage, stats.remote_cache_usage_bytes)
1733
+
1734
+ self._log_gauge(self.gauge_local_storage_usage, stats.local_storage_usage_bytes)
1735
+
1736
+ self._log_histogram(self.histogram_time_to_retrieve, stats.time_to_retrieve)
1737
+
1738
+ self._log_histogram(self.histogram_time_to_store, stats.time_to_store)
1739
+
1740
+ self._log_histogram(self.histogram_time_to_lookup, stats.time_to_lookup)
1741
+
1742
+ self._log_histogram(self.histogram_retrieve_speed, stats.retrieve_speed)
1743
+
1744
+ self._log_histogram(self.histogram_store_speed, stats.store_speed)
1745
+
1746
+ self._log_histogram(
1747
+ self.histogram_retrieve_process_tokens_time,
1748
+ stats.retrieve_process_tokens_time,
1749
+ )
1750
+ self._log_histogram(
1751
+ self.histogram_retrieve_broadcast_time, stats.retrieve_broadcast_time
1752
+ )
1753
+ self._log_histogram(
1754
+ self.histogram_retrieve_to_gpu_time, stats.retrieve_to_gpu_time
1755
+ )
1756
+ self._log_histogram(
1757
+ self.histogram_remote_backend_batched_get_blocking_time,
1758
+ stats.remote_backend_batched_get_blocking_time,
1759
+ )
1760
+ self._log_histogram(
1761
+ self.histogram_instrumented_connector_batched_get_time,
1762
+ stats.instrumented_connector_batched_get_time,
1763
+ )
1764
+ self._log_histogram(
1765
+ self.histogram_store_process_tokens_time, stats.store_process_tokens_time
1766
+ )
1767
+ self._log_histogram(
1768
+ self.histogram_store_from_gpu_time, stats.store_from_gpu_time
1769
+ )
1770
+ self._log_histogram(self.histogram_store_put_time, stats.store_put_time)
1771
+
1772
+ self._log_histogram(
1773
+ self.histogram_p2p_time_to_transfer, stats.p2p_time_to_transfer
1774
+ )
1775
+
1776
+ self._log_histogram(self.histogram_p2p_transfer_speed, stats.p2p_transfer_speed)
1777
+
1778
+ self._log_histogram(
1779
+ self.histogram_remote_time_to_get, stats.interval_remote_time_to_get
1780
+ )
1781
+ self._log_histogram(
1782
+ self.histogram_remote_time_to_put, stats.interval_remote_time_to_put
1783
+ )
1784
+ self._log_histogram(
1785
+ self.histogram_remote_time_to_get_sync,
1786
+ stats.interval_remote_time_to_get_sync,
1787
+ )
1788
+ self._log_histogram(
1789
+ self.histogram_request_cache_hit_rate, stats.interval_lookup_hit_rates
1790
+ )
1791
+ self._log_histogram(
1792
+ self.histogram_request_cache_lifespan, stats.interval_request_cache_lifespan
1793
+ )
1794
+ self._log_gauge(
1795
+ self.gauge_remote_ping_latency, stats.interval_remote_ping_latency
1796
+ )
1797
+ self._log_counter(
1798
+ self.counter_remote_ping_errors, stats.interval_remote_ping_errors
1799
+ )
1800
+ self._log_counter(
1801
+ self.counter_remote_ping_successes, stats.interval_remote_ping_success
1802
+ )
1803
+ self._log_gauge(
1804
+ self.gauge_remote_ping_error_code, stats.interval_remote_ping_error_code
1805
+ )
1806
+ self._log_gauge(
1807
+ self.gauge_active_memory_objs_count, stats.active_memory_objs_count
1808
+ )
1809
+ self._log_gauge(
1810
+ self.gauge_pinned_memory_objs_count, stats.pinned_memory_objs_count
1811
+ )
1812
+
1813
+ @staticmethod
1814
+ def _metadata_to_labels(metadata: LMCacheMetadata):
1815
+ labels = {
1816
+ "model_name": metadata.model_name,
1817
+ "worker_id": metadata.worker_id,
1818
+ "role": metadata.role,
1819
+ }
1820
+ if metadata.served_model_name:
1821
+ labels["served_model_name"] = metadata.served_model_name
1822
+ return labels
1823
+
1824
+ _instance = None
1825
+
1826
+ @staticmethod
1827
+ def GetOrCreate(
1828
+ metadata: LMCacheMetadata,
1829
+ config: Optional["LMCacheEngineConfig"] = None,
1830
+ ) -> "PrometheusLogger":
1831
+ if PrometheusLogger._instance is None:
1832
+ PrometheusLogger._instance = PrometheusLogger(metadata, config=config)
1833
+ # assert PrometheusLogger._instance.metadata == metadata, \
1834
+ # "PrometheusLogger instance already created with different metadata"
1835
+ if PrometheusLogger._instance.metadata != metadata:
1836
+ logger.error(
1837
+ "PrometheusLogger instance already created with "
1838
+ "different metadata. This should not happen except "
1839
+ "in test"
1840
+ )
1841
+ return PrometheusLogger._instance
1842
+
1843
+ @staticmethod
1844
+ def GetInstance() -> "PrometheusLogger":
1845
+ assert PrometheusLogger._instance is not None, (
1846
+ "PrometheusLogger instance not created yet"
1847
+ )
1848
+ return PrometheusLogger._instance
1849
+
1850
+ @staticmethod
1851
+ def GetInstanceOrNone() -> Optional["PrometheusLogger"]:
1852
+ """
1853
+ Returns the singleton instance of PrometheusLogger if it exists,
1854
+ otherwise returns None.
1855
+ """
1856
+ return PrometheusLogger._instance
1857
+
1858
+ @thread_safe
1859
+ def reset_counters(self) -> None:
1860
+ """
1861
+ Reset all Prometheus Counter metrics by calling clear().
1862
+ After clearing, re-initialize with labels so metrics remain visible.
1863
+ """
1864
+ for counter in self._counters:
1865
+ counter.clear()
1866
+ # Re-initialize with labels to make metric visible again
1867
+ counter.labels(**self.labels)
1868
+
1869
+ @thread_safe
1870
+ def reset_histograms(self) -> None:
1871
+ """
1872
+ Reset all Prometheus Histogram metrics by calling clear().
1873
+ After clearing, re-initialize with labels so metrics remain visible.
1874
+ """
1875
+ for histogram in self._histograms:
1876
+ histogram.clear()
1877
+ # Re-initialize with labels to make metric visible again
1878
+ histogram.labels(**self.labels)
1879
+
1880
+
1881
+ def reset_observability_metrics() -> None:
1882
+ """
1883
+ Reset observability metrics to their initial state.
1884
+ """
1885
+
1886
+ prometheus_logger = PrometheusLogger.GetInstanceOrNone()
1887
+ if prometheus_logger is not None:
1888
+ prometheus_logger.reset_counters()
1889
+ prometheus_logger.reset_histograms()
1890
+
1891
+
1892
+ class LMCacheStatsLogger:
1893
+ def __init__(
1894
+ self,
1895
+ metadata: LMCacheMetadata,
1896
+ log_interval: int,
1897
+ config: Optional["LMCacheEngineConfig"] = None,
1898
+ ):
1899
+ self.metadata = metadata
1900
+ self.log_interval = log_interval
1901
+ self.monitor = LMCStatsMonitor.GetOrCreate()
1902
+ self.prometheus_logger = PrometheusLogger.GetOrCreate(metadata, config=config)
1903
+ self.lmc_usage_logger = ContinuousUsageContext.GetOrCreate(metadata)
1904
+ self.is_running = True
1905
+ # Event for interruptible sleep during shutdown
1906
+ self.shutdown_event = threading.Event()
1907
+
1908
+ self.thread = threading.Thread(
1909
+ target=self.log_worker, daemon=True, name="stats-logger-thread"
1910
+ )
1911
+ self.thread.start()
1912
+
1913
+ def log_worker(self):
1914
+ while self.is_running:
1915
+ stats = self.monitor.get_stats_and_clear()
1916
+ self.prometheus_logger.log_prometheus(stats)
1917
+ self.lmc_usage_logger.incr_or_send_stats(stats)
1918
+ # Use Event.wait() instead of time.sleep() for interruptible sleep
1919
+ # Returns True if event was set, False if timeout occurred
1920
+ self.shutdown_event.wait(self.log_interval)
1921
+
1922
+ def shutdown(self):
1923
+ """Shutdown the stats logger gracefully with immediate wake-up"""
1924
+ logger.info("Shutting down LMCacheStatsLogger...")
1925
+
1926
+ # Signal the worker thread to stop
1927
+ self.is_running = False
1928
+
1929
+ # Signal the event to wake up the thread immediately from sleep
1930
+ self.shutdown_event.set()
1931
+
1932
+ # Wait for thread with a reasonable timeout
1933
+ if self.thread.is_alive():
1934
+ # Since we wake up the thread immediately, use a shorter timeout
1935
+ # Just enough time for the thread to finish its current iteration
1936
+ timeout = 5.0
1937
+ logger.info(
1938
+ f"Waiting for stats logger thread to finish (timeout: {timeout}s)..."
1939
+ )
1940
+
1941
+ try:
1942
+ self.thread.join(timeout=timeout)
1943
+
1944
+ if self.thread.is_alive():
1945
+ logger.warning(
1946
+ f"Stats logger thread did not terminate "
1947
+ f"within {timeout}s timeout. "
1948
+ "Thread may be blocked in logging operations. "
1949
+ "Proceeding with shutdown anyway."
1950
+ )
1951
+ else:
1952
+ logger.info("Stats logger thread terminated successfully")
1953
+ except Exception as e:
1954
+ logger.error(f"Error waiting for stats logger thread: {e}")
1955
+ else:
1956
+ logger.info("Stats logger thread already stopped")
1957
+
1958
+ logger.info("LMCacheStatsLogger shutdown complete")