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,463 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Standard
3
+ from typing import Union
4
+ import asyncio
5
+ import uuid
6
+
7
+ # Third Party
8
+ import msgspec
9
+ import zmq.asyncio
10
+
11
+ # First Party
12
+ from lmcache.logging import init_logger
13
+ from lmcache.v1.cache_controller.message import ( # noqa: E501
14
+ CheckFinishMsg,
15
+ CheckFinishRetMsg,
16
+ ClearMsg,
17
+ ClearRetMsg,
18
+ ClearWorkerMsg,
19
+ CompressMsg,
20
+ CompressRetMsg,
21
+ CompressWorkerMsg,
22
+ DecompressMsg,
23
+ DecompressRetMsg,
24
+ DecompressWorkerMsg,
25
+ ErrorMsg,
26
+ HealthMsg,
27
+ HealthRetMsg,
28
+ HealthWorkerMsg,
29
+ HealthWorkerRetMsg,
30
+ MoveMsg,
31
+ MoveRetMsg,
32
+ MoveWorkerMsg,
33
+ Msg,
34
+ MsgBase,
35
+ PinMsg,
36
+ PinRetMsg,
37
+ PinWorkerMsg,
38
+ )
39
+
40
+ logger = init_logger(__name__)
41
+
42
+
43
+ # NOTE (Jiayi): `LMCacheClusterExecutor` might need to be in different processes
44
+ # in the future for the sake of performance.
45
+ # NOTE (Jiayi): Also, consider scaling up the number of cluster executors
46
+ # in the future.
47
+ # TODO (Jiayi): need better error handling
48
+ class LMCacheClusterExecutor:
49
+ """
50
+ LMCache Cluster Executor class to handle the execution of cache operations.
51
+ """
52
+
53
+ def __init__(self, reg_controller):
54
+ """
55
+ Initialize the LMCache Executor with a cache instance.
56
+
57
+ :param lmcache_instance_id: lmcache_instance_id
58
+ """
59
+ self.reg_controller = reg_controller
60
+
61
+ async def clear(self, msg: ClearMsg) -> Union[ClearRetMsg, ErrorMsg]:
62
+ """
63
+ Execute a clear cache operation with error handling.
64
+ """
65
+ instance_id = msg.instance_id
66
+ location = msg.location
67
+
68
+ worker_ids = self.reg_controller.get_workers(instance_id)
69
+ assert worker_ids is not None
70
+ sockets = []
71
+ serialized_msgs = []
72
+ for worker_id in worker_ids:
73
+ socket = self.reg_controller.get_socket(instance_id, worker_id)
74
+ if socket is None:
75
+ return ErrorMsg(
76
+ error=(
77
+ f"Worker {worker_id} not registered for instance {instance_id}"
78
+ )
79
+ )
80
+ sockets.append(socket)
81
+
82
+ # TODO(Jiayi): Need a way to trak event_id -> worker_event_id mapping
83
+ # Also, we need to track worker_event_id status
84
+ worker_event_id = f"Worker{worker_id}{msg.event_id}"
85
+ serialized_msg = msgspec.msgpack.encode(
86
+ ClearWorkerMsg(
87
+ worker_event_id=worker_event_id,
88
+ location=location,
89
+ )
90
+ )
91
+ serialized_msgs.append(serialized_msg)
92
+ serialized_results = await self.execute_workers(
93
+ sockets=sockets,
94
+ serialized_msgs=serialized_msgs,
95
+ )
96
+
97
+ num_tokens_list = []
98
+ for i, serialized_result in enumerate(serialized_results):
99
+ result = msgspec.msgpack.decode(serialized_result, type=Msg)
100
+ num_tokens_list.append(result.num_tokens)
101
+
102
+ # TODO(Jiayi): Need to ensure cache consistency across workers.
103
+ assert len(set(num_tokens_list)) == 1, (
104
+ "The number of tokens cleared should be the same across all workers."
105
+ )
106
+
107
+ return ClearRetMsg(event_id=msg.event_id, num_tokens=num_tokens_list[0])
108
+
109
+ async def pin(self, msg: PinMsg) -> Union[PinRetMsg, ErrorMsg]:
110
+ """
111
+ Execute a pin cache operation with error handling.
112
+ """
113
+ instance_id = msg.instance_id
114
+ tokens = msg.tokens
115
+ location = msg.location
116
+
117
+ worker_ids = self.reg_controller.get_workers(instance_id)
118
+ assert worker_ids is not None
119
+ sockets = []
120
+ serialized_msgs = []
121
+ for worker_id in worker_ids:
122
+ socket = self.reg_controller.get_socket(instance_id, worker_id)
123
+ if socket is None:
124
+ return ErrorMsg(
125
+ error=(
126
+ f"Worker {worker_id} not registered for instance {instance_id}"
127
+ )
128
+ )
129
+ sockets.append(socket)
130
+
131
+ # TODO(Jiayi): Need a way to trak event_id -> worker_event_id mapping
132
+ # Also, we need to track worker_event_id status
133
+ worker_event_id = f"Worker{worker_id}{msg.event_id}"
134
+ serialized_msg = msgspec.msgpack.encode(
135
+ PinWorkerMsg(
136
+ worker_event_id=worker_event_id,
137
+ tokens=tokens,
138
+ location=location,
139
+ )
140
+ )
141
+ serialized_msgs.append(serialized_msg)
142
+ serialized_results = await self.execute_workers(
143
+ sockets=sockets,
144
+ serialized_msgs=serialized_msgs,
145
+ )
146
+
147
+ num_tokens_list = []
148
+ for i, serialized_result in enumerate(serialized_results):
149
+ result = msgspec.msgpack.decode(serialized_result, type=Msg)
150
+ num_tokens_list.append(result.num_tokens)
151
+
152
+ # TODO(Jiayi): Need to ensure cache consistency across workers.
153
+ assert len(set(num_tokens_list)) == 1, (
154
+ "The number of tokens pinned should be the same across all workers."
155
+ )
156
+
157
+ return PinRetMsg(event_id=msg.event_id, num_tokens=num_tokens_list[0])
158
+
159
+ async def compress(self, msg: CompressMsg) -> Union[CompressRetMsg, ErrorMsg]:
160
+ """
161
+ Execute a compress operation with error handling.
162
+ """
163
+ event_id = msg.event_id
164
+ instance_id = msg.instance_id
165
+ method = msg.method
166
+ location = msg.location
167
+ tokens = msg.tokens
168
+
169
+ worker_ids = self.reg_controller.get_workers(instance_id)
170
+ assert worker_ids is not None
171
+
172
+ # TODO(Jiayi): Currently, we do not support PP or heterogeneous TP.
173
+ # NOTE(Jiayi): The TP ranks are already sorted in registration_controller.
174
+
175
+ sockets = []
176
+ serialized_msgs = []
177
+ for worker_id in worker_ids:
178
+ socket = self.reg_controller.get_socket(instance_id, worker_id)
179
+
180
+ if socket is None:
181
+ return ErrorMsg(
182
+ error=(
183
+ f"Worker {worker_id} not registered for "
184
+ f"instance {instance_id} or "
185
+ )
186
+ )
187
+ sockets.append(socket)
188
+
189
+ worker_event_id = f"CompressWorker{worker_id}{str(uuid.uuid4())}"
190
+ serialized_msg = msgspec.msgpack.encode(
191
+ CompressWorkerMsg(
192
+ worker_event_id=worker_event_id,
193
+ method=method,
194
+ location=location,
195
+ tokens=tokens,
196
+ )
197
+ )
198
+ serialized_msgs.append(serialized_msg)
199
+ logger.debug(
200
+ f"Sending compress operation to worker ({instance_id}, {worker_id})"
201
+ )
202
+ serialized_results = await self.execute_workers(
203
+ sockets=sockets,
204
+ serialized_msgs=serialized_msgs,
205
+ )
206
+
207
+ num_tokens_list = []
208
+ for serialized_result in serialized_results:
209
+ result = msgspec.msgpack.decode(serialized_result, type=Msg)
210
+ num_tokens_list.append(result.num_tokens)
211
+
212
+ # TODO(Jiayi): Need to ensure cache consistency across workers.
213
+ assert len(set(num_tokens_list)) == 1, (
214
+ "The number of tokens compressed should be the same across all workers."
215
+ )
216
+
217
+ return CompressRetMsg(
218
+ event_id=event_id,
219
+ num_tokens=num_tokens_list[0],
220
+ )
221
+
222
+ async def decompress(self, msg: DecompressMsg) -> Union[DecompressRetMsg, ErrorMsg]:
223
+ """
224
+ Execute a decompress operation with error handling.
225
+ """
226
+ event_id = msg.event_id
227
+ instance_id = msg.instance_id
228
+ method = msg.method
229
+ location = msg.location
230
+ tokens = msg.tokens
231
+
232
+ worker_ids = self.reg_controller.get_workers(instance_id)
233
+ assert worker_ids is not None
234
+
235
+ sockets = []
236
+ serialized_msgs = []
237
+ for worker_id in worker_ids:
238
+ socket = self.reg_controller.get_socket(instance_id, worker_id)
239
+
240
+ if socket is None:
241
+ return ErrorMsg(
242
+ error=(
243
+ f"Worker {worker_id} not registered for "
244
+ f"instance {instance_id} or "
245
+ )
246
+ )
247
+ sockets.append(socket)
248
+
249
+ worker_event_id = f"DecompressWorker{worker_id}{str(uuid.uuid4())}"
250
+ serialized_msg = msgspec.msgpack.encode(
251
+ DecompressWorkerMsg(
252
+ worker_event_id=worker_event_id,
253
+ method=method,
254
+ location=location,
255
+ tokens=tokens,
256
+ )
257
+ )
258
+ serialized_msgs.append(serialized_msg)
259
+ logger.debug(
260
+ f"Sending decompress operation to worker ({instance_id}, {worker_id})"
261
+ )
262
+ serialized_results = await self.execute_workers(
263
+ sockets=sockets,
264
+ serialized_msgs=serialized_msgs,
265
+ )
266
+
267
+ num_tokens_list = []
268
+ for serialized_result in serialized_results:
269
+ result = msgspec.msgpack.decode(serialized_result, type=Msg)
270
+ num_tokens_list.append(result.num_tokens)
271
+
272
+ assert len(set(num_tokens_list)) == 1, (
273
+ "The number of tokens decompressed should be the same across all workers."
274
+ )
275
+
276
+ return DecompressRetMsg(
277
+ event_id=event_id,
278
+ num_tokens=num_tokens_list[0],
279
+ )
280
+
281
+ async def move(self, msg: MoveMsg) -> Union[MoveRetMsg, ErrorMsg]:
282
+ """
283
+ Execute a move cache operation with error handling.
284
+ """
285
+ # NOTE(Jiayi): Currently we assume the transfer is push-based.
286
+ src_instance_id = msg.old_position[0]
287
+ dst_instance_id = msg.new_position[0]
288
+
289
+ src_worker_ids = self.reg_controller.get_workers(src_instance_id)
290
+ assert src_worker_ids is not None
291
+ dst_worker_ids = self.reg_controller.get_workers(dst_instance_id)
292
+ assert dst_worker_ids is not None
293
+
294
+ # TODO(Jiayi): Currently, we do not support PP or heterogeneous TP.
295
+ # NOTE(Jiayi): The TP ranks are already sorted in registration_controller.
296
+
297
+ sockets = []
298
+ serialized_msgs = []
299
+ for src_worker_id, dst_worker_id in zip(
300
+ src_worker_ids, dst_worker_ids, strict=False
301
+ ):
302
+ socket = self.reg_controller.get_socket(src_instance_id, src_worker_id)
303
+ dst_url = self.reg_controller.get_peer_init_url(
304
+ dst_instance_id, dst_worker_id
305
+ )
306
+
307
+ if socket is None or dst_url is None:
308
+ return ErrorMsg(
309
+ error=(
310
+ f"Src worker {src_worker_id} not registered for "
311
+ f"instance {src_instance_id} or "
312
+ f"dst worker {dst_worker_id} not registered for "
313
+ f"instance {dst_instance_id} or P2P is not enabled."
314
+ )
315
+ )
316
+ sockets.append(socket)
317
+
318
+ worker_event_id = f"MoveWorker{src_worker_id}{str(uuid.uuid4())}"
319
+ serialized_msg = msgspec.msgpack.encode(
320
+ MoveWorkerMsg(
321
+ worker_event_id=worker_event_id,
322
+ old_position=msg.old_position[1],
323
+ new_position=(dst_url, msg.new_position[1]),
324
+ tokens=msg.tokens,
325
+ copy=msg.copy,
326
+ )
327
+ )
328
+ serialized_msgs.append(serialized_msg)
329
+ logger.debug(
330
+ f"Sending move operation to worker ({src_instance_id}, {src_worker_id})"
331
+ )
332
+ serialized_results = await self.execute_workers(
333
+ sockets=sockets,
334
+ serialized_msgs=serialized_msgs,
335
+ )
336
+
337
+ num_tokens_list = []
338
+ for serialized_result in serialized_results:
339
+ result = msgspec.msgpack.decode(serialized_result, type=Msg)
340
+ num_tokens_list.append(result.num_tokens)
341
+
342
+ # TODO(Jiayi): Need to ensure cache consistency across workers.
343
+ assert len(set(num_tokens_list)) == 1, (
344
+ "The number of tokens moved should be the same across all workers."
345
+ )
346
+
347
+ return MoveRetMsg(
348
+ event_id=msg.event_id,
349
+ num_tokens=num_tokens_list[0],
350
+ )
351
+
352
+ async def health(self, msg: HealthMsg) -> Union[HealthRetMsg, ErrorMsg]:
353
+ """
354
+ Execute a compress operation with error handling.
355
+ """
356
+ instance_id = msg.instance_id
357
+
358
+ worker_ids = self.reg_controller.get_workers(instance_id)
359
+ if worker_ids is None:
360
+ return ErrorMsg(error=f"No workers found for instance {instance_id}")
361
+
362
+ # TODO(Jiayi): Currently, we do not support PP or heterogeneous TP.
363
+ # NOTE(Jiayi): The TP ranks are already sorted in registration_controller.
364
+
365
+ sockets = []
366
+ serialized_msgs = []
367
+ for worker_id in worker_ids:
368
+ socket = self.reg_controller.get_socket(instance_id, worker_id)
369
+
370
+ if socket is None:
371
+ return ErrorMsg(
372
+ error=(
373
+ f"Worker {worker_id} not registered for "
374
+ f"instance {instance_id} or socket not found"
375
+ )
376
+ )
377
+ sockets.append(socket)
378
+
379
+ worker_event_id = f"HealthWorker{worker_id}{str(uuid.uuid4())}"
380
+ serialized_msg = msgspec.msgpack.encode(
381
+ HealthWorkerMsg(
382
+ worker_event_id=worker_event_id,
383
+ )
384
+ )
385
+ serialized_msgs.append(serialized_msg)
386
+ logger.debug(
387
+ f"Sending health check operation to worker ({instance_id}, {worker_id})"
388
+ )
389
+
390
+ # Collect results from all workers
391
+ serialized_results = await self.execute_workers(
392
+ sockets=sockets,
393
+ serialized_msgs=serialized_msgs,
394
+ )
395
+
396
+ # Process results
397
+ error_codes = {}
398
+ for i, serialized_result in enumerate(serialized_results):
399
+ try:
400
+ result = msgspec.msgpack.decode(serialized_result, type=Msg)
401
+ if isinstance(result, HealthWorkerRetMsg):
402
+ error_codes[worker_ids[i]] = result.error_code
403
+ elif isinstance(result, ErrorMsg):
404
+ error_codes[worker_ids[i]] = -1001 # Worker returned error
405
+ else:
406
+ error_codes[worker_ids[i]] = -1002 # Unexpected response
407
+ except Exception as e:
408
+ logger.error(
409
+ f"Failed to parse health response from worker "
410
+ f"{worker_ids[i]}: {str(e)}"
411
+ )
412
+ error_codes[worker_ids[i]] = -1003 # Failed to parse response
413
+
414
+ return HealthRetMsg(
415
+ event_id=msg.event_id,
416
+ error_codes=error_codes,
417
+ )
418
+
419
+ async def check_finish(
420
+ self, msg: CheckFinishMsg
421
+ ) -> Union[CheckFinishRetMsg, ErrorMsg]:
422
+ raise NotImplementedError
423
+
424
+ # TODO(Jiayi): need to make the types more specific
425
+ async def execute(self, operation: str, msg: MsgBase) -> MsgBase:
426
+ """
427
+ Execute a cache operation with error handling.
428
+
429
+ :param operation: The operation to execute
430
+ (e.g., 'clear').
431
+ :param msg: The message containing the operation details.
432
+ :return: The result of the operation or an error message.
433
+ """
434
+ try:
435
+ method = getattr(self, operation)
436
+ return await method(msg)
437
+ except AttributeError:
438
+ return ErrorMsg(error=f"Operation '{operation}' is not supported.")
439
+ except Exception as e:
440
+ return ErrorMsg(error=str(e))
441
+
442
+ async def execute_workers(
443
+ self,
444
+ sockets: list[zmq.asyncio.Socket],
445
+ serialized_msgs: list[bytes],
446
+ ) -> list[bytes]:
447
+ """
448
+ Execute a list of serialized messages on the given sockets.
449
+ :param sockets: The list of sockets to send the messages to.
450
+ :param serialized_msgs: The list of serialized messages to send.
451
+ :return: A list of serialized results received from the sockets.
452
+ """
453
+ tasks = []
454
+ for socket, serialized_msg in zip(sockets, serialized_msgs, strict=False):
455
+
456
+ async def send_and_receive(s, msg):
457
+ await s.send(msg)
458
+ return await s.recv()
459
+
460
+ tasks.append(send_and_receive(socket, serialized_msg))
461
+
462
+ serialized_results = await asyncio.gather(*tasks)
463
+ return serialized_results
@@ -0,0 +1,201 @@
1
+ /* Controller Dashboard Styles */
2
+ body {
3
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
4
+ background-color: #f8f9fa;
5
+ }
6
+
7
+ .container-fluid {
8
+ max-width: 1400px;
9
+ margin: 0 auto;
10
+ }
11
+
12
+ header {
13
+ border-radius: 0.5rem;
14
+ margin-bottom: 1rem;
15
+ }
16
+
17
+ .nav-tabs {
18
+ border-bottom: 2px solid #dee2e6;
19
+ }
20
+
21
+ .nav-tabs .nav-link {
22
+ border: none;
23
+ color: #495057;
24
+ font-weight: 500;
25
+ padding: 0.75rem 1.5rem;
26
+ }
27
+
28
+ .nav-tabs .nav-link:hover {
29
+ border: none;
30
+ color: #0d6efd;
31
+ }
32
+
33
+ .nav-tabs .nav-link.active {
34
+ color: #0d6efd;
35
+ border-bottom: 3px solid #0d6efd;
36
+ background-color: transparent;
37
+ }
38
+
39
+ .tab-content {
40
+ background-color: white;
41
+ border-radius: 0 0 0.5rem 0.5rem;
42
+ box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
43
+ }
44
+
45
+ .card {
46
+ border: 1px solid rgba(0, 0, 0, 0.125);
47
+ border-radius: 0.5rem;
48
+ box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
49
+ }
50
+
51
+ .card-title {
52
+ color: #495057;
53
+ font-weight: 600;
54
+ margin-bottom: 1rem;
55
+ }
56
+
57
+ .table-responsive {
58
+ margin-top: 1rem;
59
+ }
60
+
61
+ .table {
62
+ margin-bottom: 0;
63
+ }
64
+
65
+ .table thead th {
66
+ background-color: #f8f9fa;
67
+ border-bottom: 2px solid #dee2e6;
68
+ font-weight: 600;
69
+ color: #495057;
70
+ }
71
+
72
+ .table tbody tr:hover {
73
+ background-color: #f8f9fa;
74
+ }
75
+
76
+ .btn {
77
+ border-radius: 0.375rem;
78
+ font-weight: 500;
79
+ }
80
+
81
+ .btn-primary {
82
+ background-color: #0d6efd;
83
+ border-color: #0d6efd;
84
+ }
85
+
86
+ .btn-primary:hover {
87
+ background-color: #0b5ed7;
88
+ border-color: #0a58ca;
89
+ }
90
+
91
+ .btn-outline-primary {
92
+ color: #0d6efd;
93
+ border-color: #0d6efd;
94
+ }
95
+
96
+ .btn-outline-primary:hover {
97
+ background-color: #0d6efd;
98
+ border-color: #0d6efd;
99
+ }
100
+
101
+ .form-control, .form-select {
102
+ border-radius: 0.375rem;
103
+ border: 1px solid #ced4da;
104
+ }
105
+
106
+ .form-control:focus, .form-select:focus {
107
+ border-color: #86b7fe;
108
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
109
+ }
110
+
111
+ pre {
112
+ background-color: #f8f9fa;
113
+ border: 1px solid #dee2e6;
114
+ border-radius: 0.375rem;
115
+ padding: 1rem;
116
+ white-space: pre-wrap;
117
+ word-wrap: break-word;
118
+ font-family: 'Courier New', Courier, monospace;
119
+ font-size: 0.875rem;
120
+ }
121
+
122
+ .badge {
123
+ font-weight: 500;
124
+ padding: 0.35em 0.65em;
125
+ }
126
+
127
+ .badge.bg-success {
128
+ background-color: #198754 !important;
129
+ }
130
+
131
+ .badge.bg-warning {
132
+ background-color: #ffc107 !important;
133
+ }
134
+
135
+ .badge.bg-danger {
136
+ background-color: #dc3545 !important;
137
+ }
138
+
139
+ .badge.bg-info {
140
+ background-color: #0dcaf0 !important;
141
+ }
142
+
143
+ .spinner-border {
144
+ margin: 1rem auto;
145
+ display: block;
146
+ }
147
+
148
+ .modal-content {
149
+ border-radius: 0.5rem;
150
+ }
151
+
152
+ .modal-header {
153
+ border-bottom: 1px solid #dee2e6;
154
+ }
155
+
156
+ .modal-footer {
157
+ border-top: 1px solid #dee2e6;
158
+ }
159
+
160
+ /* Status indicators */
161
+ .status-active {
162
+ color: #198754;
163
+ }
164
+
165
+ .status-inactive {
166
+ color: #6c757d;
167
+ }
168
+
169
+ .status-error {
170
+ color: #dc3545;
171
+ }
172
+
173
+ .status-warning {
174
+ color: #ffc107;
175
+ }
176
+
177
+ /* Responsive adjustments */
178
+ @media (max-width: 768px) {
179
+ .container-fluid {
180
+ padding: 0.5rem;
181
+ }
182
+
183
+ .nav-tabs .nav-link {
184
+ padding: 0.5rem 0.75rem;
185
+ font-size: 0.875rem;
186
+ }
187
+
188
+ .tab-content {
189
+ padding: 1rem;
190
+ }
191
+ }
192
+
193
+ /* Animation for refreshing */
194
+ @keyframes spin {
195
+ 0% { transform: rotate(0deg); }
196
+ 100% { transform: rotate(360deg); }
197
+ }
198
+
199
+ .refreshing {
200
+ animation: spin 1s linear infinite;
201
+ }