From 9125ffe9dfca131dc51d7473989244263420530f Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Fri, 13 Feb 2026 15:41:05 -0800 Subject: [PATCH 01/15] docs: update docs with waymo terms of use --- README.md | 20 ++++++++++++++++-- WAYMO_LICENSE | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ docs/MODELS.md | 10 +++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 WAYMO_LICENSE diff --git a/README.md b/README.md index e2328fa..7dcc8e6 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,9 @@ For detailed architecture, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). 2. **Download fine-tuned EfficientDet model checkpoints:** - The system uses custom EfficientDet models (D1, D2, D4, D6, D7x) fine-tuned on the [Waymo Open Dataset](https://waymo.com/open/) for 5-class object detection (vehicle, pedestrian, cyclist, sign, unknown). Download and extract them: + The system uses custom EfficientDet models (D1, D2, D4, D6, D7x) fine-tuned on the [Waymo Open Dataset](https://waymo.com/open/) for 5-class object detection (vehicle, pedestrian, cyclist, sign, unknown). + + Our fine-tuned models can be downloaded and extracted as follows: ```bash # Download the model archive @@ -140,6 +142,16 @@ For detailed architecture, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). After extraction, update the checkpoint paths in your server configuration file (`config/server_config_gcloud.yaml`) and model config (`src/python/model_server/model_config.yaml`) to point to the extracted checkpoint files. See [docs/MODELS.md](docs/MODELS.md) for detailed model information and configuration. + > **IMPORTANT — Waymo Open Dataset License Notice** + > + > The fine-tuned EfficientDet model weights provided above were developed using the [Waymo Open Dataset](https://waymo.com/open/) and are released under the [Waymo Dataset License Agreement for Non-Commercial Use](https://waymo.com/open/terms/). By downloading or using these model weights, you agree that: + > + > 1. These models are for **non-commercial use only**. Any use, modification, or redistribution is subject to the terms of the [Waymo Dataset License Agreement for Non-Commercial Use](https://waymo.com/open/terms/), including the non-commercial restrictions therein. + > 2. Any further downstream use or modification of these models is subject to the same agreement. + > 3. A statement of the applicable Waymo Dataset License terms is included in this repository at [WAYMO_LICENSE](WAYMO_LICENSE). The full agreement is available at [waymo.com/open/terms](https://waymo.com/open/terms/). + > + > These models were made using the Waymo Open Dataset, provided by Waymo LLC. + 3. **Generate SSL Keys for QUIC:** ```bash cd src/quic @@ -342,7 +354,11 @@ We welcome contributions from the community! See [CONTRIBUTING.md](CONTRIBUTING. ## License -This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details. +This project's source code is licensed under the Apache 2.0 License — see the [LICENSE](LICENSE) file for details. + +The fine-tuned EfficientDet model weights distributed with this project were developed using the [Waymo Open Dataset](https://waymo.com/open/) and are subject to the [Waymo Dataset License Agreement for Non-Commercial Use](https://waymo.com/open/terms/). These model weights are provided for **non-commercial purposes only**. Any use, modification, or redistribution of the model weights must comply with the Waymo Dataset License Agreement, including the non-commercial restrictions of Section 4. A statement of the applicable Waymo Dataset License terms is included at [WAYMO_LICENSE](WAYMO_LICENSE); the full agreement is available at [waymo.com/open/terms](https://waymo.com/open/terms/). + +These models were made using the Waymo Open Dataset, provided by Waymo LLC. ## Citation diff --git a/WAYMO_LICENSE b/WAYMO_LICENSE new file mode 100644 index 0000000..c21dbf2 --- /dev/null +++ b/WAYMO_LICENSE @@ -0,0 +1,57 @@ +Waymo Dataset License Agreement for Non-Commercial Use +======================================================= + +The fine-tuned EfficientDet model weights distributed with this project +("Distributed WOD Models") were developed using the Waymo Open Dataset +and are subject to the Waymo Dataset License Agreement for Non-Commercial +Use (the "Agreement"). + +The full, canonical text of the Agreement is available at: + + https://waymo.com/open/terms/ + +By downloading, using, modifying, or redistributing the Distributed WOD +Models included in this project, you agree to be bound by the terms of +the Agreement. Key terms are summarized below for convenience, but the +full Agreement at the URL above governs in all cases. + + +NOTICE TO RECIPIENTS +-------------------- + +1. The Distributed WOD Models in this repository are released under the + Waymo Dataset License Agreement for Non-Commercial Use (the + "Agreement"), available at https://waymo.com/open/terms/. + +2. Any use, modification, or redistribution of the Distributed WOD Models + is subject to the terms of the Agreement, including the non-commercial + restrictions of Section 4. The Distributed WOD Models may only be used + for Non-commercial Purposes as defined in the Agreement. + +3. "Non-commercial Purposes" means research, teaching, scientific + publication, and personal experimentation. Non-commercial Purposes + expressly excludes use in operation of a vehicle, use in Production + Systems, or any use for which You receive monetary compensation. + +4. You may not distribute the Distributed WOD Models to any third party + except in compliance with the Agreement, and any such recipient must + also be bound by and comply with the Agreement. + +5. Upon use of any Distributed WOD Models for any purpose that is not a + Non-commercial Purpose, or is otherwise in breach of the Agreement, + any rights granted to you shall be null and void, and any rights + granted to third-party recipients are terminated automatically. + + +ATTRIBUTION +----------- + +These models were made using the Waymo Open Dataset, provided by Waymo LLC. + +For more information about the Waymo Open Dataset, visit: + + https://waymo.com/open/ + +For licensing inquiries, contact: + + open-dataset@waymo.com diff --git a/docs/MODELS.md b/docs/MODELS.md index 3682efd..c25b0a2 100644 --- a/docs/MODELS.md +++ b/docs/MODELS.md @@ -1,5 +1,15 @@ # Model Setup & Reference +> **Waymo Open Dataset License Notice** +> +> The fine-tuned EfficientDet model weights described in this document were developed using the [Waymo Open Dataset](https://waymo.com/open/) and are released under the [Waymo Dataset License Agreement for Non-Commercial Use](https://waymo.com/open/terms/). By downloading or using these model weights, you agree that: +> +> 1. These models are for **non-commercial use only**. Any use, modification, or redistribution is subject to the terms of the [Waymo Dataset License Agreement for Non-Commercial Use](https://waymo.com/open/terms/), including the non-commercial restrictions therein. +> 2. Any further downstream use or modification of these models is subject to the same agreement. +> 3. A statement of the applicable Waymo Dataset License terms is included in this repository at [WAYMO_LICENSE](../WAYMO_LICENSE). The full agreement is available at [waymo.com/open/terms](https://waymo.com/open/terms/). +> +> These models were made using the Waymo Open Dataset, provided by Waymo LLC. + This document covers the fine-tuned EfficientDet models used by TURBO for object detection, including download instructions, configuration, and inference pipeline details. ## Overview From 39a9d9ff8720bb928181f08ef37066d9c86fc52d Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Fri, 13 Feb 2026 15:46:48 -0800 Subject: [PATCH 02/15] docs: fix pull request template placement --- .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md From 7e16befc4f358ce7ff6167dab7cbfd74079554ff Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Fri, 13 Feb 2026 23:54:21 -0800 Subject: [PATCH 03/15] refactor: restructure config format with timestamps and common root dir --- config/client_config.yaml | 82 +++++----- config/quic_config_client.yaml | 9 +- config/quic_config_gcloud.yaml | 9 +- config/server_config_gcloud.yaml | 29 ++-- docs/CONFIGURATION.md | 166 +++++++++++--------- docs/IPC.md | 4 +- docs/LOGGING.md | 7 +- src/python/client_main.py | 57 +++++++ src/python/server.py | 27 +++- src/python/server_main.py | 20 +++ src/quic/Cargo.toml | 1 + src/quic/quic_client/Cargo.toml | 1 + src/quic/quic_client/src/client.rs | 21 ++- src/quic/quic_conn/src/utils/quic_config.rs | 10 +- src/quic/quic_server/Cargo.toml | 1 + src/quic/quic_server/src/server.rs | 25 +-- 16 files changed, 300 insertions(+), 169 deletions(-) diff --git a/config/client_config.yaml b/config/client_config.yaml index 07590e2..e0ecc8a 100644 --- a/config/client_config.yaml +++ b/config/client_config.yaml @@ -1,5 +1,9 @@ logging_config_filepath: /home/hwei/turbo/config/logging_config.yaml +experiment_output_dir: /home/hwei/experiment2-out +client_subdir: client +zmq_dir: /home/hwei/experiment2-out/zmq + web_dashboard_config: refresh_rate_seconds: 6 plotting_loop_sleep_seconds: 2 @@ -20,52 +24,49 @@ DST_IP: &dst-ip 136.118.43.70 main_client_config_list: - service_id: 1 - client_savedir: /home/hwei/experiment2-out/client max_entries: *max-log-entries thread_concurrency: 10 - camera_bidirectional_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/service1-camera-socket + camera_bidirectional_zmq_sockname: service1-camera-socket camera_stream_shmem_filename: service1-camera-shmem - bandwidth_allocation_incoming_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/main-client-1-bw-subscriber - quic_rcv_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/car-server-outgoing-1 - quic_snd_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/car-server-incoming-1 - outgoing_zmq_diagnostic_sockname: ipc:///home/hwei/experiment2-out/zmq/car-client-diagnostics + bandwidth_allocation_incoming_zmq_sockname: main-client-1-bw-subscriber + quic_rcv_zmq_sockname: car-server-outgoing-1 + quic_snd_zmq_sockname: car-server-incoming-1 + outgoing_zmq_diagnostic_sockname: car-client-diagnostics camera_np_size: [1080, 1920, 3] model_name_imagesize_map: *model_imagesizes - zmq_kill_switch_sockname: ipc:///home/hwei/experiment2-out/zmq/client-kill-1-switch + zmq_kill_switch_sockname: client-kill-1-switch quic_snd_shm_filename: client-service1-incoming-shm quic_rcv_shm_filename: client-service1-outgoing-shm quic_shm_size: *quic-shm-size SLO_TIMEOUT_MS: *slo-timeout - service_id: 2 - client_savedir: /home/hwei/experiment2-out/client max_entries: *max-log-entries thread_concurrency: 10 - camera_bidirectional_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/service2-camera-socket + camera_bidirectional_zmq_sockname: service2-camera-socket camera_stream_shmem_filename: service2-camera-shmem - bandwidth_allocation_incoming_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/main-client-2-bw-subscriber - quic_rcv_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/car-server-outgoing-2 - quic_snd_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/car-server-incoming-2 - outgoing_zmq_diagnostic_sockname: ipc:///home/hwei/experiment2-out/zmq/car-client-diagnostics + bandwidth_allocation_incoming_zmq_sockname: main-client-2-bw-subscriber + quic_rcv_zmq_sockname: car-server-outgoing-2 + quic_snd_zmq_sockname: car-server-incoming-2 + outgoing_zmq_diagnostic_sockname: car-client-diagnostics camera_np_size: [1080, 1920, 3] model_name_imagesize_map: *model_imagesizes - zmq_kill_switch_sockname: ipc:///home/hwei/experiment2-out/zmq/client-kill-2-switch + zmq_kill_switch_sockname: client-kill-2-switch quic_snd_shm_filename: client-service2-incoming-shm quic_rcv_shm_filename: client-service2-outgoing-shm quic_shm_size: *quic-shm-size SLO_TIMEOUT_MS: *slo-timeout - service_id: 3 - client_savedir: /home/hwei/experiment2-out/client max_entries: *max-log-entries thread_concurrency: 10 - camera_bidirectional_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/service3-camera-socket + camera_bidirectional_zmq_sockname: service3-camera-socket camera_stream_shmem_filename: service3-camera-shmem - bandwidth_allocation_incoming_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/main-client-3-bw-subscriber - quic_rcv_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/car-server-outgoing-3 - quic_snd_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/car-server-incoming-3 - outgoing_zmq_diagnostic_sockname: ipc:///home/hwei/experiment2-out/zmq/car-client-diagnostics + bandwidth_allocation_incoming_zmq_sockname: main-client-3-bw-subscriber + quic_rcv_zmq_sockname: car-server-outgoing-3 + quic_snd_zmq_sockname: car-server-incoming-3 + outgoing_zmq_diagnostic_sockname: car-client-diagnostics camera_np_size: [1080, 1920, 3] model_name_imagesize_map: *model_imagesizes - zmq_kill_switch_sockname: ipc:///home/hwei/experiment2-out/zmq/client-kill-3-switch + zmq_kill_switch_sockname: client-kill-3-switch quic_snd_shm_filename: client-service3-incoming-shm quic_rcv_shm_filename: client-service3-outgoing-shm quic_shm_size: *quic-shm-size @@ -76,62 +77,58 @@ bandwidth_allocator_config: t_SLO: 150 #TODO: change this parquet_eval_dir: /home/hwei/full-eval model_info_csv_path: /home/hwei/turbo/experiment_model_info.csv - outgoing_zmq_diagnostic_sockname: ipc:///home/hwei/experiment2-out/zmq/car-client-diagnostics + outgoing_zmq_diagnostic_sockname: car-client-diagnostics outgoing_zmq_client_socknames: - - ipc:///home/hwei/experiment2-out/zmq/main-client-1-bw-subscriber - - ipc:///home/hwei/experiment2-out/zmq/main-client-2-bw-subscriber - - ipc:///home/hwei/experiment2-out/zmq/main-client-3-bw-subscriber - bidirectional_zmq_quic_sockname: ipc:///home/hwei/experiment2-out/zmq/car-server-bw-service - zmq_kill_switch_sockname: ipc:///home/hwei/experiment2-out/zmq/bandwidth-allocator-kill-switch - bidirectional_zmq_ping_handler_sockname: ipc:///home/hwei/experiment2-out/zmq/ping-handler + - main-client-1-bw-subscriber + - main-client-2-bw-subscriber + - main-client-3-bw-subscriber + bidirectional_zmq_quic_sockname: car-server-bw-service + zmq_kill_switch_sockname: bandwidth-allocator-kill-switch + bidirectional_zmq_ping_handler_sockname: ping-handler ping_handler_config: dst_ip: *dst-ip - ping_savedir: /home/hwei/experiment2-out/client max_entries: 100 thread_concurrency: 5 - bidirectional_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/ping-handler - zmq_kill_switch_sockname: ipc:///home/hwei/experiment2-out/zmq/ping-handler-kill-switch + bidirectional_zmq_sockname: ping-handler + zmq_kill_switch_sockname: ping-handler-kill-switch camera_stream_config_list: - camera_id: 1 # FRONT - camera_savedir: /home/hwei/experiment2-out/client usb_id: 0 max_entries: *max-log-entries thread_concurrency: 10 - bidirectional_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/service1-camera-socket + bidirectional_zmq_sockname: service1-camera-socket camera_stream_shmem_filename: service1-camera-shmem shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] - zmq_kill_switch_sockname: ipc:///home/hwei/experiment2-out/zmq/camera-kill-1-switch + zmq_kill_switch_sockname: camera-kill-1-switch mock_camera_image_path: null # set to a file path (e.g. mock_webcam_image.jpg) to use a static image instead of webcam - camera_id: 2 # FRONT LEFT - camera_savedir: /home/hwei/experiment2-out/client usb_id: 4 # TODO: CONFIGURE THIS max_entries: *max-log-entries thread_concurrency: 10 - bidirectional_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/service2-camera-socket + bidirectional_zmq_sockname: service2-camera-socket camera_stream_shmem_filename: service2-camera-shmem shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] - zmq_kill_switch_sockname: ipc:///home/hwei/experiment2-out/zmq/camera-kill-2-switch + zmq_kill_switch_sockname: camera-kill-2-switch mock_camera_image_path: null - camera_id: 3 # FRONT RIGHT - camera_savedir: /home/hwei/experiment2-out/client usb_id: 8 # TODO: CONFIGURE THIS max_entries: *max-log-entries thread_concurrency: 10 - bidirectional_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/service3-camera-socket + bidirectional_zmq_sockname: service3-camera-socket camera_stream_shmem_filename: service3-camera-shmem shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] - zmq_kill_switch_sockname: ipc:///home/hwei/experiment2-out/zmq/camera-kill-3-switch + zmq_kill_switch_sockname: camera-kill-3-switch mock_camera_image_path: null main_plotter_config: # service 4 is for junk stream plotting_loop_sleep_seconds: 2 - zmq_incoming_diagnostic_name: ipc:///home/hwei/experiment2-out/zmq/car-client-diagnostics + zmq_incoming_diagnostic_name: car-client-diagnostics bandwidth_allocation_plot_config: service_id_list: [1, 2, 3] window_size_x: 40 @@ -148,7 +145,7 @@ main_plotter_config: utility_y_major_loc: .2 utility_y_minor_loc: .1 - service_status_plot_config: + service_status_plot_config: - service_id: 1 window_size_x: 40 cnt_min_y: -3 @@ -224,4 +221,3 @@ main_plotter_config: x_minor_loc: 5 y_major_loc: 100 y_minor_loc: 20 - diff --git a/config/quic_config_client.yaml b/config/quic_config_client.yaml index e2254fb..c5b2a13 100644 --- a/config/quic_config_client.yaml +++ b/config/quic_config_client.yaml @@ -1,4 +1,8 @@ -zmq_pathdir: "/home/hwei/experiment2-out/zmq" +experiment_output_dir: "/home/hwei/experiment2-out" +zmq_dir: "/home/hwei/experiment2-out/zmq" +quic_client_log_subdir: quic-client-out +quic_server_log_subdir: quic-server-out + slo_timeout_ms: 100 junk_tx_loop_interval_ms: 100 logging_interval_ms: 500 @@ -11,9 +15,6 @@ junk_restart_interval_ms: 500 services: [1, 2, 3, 4] -quic_client_log_path: "/home/hwei/experiment2-out/quic-client-out" -quic_server_log_path: "/home/hwei/experiment2-out/quic-server-out" - client_enable_bw_stat_log: True client_enable_allocation_stat_log: True client_enable_network_stat_log: True diff --git a/config/quic_config_gcloud.yaml b/config/quic_config_gcloud.yaml index 01bc4bd..421f678 100644 --- a/config/quic_config_gcloud.yaml +++ b/config/quic_config_gcloud.yaml @@ -1,4 +1,8 @@ -zmq_pathdir: "/home/hwei/experiment2-out/zmq" +experiment_output_dir: "/home/hwei/experiment2-out" +zmq_dir: "/home/hwei/experiment2-out/zmq" +quic_client_log_subdir: quic-client-out +quic_server_log_subdir: quic-server-out + slo_timeout_ms: 100 junk_tx_loop_interval_ms: 7 logging_interval_ms: 500 @@ -10,9 +14,6 @@ junk_restart_interval_ms: 500 services: [1, 2, 3, 4] -quic_client_log_path: "/home/hwei/experiment2-out/quic-client-out" -quic_server_log_path: "/home/hwei/experiment2-out/quic-server-out" - client_enable_bw_stat_log: True client_enable_allocation_stat_log: True client_enable_network_stat_log: True diff --git a/config/server_config_gcloud.yaml b/config/server_config_gcloud.yaml index 4339e55..9d2d1b4 100644 --- a/config/server_config_gcloud.yaml +++ b/config/server_config_gcloud.yaml @@ -1,5 +1,9 @@ logging_config_filepath: /home/hwei/turbo/config/logging_config.yaml +experiment_output_dir: /home/hwei/experiment2-out +server_subdir: server +zmq_dir: /home/hwei/experiment2-out/zmq + MAX_LOG_ENTRIES: &max-log-entries 100 SHM_FILESIZE: &shm-filesize 50000000 @@ -24,42 +28,41 @@ server_model_list: &model-list-ref server_config_list: - service_id: 1 - server_log_savedir: /home/hwei/experiment2-out/server max_entries: *max-log-entries model_metadata_list: *model-list-ref device: "cuda:0" - incoming_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/remote-server-outgoing-1 + incoming_zmq_sockname: remote-server-outgoing-1 incoming_shm_filename: server-service1-outgoing-shm - outgoing_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/remote-server-incoming-1 + outgoing_zmq_sockname: remote-server-incoming-1 outgoing_shm_filename: server-service1-incoming-shm thread_concurrency: 10 shm_filesize: *shm-filesize - zmq_kill_switch_sockname: ipc:///home/hwei/experiment2-out/zmq/remote-server-kill-switch-1 + zmq_kill_switch_sockname: remote-server-kill-switch-1 mock_inference_output_path: null # set to a file path (e.g. example_effdet_d4_output.npz) to skip model loading and return mock detections + mock_model_latency_csv_path: null # set to experiment_model_info.csv path to simulate per-model inference latency in mock mode - service_id: 2 - server_log_savedir: /home/hwei/experiment2-out/server max_entries: *max-log-entries model_metadata_list: *model-list-ref device: "cuda:1" - incoming_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/remote-server-outgoing-2 + incoming_zmq_sockname: remote-server-outgoing-2 incoming_shm_filename: server-service2-outgoing-shm - outgoing_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/remote-server-incoming-2 + outgoing_zmq_sockname: remote-server-incoming-2 outgoing_shm_filename: server-service2-incoming-shm thread_concurrency: 10 shm_filesize: *shm-filesize - zmq_kill_switch_sockname: ipc:///home/hwei/experiment2-out/zmq/remote-server-kill-switch-2 + zmq_kill_switch_sockname: remote-server-kill-switch-2 mock_inference_output_path: null + mock_model_latency_csv_path: null - service_id: 3 - server_log_savedir: /home/hwei/experiment2-out/server max_entries: *max-log-entries model_metadata_list: *model-list-ref device: "cuda:2" - incoming_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/remote-server-outgoing-3 + incoming_zmq_sockname: remote-server-outgoing-3 incoming_shm_filename: server-service3-outgoing-shm - outgoing_zmq_sockname: ipc:///home/hwei/experiment2-out/zmq/remote-server-incoming-3 + outgoing_zmq_sockname: remote-server-incoming-3 outgoing_shm_filename: server-service3-incoming-shm thread_concurrency: 10 shm_filesize: *shm-filesize - zmq_kill_switch_sockname: ipc:///home/hwei/experiment2-out/zmq/remote-server-kill-switch-3 + zmq_kill_switch_sockname: remote-server-kill-switch-3 mock_inference_output_path: null - \ No newline at end of file + mock_model_latency_csv_path: null diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index ff3e612..a2e33c1 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -21,7 +21,11 @@ The config files make heavy use of **YAML anchors** (`&anchor-name`) and **refer ### IPC Socket Path Convention -All ZMQ IPC socket paths follow the pattern `ipc:///`. The `zmq_pathdir` is set in the QUIC config, and all socket paths in the client/server configs must use the same base directory. See [IPC.md](IPC.md) for the full socket reference. +ZMQ IPC socket names are stored as **bare names** (e.g., `service1-camera-socket`) in all YAML config files. The Python orchestrators (`client_main.py`, `server_main.py`) automatically resolve them to full `ipc://` paths using the `zmq_dir` directory specified in the config. For example, a bare name `service1-camera-socket` with `zmq_dir: /home/user/experiment-out/zmq` becomes `ipc:///home/user/experiment-out/zmq/service1-camera-socket`. See [IPC.md](IPC.md) for the full socket reference. + +### Auto-Generated Output Directories + +Save directories for logs (`client_savedir`, `camera_savedir`, `ping_savedir`, `server_log_savedir`) are **not specified in the YAML configs**. Instead, each orchestrator creates a timestamped run directory at startup under `experiment_output_dir` and injects the appropriate save path into each component's config. For example, `client_main.py` creates `experiment_output_dir/client_main_2024-01-15_10-30-00/client/` and sets that as the save directory for all client-side components. The QUIC binaries similarly create timestamped subdirectories (e.g., `experiment_output_dir/quic_client_2024-01-15_10-30-00/quic-client-out/`). --- @@ -34,6 +38,10 @@ Configures the client-side (AV) components: camera streams, perception service c ```yaml logging_config_filepath: /path/to/turbo/config/logging_config.yaml +experiment_output_dir: /path/to/experiment-out +client_subdir: client +zmq_dir: /path/to/experiment-out/zmq + DST_IP: &dst-ip # Cloud server IP for ICMP pings SLO_TIMEOUT: &slo-timeout 200 # Client-side SLO timeout (ms), shared via anchor QUIC_SHM_SIZE: &quic-shm-size 50000000 # Shared memory region size (bytes, ~50 MB) @@ -43,6 +51,9 @@ MAX_LOG_ENTRIES: &max-log-entries 100 # Max records in memory before spilling | Parameter | Must customize? | Description | |-----------|:-:|---| | `logging_config_filepath` | Yes | Absolute path to the Python logging config file | +| `experiment_output_dir` | **Yes** | Base output directory. `client_main.py` creates a timestamped subdirectory here (e.g., `experiment-out/client_main_2024-01-15_10-30-00/`) for each run | +| `client_subdir` | No | Subdirectory name within the timestamped run directory for client Parquet logs. Default `client` | +| `zmq_dir` | **Yes** | Directory for ZMQ IPC socket files. Must match across client, server, and QUIC configs. Created automatically if it doesn't exist | | `DST_IP` | **Yes** | Public IP of your cloud server. Used by PingHandler for RTT measurement | | `SLO_TIMEOUT` | Maybe | Service-level objective timeout in ms. Frames exceeding this are dropped. Default `200` is suitable for most setups | | `QUIC_SHM_SIZE` | No | Size of each POSIX shared memory region in bytes. `50000000` (50 MB) is sufficient for HD frames | @@ -81,39 +92,39 @@ One entry per perception service. Add or remove entries to match your number of ```yaml main_client_config_list: - service_id: 1 - client_savedir: /path/to/experiment-out/client max_entries: *max-log-entries thread_concurrency: 10 - camera_bidirectional_zmq_sockname: ipc:///path/to/zmq/service1-camera-socket + camera_bidirectional_zmq_sockname: service1-camera-socket camera_stream_shmem_filename: service1-camera-shmem - bandwidth_allocation_incoming_zmq_sockname: ipc:///path/to/zmq/main-client-1-bw-subscriber - quic_rcv_zmq_sockname: ipc:///path/to/zmq/car-server-outgoing-1 - quic_snd_zmq_sockname: ipc:///path/to/zmq/car-server-incoming-1 - outgoing_zmq_diagnostic_sockname: ipc:///path/to/zmq/car-client-diagnostics + bandwidth_allocation_incoming_zmq_sockname: main-client-1-bw-subscriber + quic_rcv_zmq_sockname: car-server-outgoing-1 + quic_snd_zmq_sockname: car-server-incoming-1 + outgoing_zmq_diagnostic_sockname: car-client-diagnostics camera_np_size: [1080, 1920, 3] model_name_imagesize_map: *model_imagesizes - zmq_kill_switch_sockname: ipc:///path/to/zmq/client-kill-1-switch + zmq_kill_switch_sockname: client-kill-1-switch quic_snd_shm_filename: client-service1-incoming-shm quic_rcv_shm_filename: client-service1-outgoing-shm quic_shm_size: *quic-shm-size SLO_TIMEOUT_MS: *slo-timeout ``` +**Note**: `client_savedir` is not specified in the YAML. It is automatically injected by `client_main.py` using the timestamped run directory. + | Parameter | Must customize? | Description | |-----------|:-:|---| | `service_id` | Yes (if adding/removing services) | Unique integer ID for this service. Must match across client, server, and QUIC configs | -| `client_savedir` | **Yes** | Directory for client-side Parquet log output. Must exist and be writable | | `max_entries` | No | Log buffer size before spilling to Parquet (uses global anchor) | | `thread_concurrency` | No | Number of threads in the client's thread pool. Default `10` is suitable for most setups | -| `camera_bidirectional_zmq_sockname` | Yes (path) | ZMQ REQ/REP socket to the CameraDataStream for this service. Replace path prefix with your `zmq_pathdir` | +| `camera_bidirectional_zmq_sockname` | No | Bare ZMQ socket name for the CameraDataStream. Auto-resolved to `ipc:///` by `client_main.py` | | `camera_stream_shmem_filename` | No | POSIX SHM region name for raw camera frames. Must match the corresponding camera stream entry | -| `bandwidth_allocation_incoming_zmq_sockname` | Yes (path) | ZMQ SUB socket to receive bandwidth allocation updates from the BandwidthAllocator | -| `quic_rcv_zmq_sockname` | Yes (path) | ZMQ socket for sending compressed images to the QUIC client | -| `quic_snd_zmq_sockname` | Yes (path) | ZMQ socket for receiving inference results from the QUIC client | -| `outgoing_zmq_diagnostic_sockname` | Yes (path) | ZMQ PUB socket for sending diagnostic messages to the plotter. All services share the same socket name | +| `bandwidth_allocation_incoming_zmq_sockname` | No | Bare ZMQ socket name for receiving bandwidth allocation updates from the BandwidthAllocator | +| `quic_rcv_zmq_sockname` | No | Bare ZMQ socket name for sending compressed images to the QUIC client | +| `quic_snd_zmq_sockname` | No | Bare ZMQ socket name for receiving inference results from the QUIC client | +| `outgoing_zmq_diagnostic_sockname` | No | Bare ZMQ socket name for diagnostic messages to the plotter. All services share the same name | | `camera_np_size` | Maybe | Camera frame dimensions as `[height, width, channels]`. Change if your cameras produce a different resolution | | `model_name_imagesize_map` | No | Reference to the global model image size map (uses anchor) | -| `zmq_kill_switch_sockname` | Yes (path) | ZMQ SUB socket for receiving graceful shutdown signals from `client_main.py` | +| `zmq_kill_switch_sockname` | No | Bare ZMQ socket name for receiving graceful shutdown signals from `client_main.py` | | `quic_snd_shm_filename` | No | POSIX SHM region name for outgoing images to QUIC | | `quic_rcv_shm_filename` | No | POSIX SHM region name for incoming results from QUIC | | `quic_shm_size` | No | Size of the QUIC shared memory regions (uses global anchor) | @@ -129,14 +140,14 @@ bandwidth_allocator_config: t_SLO: 150 parquet_eval_dir: /path/to/full-eval model_info_csv_path: /path/to/turbo/experiment_model_info.csv - outgoing_zmq_diagnostic_sockname: ipc:///path/to/zmq/car-client-diagnostics + outgoing_zmq_diagnostic_sockname: car-client-diagnostics outgoing_zmq_client_socknames: - - ipc:///path/to/zmq/main-client-1-bw-subscriber - - ipc:///path/to/zmq/main-client-2-bw-subscriber - - ipc:///path/to/zmq/main-client-3-bw-subscriber - bidirectional_zmq_quic_sockname: ipc:///path/to/zmq/car-server-bw-service - zmq_kill_switch_sockname: ipc:///path/to/zmq/bandwidth-allocator-kill-switch - bidirectional_zmq_ping_handler_sockname: ipc:///path/to/zmq/ping-handler + - main-client-1-bw-subscriber + - main-client-2-bw-subscriber + - main-client-3-bw-subscriber + bidirectional_zmq_quic_sockname: car-server-bw-service + zmq_kill_switch_sockname: bandwidth-allocator-kill-switch + bidirectional_zmq_ping_handler_sockname: ping-handler ``` | Parameter | Must customize? | Description | @@ -145,11 +156,11 @@ bandwidth_allocator_config: | `t_SLO` | Maybe | Latency SLO constraint used by the LP solver (ms). Should be slightly tighter than `SLO_TIMEOUT` to account for processing overhead | | `parquet_eval_dir` | **Yes** | Path to directory containing pre-computed utility curve Parquet files. These are generated offline and encode the bandwidth-to-accuracy mapping for each model configuration | | `model_info_csv_path` | **Yes** | Path to `experiment_model_info.csv`, which maps model configuration strings to transport size (Mb) and runtime (ms) | -| `outgoing_zmq_diagnostic_sockname` | Yes (path) | ZMQ PUB socket for allocation diagnostics. Should match the diagnostic socket used by clients | -| `outgoing_zmq_client_socknames` | Yes (path) | List of ZMQ PUB sockets, one per service. Each must match the corresponding client's `bandwidth_allocation_incoming_zmq_sockname` | -| `bidirectional_zmq_quic_sockname` | Yes (path) | ZMQ REP socket for receiving bandwidth/RTT updates from the QUIC client | -| `zmq_kill_switch_sockname` | Yes (path) | ZMQ SUB socket for graceful shutdown | -| `bidirectional_zmq_ping_handler_sockname` | Yes (path) | ZMQ REQ socket for querying RTT from the PingHandler | +| `outgoing_zmq_diagnostic_sockname` | No | Bare ZMQ socket name for allocation diagnostics. Should match the diagnostic socket used by clients | +| `outgoing_zmq_client_socknames` | No | List of bare ZMQ socket names, one per service. Each must match the corresponding client's `bandwidth_allocation_incoming_zmq_sockname` | +| `bidirectional_zmq_quic_sockname` | No | Bare ZMQ socket name for receiving bandwidth/RTT updates from the QUIC client | +| `zmq_kill_switch_sockname` | No | Bare ZMQ socket name for graceful shutdown | +| `bidirectional_zmq_ping_handler_sockname` | No | Bare ZMQ socket name for querying RTT from the PingHandler | ### Camera Stream Configuration (`camera_stream_config_list`) @@ -158,30 +169,30 @@ One entry per USB camera. Must have a matching entry in `main_client_config_list ```yaml camera_stream_config_list: - camera_id: 1 # Must match service_id - camera_savedir: /path/to/experiment-out/client usb_id: 0 # USB device index max_entries: *max-log-entries thread_concurrency: 10 - bidirectional_zmq_sockname: ipc:///path/to/zmq/service1-camera-socket + bidirectional_zmq_sockname: service1-camera-socket camera_stream_shmem_filename: service1-camera-shmem shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] - zmq_kill_switch_sockname: ipc:///path/to/zmq/camera-kill-1-switch - mock_camera_image_path: null + zmq_kill_switch_sockname: camera-kill-1-switch + mock_camera_image_path: mock_webcam_image.jpg # set to null for live camera ``` +**Note**: `camera_savedir` is not specified in the YAML. It is automatically injected by `client_main.py` using the timestamped run directory. + | Parameter | Must customize? | Description | |-----------|:-:|---| | `camera_id` | Yes (if adding/removing services) | Must match the `service_id` of the corresponding client entry | -| `camera_savedir` | **Yes** | Directory for camera Parquet log output | | `usb_id` | **Yes** | USB camera device index for `cv2.VideoCapture`. Run `ls /dev/video*` to identify your cameras. These indices vary by system | | `max_entries` | No | Log buffer size (uses global anchor) | | `thread_concurrency` | No | Thread pool size for camera operations | -| `bidirectional_zmq_sockname` | Yes (path) | ZMQ REP socket for client communication. Must match the corresponding client's `camera_bidirectional_zmq_sockname` | +| `bidirectional_zmq_sockname` | No | Bare ZMQ socket name for client communication. Must match the corresponding client's `camera_bidirectional_zmq_sockname` | | `camera_stream_shmem_filename` | No | SHM region name. Must match the corresponding client's `camera_stream_shmem_filename` | | `shmem_buf_size` | No | SHM buffer size in bytes (uses global anchor) | | `camera_np_size` | Maybe | Camera frame dimensions `[height, width, channels]`. Must match the client entry | -| `zmq_kill_switch_sockname` | Yes (path) | ZMQ SUB socket for graceful shutdown | +| `zmq_kill_switch_sockname` | No | Bare ZMQ socket name for graceful shutdown | | `mock_camera_image_path` | Maybe | Set to a file path (e.g., `mock_webcam_image.jpg`) to use a static image instead of a live webcam. Useful for testing without hardware. Set to `null` for live camera | ### Ping Handler Configuration (`ping_handler_config`) @@ -189,21 +200,21 @@ camera_stream_config_list: ```yaml ping_handler_config: dst_ip: *dst-ip - ping_savedir: /path/to/experiment-out/client max_entries: 100 thread_concurrency: 5 - bidirectional_zmq_sockname: ipc:///path/to/zmq/ping-handler - zmq_kill_switch_sockname: ipc:///path/to/zmq/ping-handler-kill-switch + bidirectional_zmq_sockname: ping-handler + zmq_kill_switch_sockname: ping-handler-kill-switch ``` +**Note**: `ping_savedir` is not specified in the YAML. It is automatically injected by `client_main.py` using the timestamped run directory. + | Parameter | Must customize? | Description | |-----------|:-:|---| | `dst_ip` | **Yes** | IP address to ping for RTT measurement. Should be your cloud server's public IP (uses global `DST_IP` anchor) | -| `ping_savedir` | **Yes** | Directory for ping log Parquet output | | `max_entries` | No | Log buffer size | | `thread_concurrency` | No | Thread pool size | -| `bidirectional_zmq_sockname` | Yes (path) | ZMQ REP socket. Must match the bandwidth allocator's `bidirectional_zmq_ping_handler_sockname` | -| `zmq_kill_switch_sockname` | Yes (path) | ZMQ SUB socket for graceful shutdown | +| `bidirectional_zmq_sockname` | No | Bare ZMQ socket name. Must match the bandwidth allocator's `bidirectional_zmq_ping_handler_sockname` | +| `zmq_kill_switch_sockname` | No | Bare ZMQ socket name for graceful shutdown | ### Diagnostic Plotter Configuration (`main_plotter_config`) @@ -212,7 +223,7 @@ Configures the real-time matplotlib diagnostic plots. This section is optional ```yaml main_plotter_config: plotting_loop_sleep_seconds: 2 - zmq_incoming_diagnostic_name: ipc:///path/to/zmq/car-client-diagnostics + zmq_incoming_diagnostic_name: car-client-diagnostics bandwidth_allocation_plot_config: service_id_list: [1, 2, 3] window_size_x: 40 # X-axis window size (seconds of data shown) @@ -261,7 +272,7 @@ main_plotter_config: | Parameter | Must customize? | Description | |-----------|:-:|---| | `plotting_loop_sleep_seconds` | No | Sleep interval between plot updates | -| `zmq_incoming_diagnostic_name` | Yes (path) | ZMQ SUB socket for receiving diagnostics from clients, bandwidth allocator, and QUIC. Must match `outgoing_zmq_diagnostic_sockname` used by other components | +| `zmq_incoming_diagnostic_name` | No | Bare ZMQ socket name for receiving diagnostics from clients, bandwidth allocator, and QUIC. Must match `outgoing_zmq_diagnostic_sockname` used by other components | | `bandwidth_allocation_plot_config` | No | Plot layout for the bandwidth allocation overview. Adjust `window_size_x` to show more or less history | | `service_status_plot_config` | Yes (if adding/removing services) | One entry per service. Adjust to match your `service_id_list` | | `service_utilization_plot_config` | Yes (if adding/removing services) | One entry per service including junk service (service 4). Adjust to match your services list | @@ -277,6 +288,10 @@ Configures the server-side (cloud) components: model servers and GPU assignments ```yaml logging_config_filepath: /path/to/turbo/config/logging_config.yaml +experiment_output_dir: /path/to/experiment-out +server_subdir: server +zmq_dir: /path/to/experiment-out/zmq + MAX_LOG_ENTRIES: &max-log-entries 100 SHM_FILESIZE: &shm-filesize 50000000 ``` @@ -284,6 +299,9 @@ SHM_FILESIZE: &shm-filesize 50000000 | Parameter | Must customize? | Description | |-----------|:-:|---| | `logging_config_filepath` | Yes | Absolute path to the Python logging config file | +| `experiment_output_dir` | **Yes** | Base output directory. `server_main.py` creates a timestamped subdirectory here (e.g., `experiment-out/server_main_2024-01-15_10-30-00/`) for each run | +| `server_subdir` | No | Subdirectory name within the timestamped run directory for server Parquet logs. Default `server` | +| `zmq_dir` | **Yes** | Directory for ZMQ IPC socket files. Must match across client, server, and QUIC configs. Created automatically if it doesn't exist | | `MAX_LOG_ENTRIES` | No | Log buffer size before spilling to Parquet | | `SHM_FILESIZE` | No | Shared memory region size in bytes (~50 MB) | @@ -325,35 +343,37 @@ One entry per perception service. Add or remove entries to match your number of ```yaml server_config_list: - service_id: 1 - server_log_savedir: /path/to/experiment-out/server max_entries: *max-log-entries model_metadata_list: *model-list-ref device: "cuda:0" - incoming_zmq_sockname: ipc:///path/to/zmq/remote-server-outgoing-1 + incoming_zmq_sockname: remote-server-outgoing-1 incoming_shm_filename: server-service1-outgoing-shm - outgoing_zmq_sockname: ipc:///path/to/zmq/remote-server-incoming-1 + outgoing_zmq_sockname: remote-server-incoming-1 outgoing_shm_filename: server-service1-incoming-shm thread_concurrency: 10 shm_filesize: *shm-filesize - zmq_kill_switch_sockname: ipc:///path/to/zmq/remote-server-kill-switch-1 + zmq_kill_switch_sockname: remote-server-kill-switch-1 mock_inference_output_path: null + mock_model_latency_csv_path: null ``` +**Note**: `server_log_savedir` is not specified in the YAML. It is automatically injected by `server_main.py` using the timestamped run directory. + | Parameter | Must customize? | Description | |-----------|:-:|---| | `service_id` | Yes (if adding/removing services) | Unique integer ID. Must match the corresponding client-side service | -| `server_log_savedir` | **Yes** | Directory for server-side Parquet log output. Must exist and be writable | | `max_entries` | No | Log buffer size (uses global anchor) | | `model_metadata_list` | No | Reference to `server_model_list` (uses YAML anchor). All servers share the same model list | | `device` | **Yes** | PyTorch device string (e.g., `cuda:0`, `cuda:1`, `cpu`). Assign a different GPU to each service for parallelism. Run `nvidia-smi` to see available GPUs | -| `incoming_zmq_sockname` | Yes (path) | ZMQ REP socket for receiving images from the QUIC server | +| `incoming_zmq_sockname` | No | Bare ZMQ socket name for receiving images from the QUIC server. Auto-resolved to `ipc:///` by `server_main.py` | | `incoming_shm_filename` | No | SHM region name for incoming image data | -| `outgoing_zmq_sockname` | Yes (path) | ZMQ REQ socket for sending inference results back to the QUIC server | +| `outgoing_zmq_sockname` | No | Bare ZMQ socket name for sending inference results back to the QUIC server | | `outgoing_shm_filename` | No | SHM region name for outgoing inference results | | `thread_concurrency` | No | Thread pool size for server operations | | `shm_filesize` | No | SHM region size in bytes (uses global anchor) | -| `zmq_kill_switch_sockname` | Yes (path) | ZMQ SUB socket for graceful shutdown from `server_main.py` | +| `zmq_kill_switch_sockname` | No | Bare ZMQ socket name for graceful shutdown from `server_main.py` | | `mock_inference_output_path` | Maybe | Set to a `.npz` file path (e.g., `example_effdet_d4_output.npz`) to skip model loading and return pre-recorded detections. Useful for testing without a GPU. Set to `null` for real inference | +| `mock_model_latency_csv_path` | Maybe | Set to the `experiment_model_info.csv` path to simulate per-model inference latency in mock mode. Only used when `mock_inference_output_path` is set. Set to `null` to skip latency simulation | --- @@ -364,7 +384,11 @@ Configures the QUIC transport layer (Rust binaries: `quic_client` and `quic_serv ### Paths and Timing ```yaml -zmq_pathdir: "/path/to/experiment-out/zmq" +experiment_output_dir: "/path/to/experiment-out" +zmq_dir: "/path/to/experiment-out/zmq" +quic_client_log_subdir: quic-client-out +quic_server_log_subdir: quic-server-out + slo_timeout_ms: 100 junk_tx_loop_interval_ms: 100 logging_interval_ms: 500 @@ -377,7 +401,10 @@ junk_restart_interval_ms: 500 | Parameter | Must customize? | Description | |-----------|:-:|---| -| `zmq_pathdir` | **Yes** | Base directory for all ZMQ IPC socket files. Must match the path prefix used in all `ipc://` socket paths in client and server configs. This directory must exist before starting | +| `experiment_output_dir` | **Yes** | Base output directory. The QUIC client/server creates a timestamped subdirectory here (e.g., `experiment-out/quic_client_2024-01-15_10-30-00/`) for each run. Must match the value used in client and server Python configs | +| `zmq_dir` | **Yes** | Directory for ZMQ IPC socket files. Must match across client, server, and QUIC configs. The QUIC binaries assert this directory exists before starting | +| `quic_client_log_subdir` | No | Subdirectory name within the timestamped run directory for QUIC client Parquet logs. Default `quic-client-out` | +| `quic_server_log_subdir` | No | Subdirectory name within the timestamped run directory for QUIC server Parquet logs. Default `quic-server-out` | | `slo_timeout_ms` | Maybe | Timeout for dropping stale queued frames in the QUIC send loop (ms). Should be aligned with `SLO_TIMEOUT` in the client config. Frames older than this are silently dropped from the LIFO queue | | `junk_tx_loop_interval_ms` | No | Interval between junk service transmissions (ms). Lower values probe bandwidth more aggressively. Cloud config uses `7`, client config uses `100` | | `logging_interval_ms` | No | How often network statistics are logged to Parquet (ms) | @@ -399,18 +426,6 @@ enable_junk_service: True | `services` | Yes (if adding/removing services) | List of all service IDs. If `enable_junk_service` is `True`, the highest ID is the junk service and should not appear in the client's `service_id_list` | | `enable_junk_service` | Maybe | If `True`, the last service in the list sends dummy data to probe available bandwidth. Recommended `True` for accurate bandwidth estimation | -### Logging Paths - -```yaml -quic_client_log_path: "/path/to/experiment-out/quic-client-out" -quic_server_log_path: "/path/to/experiment-out/quic-server-out" -``` - -| Parameter | Must customize? | Description | -|-----------|:-:|---| -| `quic_client_log_path` | **Yes** | Output directory for QUIC client Parquet logs. Must exist and be writable | -| `quic_server_log_path` | **Yes** | Output directory for QUIC server Parquet logs. Must exist and be writable | - ### Logging Flags (Client-side QUIC) ```yaml @@ -454,20 +469,18 @@ When deploying the system, work through this checklist to ensure all paths and p ### 1. Choose an output directory -All components write logs and IPC socket files to a shared output directory tree. Create it first: +All components write logs and IPC socket files to a shared output directory tree. Set `experiment_output_dir` and `zmq_dir` consistently across all four config files. The orchestrators automatically create timestamped run subdirectories and ZMQ directories at startup — you only need the base directory to exist (or be creatable): ```bash -mkdir -p ~/experiment-out/{zmq,client,server,quic-client-out,quic-server-out} +mkdir -p ~/experiment-out ``` -Then update all path references in all four config files to use your chosen directory. - ### 2. Client-side (`client_config.yaml`) - [ ] `logging_config_filepath` points to `config/logging_config.yaml` (absolute path) +- [ ] `experiment_output_dir` is set to your chosen output directory +- [ ] `zmq_dir` is set (e.g., `~/experiment-out/zmq`) and matches all other configs - [ ] `DST_IP` is set to your cloud server's public IP address -- [ ] All `ipc://` socket paths use your output directory (e.g., `ipc:///home/you/experiment-out/zmq/...`) -- [ ] `client_savedir` and `camera_savedir` point to writable log directories - [ ] `parquet_eval_dir` points to the directory containing pre-computed utility curve Parquet files - [ ] `model_info_csv_path` points to the `experiment_model_info.csv` file - [ ] `camera_stream_config_list[].usb_id` matches your USB camera device IDs (run `ls /dev/video*`) @@ -479,24 +492,25 @@ Then update all path references in all four config files to use your chosen dire ### 3. Server-side (`server_config_gcloud.yaml`) - [ ] `logging_config_filepath` points to `config/logging_config.yaml` (absolute path) +- [ ] `experiment_output_dir` is set to your chosen output directory (same as client config) +- [ ] `zmq_dir` matches all other configs - [ ] `server_model_list[].checkpoint_path` points to valid EfficientDet `.ckpt` files (see [MODELS.md](MODELS.md)) - [ ] `server_config_list[].device` assigns a unique GPU to each service (e.g., `cuda:0`, `cuda:1`, `cuda:2`) -- [ ] All `ipc://` socket paths use the same base directory as the QUIC config's `zmq_pathdir` -- [ ] `server_log_savedir` points to a writable log directory - [ ] Number of entries in `server_config_list` matches the number of real services (not including junk) -- [ ] If testing without a GPU: set `mock_inference_output_path` to a `.npz` file path +- [ ] If testing without a GPU: set `mock_inference_output_path` to a `.npz` file path and optionally set `mock_model_latency_csv_path` to simulate per-model latency ### 4. QUIC configs (`quic_config_client.yaml` and `quic_config_gcloud.yaml`) -- [ ] `zmq_pathdir` matches the base directory used in all `ipc://` paths in client and server configs +- [ ] `experiment_output_dir` matches the value in client and server configs +- [ ] `zmq_dir` matches all other configs - [ ] `services` list includes all active service IDs plus the junk service (e.g., `[1, 2, 3, 4]`) -- [ ] `quic_client_log_path` and `quic_server_log_path` point to writable directories - [ ] `slo_timeout_ms` is aligned with `SLO_TIMEOUT` in the client config - [ ] Both client and server QUIC configs have the same `services` list ### 5. Cross-config consistency -- [ ] The `zmq_pathdir` in QUIC configs matches the path prefix of all `ipc://` sockets in client and server configs +- [ ] `experiment_output_dir` is the same across all four config files +- [ ] `zmq_dir` is the same across all four config files - [ ] The `services` list in QUIC configs includes all `service_id` values from client and server configs, plus the junk service - [ ] The `SLO_TIMEOUT` (client) and `slo_timeout_ms` (QUIC) are aligned - [ ] SHM filenames in client config match what the QUIC binaries expect (follow the naming convention `client-service{N}-incoming-shm` / `client-service{N}-outgoing-shm`) diff --git a/docs/IPC.md b/docs/IPC.md index 106bda7..1a6765b 100644 --- a/docs/IPC.md +++ b/docs/IPC.md @@ -95,7 +95,7 @@ This section documents every IPC channel between components. All connections use | MainPlotter | ZMQ PUB (connect) | `car-client-diagnostics` | PUB/SUB | QUIC Client → MainPlotter | `send_loop` publishes per-service network utilization stats (send rate, receive rate, allocated limit) | | QUIC Server | QUIC stream | TCP (s2n-quic) | Bidirectional | QUIC Client ↔ QUIC Server | Multiplexed per-service bidirectional streams carrying image data and inference results | -**Config**: `quic/quic_config_*.yaml` (zmq_pathdir, services, timing, logging) +**Config**: `quic/quic_config_*.yaml` (zmq_dir, services, timing, logging) --- @@ -183,7 +183,7 @@ This section documents every IPC channel between components. All connections use ## ZMQ Socket Protocol Summary -All ZMQ sockets use the `ipc://` transport over Unix domain sockets. The base directory is configured as `zmq_pathdir` in the QUIC config and embedded in full paths in the client/server YAML configs. +All ZMQ sockets use the `ipc://` transport over Unix domain sockets. The base directory is configured as `zmq_dir` in all config files. Socket names are stored as bare names in the YAML configs and automatically resolved to full `ipc:///` paths by the Python orchestrators (`client_main.py`, `server_main.py`) and the Rust QUIC binaries. | Socket Name | Type | Binder (Role) | Connector (Role) | Payload | |-------------|------|---------------|-------------------|---------| diff --git a/docs/LOGGING.md b/docs/LOGGING.md index 9943ee0..6c66a2f 100644 --- a/docs/LOGGING.md +++ b/docs/LOGGING.md @@ -6,7 +6,12 @@ This document provides detailed documentation for the experiment logging system Every component logs structured experiment data to **Parquet files** via the SpillableStore pattern (Python) or the `create_flush_parquet` utility (Rust). Records accumulate in memory up to a configurable `max_entries` threshold, then spill to a numbered Parquet file on disk. This produces sequentially numbered part files during a single experiment run. -All Python-side Parquet files are written to the `client_savedir` / `server_log_savedir` / `ping_savedir` directories specified in the YAML configs (typically `~/experiment2-out/client/` and `~/experiment2-out/server/`). Rust-side Parquet files are written to `quic_client_log_path` / `quic_server_log_path` (typically `~/experiment2-out/quic-client-out/` and `~/experiment2-out/quic-server-out/`). +Log output directories are **automatically created with timestamps** by each orchestrator at startup — they are not specified directly in the YAML configs. Instead, each config file specifies an `experiment_output_dir` base path and a subdirectory name. The orchestrators create a timestamped run directory under the base path and inject the full save path into each component's config. + +- **Python client-side** (`client_main.py`): Creates `/client_main_//` and injects it as `client_savedir`, `camera_savedir`, and `ping_savedir` for all client components. +- **Python server-side** (`server_main.py`): Creates `/server_main_//` and injects it as `server_log_savedir` for all model servers. +- **QUIC client** (`quic_client`): Creates `/quic_client_//` for QUIC client Parquet logs. +- **QUIC server** (`quic_server`): Creates `/quic_server_//` for QUIC server Parquet logs. ## Python-Side Logs diff --git a/src/python/client_main.py b/src/python/client_main.py index 40432ef..0042683 100644 --- a/src/python/client_main.py +++ b/src/python/client_main.py @@ -75,6 +75,63 @@ logging.config.dictConfig(log_config) + # Create timestamped run directory and ZMQ directory for this experiment run + experiment_output_dir = Path(config_doc["experiment_output_dir"]) + client_subdir = config_doc.get("client_subdir", "client") + zmq_dir = Path(config_doc["zmq_dir"]) + + timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + run_dir = experiment_output_dir / f"client_main_{timestamp}" + client_dir = run_dir / client_subdir + + client_dir.mkdir(parents=True, exist_ok=True) + zmq_dir.mkdir(parents=True, exist_ok=True) + + logger.info("Experiment run directory: %s", run_dir) + + def resolve_zmq(name: str) -> str: + return f"ipc://{zmq_dir / name}" + + # Resolve paths in config dicts before creating Pydantic models + for doc in config_doc["main_client_config_list"]: + doc["client_savedir"] = str(client_dir) + for key in [ + "camera_bidirectional_zmq_sockname", + "bandwidth_allocation_incoming_zmq_sockname", + "quic_rcv_zmq_sockname", + "quic_snd_zmq_sockname", + "outgoing_zmq_diagnostic_sockname", + "zmq_kill_switch_sockname", + ]: + doc[key] = resolve_zmq(doc[key]) + + bw_doc = config_doc["bandwidth_allocator_config"] + for key in [ + "outgoing_zmq_diagnostic_sockname", + "bidirectional_zmq_quic_sockname", + "zmq_kill_switch_sockname", + "bidirectional_zmq_ping_handler_sockname", + ]: + bw_doc[key] = resolve_zmq(bw_doc[key]) + bw_doc["outgoing_zmq_client_socknames"] = [ + resolve_zmq(name) for name in bw_doc["outgoing_zmq_client_socknames"] + ] + + ping_doc = config_doc["ping_handler_config"] + ping_doc["ping_savedir"] = str(client_dir) + for key in ["bidirectional_zmq_sockname", "zmq_kill_switch_sockname"]: + ping_doc[key] = resolve_zmq(ping_doc[key]) + + for doc in config_doc["camera_stream_config_list"]: + doc["camera_savedir"] = str(client_dir) + for key in ["bidirectional_zmq_sockname", "zmq_kill_switch_sockname"]: + doc[key] = resolve_zmq(doc[key]) + + plotter_doc = config_doc["main_plotter_config"] + plotter_doc["zmq_incoming_diagnostic_name"] = resolve_zmq( + plotter_doc["zmq_incoming_diagnostic_name"] + ) + logger.info("experiment starting.") proc_results = [] # List of (process_name, AsyncResult) diff --git a/src/python/server.py b/src/python/server.py index c450476..66ac999 100644 --- a/src/python/server.py +++ b/src/python/server.py @@ -62,6 +62,7 @@ class ModelServerConfig(BaseModel): shm_filesize: int zmq_kill_switch_sockname: str mock_inference_output_path: Optional[str] = None + mock_model_latency_csv_path: Optional[str] = None MODEL_SERVER_STORE_SCHEMA = { @@ -273,9 +274,17 @@ def __init__(self, config: ModelServerConfig) -> None: raise ConfigurationError(f"Mock inference output file not found: {mock_path}") self.mock_result = np.fromfile(str(mock_path), dtype=np.float32) - # TODO: use actual model benchmarks to tune this magic number. - mock_sleep_time = .50 # sleep for 50 ms - time.sleep(mock_sleep_time) + # Load model latency benchmarks from CSV for realistic per-request mock timing + self.mock_latency_map = {} + if config.mock_model_latency_csv_path is not None: + csv_path = Path(config.mock_model_latency_csv_path) + if not csv_path.exists(): + raise ConfigurationError(f"Mock model latency CSV not found: {csv_path}") + df = pl.read_csv(str(csv_path)) + for row in df.iter_rows(named=True): + self.mock_latency_map[row["Model"]] = row["Runtime [ms]"] / 1000.0 + LOGGER.info("ModelServer %d: Loaded mock latency map with %d entries", + self.service_id, len(self.mock_latency_map)) LOGGER.info("ModelServer %d: Mock mode enabled, loaded mock output from %s (shape=%s)", self.service_id, mock_path, self.mock_result.shape) @@ -441,7 +450,17 @@ def main_loop(self) -> None: if self.mock_mode: preprocessing_latency = 0.0 - inference_latency = 0.0 + + mock_sleep_secs = self.mock_latency_map.get(recv_obj.requested_processing) + if mock_sleep_secs is not None: + time.sleep(mock_sleep_secs) + else: + LOGGER.warning( + "ModelServer %d: No latency entry for '%s' in mock latency map, skipping sleep", + self.service_id, recv_obj.requested_processing + ) + + inference_latency = time.perf_counter() - start_time - deserialization_polling_latency result_numpy = self.mock_result else: this_image = recv_obj.input_image diff --git a/src/python/server_main.py b/src/python/server_main.py index cabbcbf..7273f39 100644 --- a/src/python/server_main.py +++ b/src/python/server_main.py @@ -67,6 +67,26 @@ logging.config.dictConfig(log_config) + # Create timestamped run directory and ZMQ directory for this experiment run + experiment_output_dir = Path(config_doc["experiment_output_dir"]) + server_subdir = config_doc.get("server_subdir", "server") + zmq_dir = Path(config_doc["zmq_dir"]) + + timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + run_dir = experiment_output_dir / f"server_main_{timestamp}" + server_dir = run_dir / server_subdir + + server_dir.mkdir(parents=True, exist_ok=True) + zmq_dir.mkdir(parents=True, exist_ok=True) + + logger.info("Experiment run directory: %s", run_dir) + + # Resolve paths in server config dicts before creating Pydantic models + for doc in config_doc["server_config_list"]: + doc["server_log_savedir"] = str(server_dir) + for key in ["incoming_zmq_sockname", "outgoing_zmq_sockname", "zmq_kill_switch_sockname"]: + doc[key] = f"ipc://{zmq_dir / doc[key]}" + logger.info("experiment starting.") proc_results = [] # List of (process_name, AsyncResult) diff --git a/src/quic/Cargo.toml b/src/quic/Cargo.toml index b6d9c6a..37d12b1 100644 --- a/src/quic/Cargo.toml +++ b/src/quic/Cargo.toml @@ -34,3 +34,4 @@ env_logger = "0.11.8" config = "0.15.11" libc = "0.2.174" ctrlc = "3.5.0" +chrono = "0.4" diff --git a/src/quic/quic_client/Cargo.toml b/src/quic/quic_client/Cargo.toml index b5d0b4f..4331f9d 100644 --- a/src/quic/quic_client/Cargo.toml +++ b/src/quic/quic_client/Cargo.toml @@ -27,6 +27,7 @@ env_logger = {workspace = true} config = {workspace = true} libc = {workspace = true} ctrlc = {workspace = true} +chrono = {workspace = true} quic_conn = {path = "../quic_conn"} diff --git a/src/quic/quic_client/src/client.rs b/src/quic/quic_client/src/client.rs index 8b467ec..d65d44c 100644 --- a/src/quic/quic_client/src/client.rs +++ b/src/quic/quic_client/src/client.rs @@ -64,9 +64,14 @@ async fn main() -> Result<(), Box> { .add_source(File::with_name(file_path)) .build()?; - let binding = config.get_string("quic_client_log_path")?.clone(); - let quic_client_log_dir = Path::new(&binding); - assert!(quic_client_log_dir.exists() && quic_client_log_dir.is_dir()); + let experiment_output_dir = config.get_string("experiment_output_dir")?; + let quic_client_log_subdir = config.get_string("quic_client_log_subdir")?; + let timestamp = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S"); + let quic_client_log_path_buf = Path::new(&experiment_output_dir) + .join(format!("quic_client_{}", timestamp)) + .join(&quic_client_log_subdir); + std::fs::create_dir_all(&quic_client_log_path_buf)?; + let quic_client_log_dir = quic_client_log_path_buf.as_path(); let bw_stat_log_capacity = config.get_int("bw_stat_log_capacity")? as usize; let _allocation_stat_log_capacity = config.get_int("allocation_stat_log_capacity")? as usize; @@ -84,20 +89,20 @@ async fn main() -> Result<(), Box> { let enable_junk_service: bool = config.get_bool("enable_junk_service")?; let quic_config = QuicConfig::read_from_config(config); - let (timing_config, init_allocation, zmq_pathdir, services) = ( + let (timing_config, init_allocation, zmq_dir, services) = ( quic_config.timing_config, quic_config.init_allocation, - quic_config.zmq_pathdir, + quic_config.zmq_dir, quic_config.services, ); - assert!(Path::new(&zmq_pathdir).exists() && Path::new(&zmq_pathdir).is_dir()); + assert!(Path::new(&zmq_dir).exists() && Path::new(&zmq_dir).is_dir()); let get_zmq_fullpath = |suffix: &str| { format!( "ipc://{}", String::from( - Path::new(&zmq_pathdir) + Path::new(&zmq_dir) .join(suffix) .to_str() .expect("ZMQ path must be valid UTF-8"), @@ -129,7 +134,7 @@ async fn main() -> Result<(), Box> { )? .start()?; - let _zmq_path = Path::new(&zmq_pathdir); + let _zmq_path = Path::new(&zmq_dir); let addr: SocketAddr = ip_addr.parse()?; let connect = Connect::new(addr).with_server_name("localhost"); let mut connection = tokio::task::spawn_blocking(|| async move { diff --git a/src/quic/quic_conn/src/utils/quic_config.rs b/src/quic/quic_conn/src/utils/quic_config.rs index acd85e1..0799f91 100644 --- a/src/quic/quic_conn/src/utils/quic_config.rs +++ b/src/quic/quic_conn/src/utils/quic_config.rs @@ -11,7 +11,7 @@ use config::Config; pub struct QuicConfig { pub timing_config: TimingConfig, pub init_allocation: f64, - pub zmq_pathdir: String, + pub zmq_dir: String, pub services: Vec, } @@ -66,9 +66,9 @@ impl QuicConfig { let init_allocation = config .get_float("init_allocation") .expect("config must contain 'init_allocation'"); - let zmq_pathdir = config - .get_string("zmq_pathdir") - .expect("config must contain 'zmq_pathdir'"); + let zmq_dir = config + .get_string("zmq_dir") + .expect("config must contain 'zmq_dir'"); let services: Vec = config .get_array("services") .expect("config must contain 'services' array"); @@ -76,7 +76,7 @@ impl QuicConfig { QuicConfig { timing_config, init_allocation, - zmq_pathdir, + zmq_dir, services: Vec::from_iter(services.iter().map(|v| { v.clone() .into_int() diff --git a/src/quic/quic_server/Cargo.toml b/src/quic/quic_server/Cargo.toml index 575664d..54e4da8 100644 --- a/src/quic/quic_server/Cargo.toml +++ b/src/quic/quic_server/Cargo.toml @@ -27,6 +27,7 @@ env_logger = {workspace = true} config = {workspace = true} libc = {workspace = true} ctrlc = {workspace = true} +chrono = {workspace = true} quic_conn = {path = "../quic_conn"} diff --git a/src/quic/quic_server/src/server.rs b/src/quic/quic_server/src/server.rs index 71d9a67..1f2207c 100644 --- a/src/quic/quic_server/src/server.rs +++ b/src/quic/quic_server/src/server.rs @@ -132,12 +132,19 @@ async fn handle_stream( .build() .expect("config must build successfully"); - let binding = config - .get_string("quic_server_log_path") - .expect("config must contain 'quic_server_log_path'") - .clone(); - let quic_server_log_dir = Path::new(&binding); - assert!(quic_server_log_dir.exists() && quic_server_log_dir.is_dir()); + let experiment_output_dir = config + .get_string("experiment_output_dir") + .expect("config must contain 'experiment_output_dir'"); + let quic_server_log_subdir = config + .get_string("quic_server_log_subdir") + .expect("config must contain 'quic_server_log_subdir'"); + let timestamp = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S"); + let quic_server_log_path = Path::new(&experiment_output_dir) + .join(format!("quic_server_{}", timestamp)) + .join(&quic_server_log_subdir); + std::fs::create_dir_all(&quic_server_log_path) + .expect("must be able to create quic server log directory"); + let quic_server_log_dir = quic_server_log_path.as_path(); let bw_stat_log_capacity = config .get_int("bw_stat_log_capacity") @@ -179,10 +186,10 @@ async fn handle_stream( info!("Server config parsed successfully for service={}", service_string); let quic_config = QuicConfig::read_from_config(config); - let (timing_config, _init_allocation, zmq_pathdir, services) = ( + let (timing_config, _init_allocation, zmq_dir, services) = ( quic_config.timing_config, quic_config.init_allocation, - quic_config.zmq_pathdir, + quic_config.zmq_dir, quic_config.services, ); @@ -190,7 +197,7 @@ async fn handle_stream( format!( "ipc://{}", String::from( - Path::new(&zmq_pathdir) + Path::new(&zmq_dir) .join(suffix) .to_str() .expect("ZMQ path must be valid UTF-8"), From 42ac239bf9e8a5edf5a261b5c024f80b8c561ea1 Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Sat, 14 Feb 2026 01:58:46 -0800 Subject: [PATCH 04/15] fix: fix mock logic paths; add stack trace outputs for errors --- ...utput.npz => example_effdet_d4_output.npy} | Bin 2400 -> 2528 bytes src/python/client_main.py | 4 +++- src/python/server.py | 4 +++- src/python/server_main.py | 4 +++- src/python/web_frontend/web_config.py | 8 ++++++-- src/quic/quic_client/src/client.rs | 5 +++++ src/quic/quic_server/src/server.rs | 7 ++++++- 7 files changed, 26 insertions(+), 6 deletions(-) rename src/python/camera_stream/{example_effdet_d4_output.npz => example_effdet_d4_output.npy} (94%) diff --git a/src/python/camera_stream/example_effdet_d4_output.npz b/src/python/camera_stream/example_effdet_d4_output.npy similarity index 94% rename from src/python/camera_stream/example_effdet_d4_output.npz rename to src/python/camera_stream/example_effdet_d4_output.npy index f480c67fc5ad78dd74dca1cd22fa244c53ebcfd6..d5567ba7c93196e5b5ffb716952ee529cfbf64cd 100644 GIT binary patch delta 137 zcmaDL^gy_NvR|lgKqMnW8AG*tN@{U(k-C+Fx=osix{iW+T7FSUQDR None: mock_path = Path(config.mock_inference_output_path) if not mock_path.exists(): raise ConfigurationError(f"Mock inference output file not found: {mock_path}") - self.mock_result = np.fromfile(str(mock_path), dtype=np.float32) + self.mock_result = np.load(str(mock_path)) # Load model latency benchmarks from CSV for realistic per-request mock timing self.mock_latency_map = {} @@ -282,6 +282,8 @@ def __init__(self, config: ModelServerConfig) -> None: raise ConfigurationError(f"Mock model latency CSV not found: {csv_path}") df = pl.read_csv(str(csv_path)) for row in df.iter_rows(named=True): + if row["Model"] is None or row["Runtime [ms]"] is None: + continue self.mock_latency_map[row["Model"]] = row["Runtime [ms]"] / 1000.0 LOGGER.info("ModelServer %d: Loaded mock latency map with %d entries", self.service_id, len(self.mock_latency_map)) diff --git a/src/python/server_main.py b/src/python/server_main.py index 7273f39..2ad591e 100644 --- a/src/python/server_main.py +++ b/src/python/server_main.py @@ -32,6 +32,7 @@ from logging import FileHandler from pathlib import Path import signal +import traceback import yaml from multiprocessing import Pool, Queue, Pipe import time @@ -113,7 +114,8 @@ def run_server(server_config): def err_callback(err): """Handle process pool errors by sending ABORT to all processes.""" global early_abort - logger.error("Process error: %s: %s", type(err).__name__, err) + tb = "".join(traceback.format_exception(type(err), err, err.__traceback__)) + logger.error("Process error: %s: %s\n%s", type(err).__name__, err, tb) # for ks in kill_switches: # ks.send_string("ABORT") early_abort = True diff --git a/src/python/web_frontend/web_config.py b/src/python/web_frontend/web_config.py index 7438295..3ea01ec 100644 --- a/src/python/web_frontend/web_config.py +++ b/src/python/web_frontend/web_config.py @@ -7,6 +7,7 @@ default configurations for standalone testing. """ +from pathlib import Path import yaml from util.plotting_main import MainPlotterConfig from util.bandwidth_allocation_plot import BandwidthAllocationPlotConfig @@ -26,8 +27,11 @@ def load_config_from_yaml(config_file: str = "client_config.yaml") -> MainPlotte if "main_plotter_config" in config_data: plotter_config = config_data["main_plotter_config"] - zmq_name = plotter_config["zmq_incoming_diagnostic_name"] - + # Resolve the bare ZMQ socket name to a full ipc:// path using zmq_dir, + # matching what client_main.py does with resolve_zmq() + zmq_dir = config_data["zmq_dir"] + zmq_name = f"ipc://{Path(zmq_dir) / plotter_config['zmq_incoming_diagnostic_name']}" + # Build the configuration using your actual structure plotting_config = { "zmq_incoming_diagnostic_name": zmq_name, diff --git a/src/quic/quic_client/src/client.rs b/src/quic/quic_client/src/client.rs index d65d44c..3472b11 100644 --- a/src/quic/quic_client/src/client.rs +++ b/src/quic/quic_client/src/client.rs @@ -45,7 +45,12 @@ pub static CERT_PEM: &str = #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<(), Box> { + std::env::set_var("RUST_BACKTRACE", "1"); env_logger::init(); + std::panic::set_hook(Box::new(|panic_info| { + let backtrace = std::backtrace::Backtrace::force_capture(); + error!("Panic: {}\n{}", panic_info, backtrace); + })); info!("QUIC client starting"); let args: Vec = env::args().collect(); diff --git a/src/quic/quic_server/src/server.rs b/src/quic/quic_server/src/server.rs index 1f2207c..c9a9e0e 100644 --- a/src/quic/quic_server/src/server.rs +++ b/src/quic/quic_server/src/server.rs @@ -11,7 +11,7 @@ use atomic_float::AtomicF64; use config::{Config, File}; -use log::{debug, info}; +use log::{debug, error, info}; use s2n_quic::provider::congestion_controller::Bbr; use s2n_quic::{stream::BidirectionalStream, Server}; use std::env; @@ -37,7 +37,12 @@ use quic_conn::utils::{quic_config::QuicConfig, tokio_context::TokioContext}; #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<(), Box> { + std::env::set_var("RUST_BACKTRACE", "1"); env_logger::init(); + std::panic::set_hook(Box::new(|panic_info| { + let backtrace = std::backtrace::Backtrace::force_capture(); + error!("Panic: {}\n{}", panic_info, backtrace); + })); let recovery_ptr = Arc::new(RecoverySnapshot { rtt: AtomicF64::new(5.), From dd75f005a07085046fde13c503969fe72ae8d3d4 Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Sun, 1 Mar 2026 01:33:22 -0800 Subject: [PATCH 05/15] feat: docker setup; other doc and config refactors --- .dockerignore | 25 ++ README.md | 279 ++++++++++++++--- config/client_config.yaml | 16 +- config/server_config_gcloud.yaml | 19 +- docker/.env.example | 47 +++ docker/Dockerfile_turbo_python_base | 42 +++ docker/Dockerfile_turbo_python_binary | 28 ++ docker/Dockerfile_turbo_quic_binary | 39 +++ docker/Dockerfile_turbo_rust_base | 36 +++ docker/README.Docker.md | 189 ++++++++++++ docker/compose.yaml | 288 ++++++++++++++++++ docker/config/client_config_docker.yaml | 225 ++++++++++++++ docker/config/logging_config_docker.yaml | 66 ++++ docker/config/quic_config_client_docker.yaml | 35 +++ docker/config/quic_config_gcloud_docker.yaml | 34 +++ .../config/server_config_gcloud_docker.yaml | 83 +++++ docs/CONFIGURATION.md | 6 +- docs/IPC.md | 2 +- src/python/client.py | 5 +- src/python/client_main.py | 37 ++- src/python/server_main.py | 5 +- .../web_frontend/start_web_dashboard.py | 2 +- src/python/web_frontend/web_frontend.py | 9 +- 23 files changed, 1451 insertions(+), 66 deletions(-) create mode 100644 .dockerignore create mode 100644 docker/.env.example create mode 100644 docker/Dockerfile_turbo_python_base create mode 100644 docker/Dockerfile_turbo_python_binary create mode 100644 docker/Dockerfile_turbo_quic_binary create mode 100644 docker/Dockerfile_turbo_rust_base create mode 100644 docker/README.Docker.md create mode 100644 docker/compose.yaml create mode 100644 docker/config/client_config_docker.yaml create mode 100644 docker/config/logging_config_docker.yaml create mode 100644 docker/config/quic_config_client_docker.yaml create mode 100644 docker/config/quic_config_gcloud_docker.yaml create mode 100644 docker/config/server_config_gcloud_docker.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6c79fb9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.DS_Store +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/charts +**/docker-compose* +**/compose.y*ml +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/secrets.dev.yaml +**/values.dev.yaml +/bin +/target +LICENSE diff --git a/README.md b/README.md index 7dcc8e6..6e1635b 100644 --- a/README.md +++ b/README.md @@ -91,56 +91,46 @@ Running on a GPU-equipped cloud instance (e.g., H100): For detailed architecture, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). -## Quick Start +## Quick Start (Docker) — Recommended + +The recommended way to run TURBO is with Docker. The Docker setup automatically orchestrates all processes — 2 on the server (QUIC server + model servers) and 3 on the client (client orchestrator + web dashboard + QUIC client) — handling startup ordering, ZMQ socket management, and inter-process communication for you. ### Prerequisites -**Client (AV) side:** -- Python 3.10; preferably managed via [uv](https://docs.astral.sh/uv/) (alternatively, via [Anaconda](https://anaconda.org/), specifically the [`Miniconda3-py310_25.11.1-1` release version on this page](https://repo.anaconda.com/miniconda/)) -- Rust 1.70+ (for QUIC transport) -- USB webcams (or video sources) +- [Docker Engine](https://docs.docker.com/engine/install/) 24.0+ with [Docker Compose V2](https://docs.docker.com/compose/install/) +- [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) (for GPU inference) - Linux (tested on Ubuntu 20.04+) -**Server (Cloud) side:** -- Python 3.10; preferably managed via [uv](https://docs.astral.sh/uv/) (alternatively, via [Anaconda](https://anaconda.org/), specifically the [`Miniconda3-py310_25.11.1-1` release version on this page](https://repo.anaconda.com/miniconda/)) -- CUDA-capable GPU (tested on H100, A100) -- PyTorch 2.0+ -- Rust 1.70+ (for QUIC transport) -- Fine-tuned EfficientDet model checkpoints (see [Model Setup](#model-setup) below) -- Needed dependencies for `OpenCV` -- (e.g. `sudo apt-get update && sudo apt-get install ffmpeg libsm6 libxext6`) +Verify your setup: +```bash +docker compose version # should be v2.20+ +nvidia-smi # should show your GPU(s) +docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU in Docker +``` -### Installation +### Setup -1. **Clone the repository and install dependencies:** +1. **Clone the repository:** ```bash git clone https://github.com/NetSys/turbo.git cd turbo - uv sync ``` -
- Alternative: using pip - - ```bash - pip install . - ``` -
+2. **Download fine-tuned EfficientDet model checkpoints (server only):** -2. **Download fine-tuned EfficientDet model checkpoints:** + The system uses custom EfficientDet models (D1, D2, D4, D6, D7x) fine-tuned on the [Waymo Open Dataset](https://waymo.com/open/) for 5-class object detection (vehicle, pedestrian, cyclist, sign, unknown). - The system uses custom EfficientDet models (D1, D2, D4, D6, D7x) fine-tuned on the [Waymo Open Dataset](https://waymo.com/open/) for 5-class object detection (vehicle, pedestrian, cyclist, sign, unknown). - Our fine-tuned models can be downloaded and extracted as follows: ```bash # Download the model archive wget https://storage.googleapis.com/turbo-nines-2026/av-models.zip - # Extract to a location of your choice (e.g., ~/av-models in this example) + # Extract to your home directory (creates ~/av-models/) unzip av-models.zip -d ~ ``` - After extraction, update the checkpoint paths in your server configuration file (`config/server_config_gcloud.yaml`) and model config (`src/python/model_server/model_config.yaml`) to point to the extracted checkpoint files. See [docs/MODELS.md](docs/MODELS.md) for detailed model information and configuration. + See [docs/MODELS.md](docs/MODELS.md) for detailed model information. > **IMPORTANT — Waymo Open Dataset License Notice** > @@ -152,6 +142,130 @@ For detailed architecture, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). > > These models were made using the Waymo Open Dataset, provided by Waymo LLC. +3. **Download pre-computed evaluation data (client only):** + + The client requires pre-computed full evaluation data for utility curve computation. Download and extract as follows: + + ```bash + # Download the evaluation data archive + wget https://storage.googleapis.com/turbo-nines-2026/full-eval.zip + + # Extract to your home directory (creates ~/full-eval/) + unzip full-eval.zip -d ~ + ``` + +4. **Generate SSL keys for QUIC:** + ```bash + cd src/quic + pip install cryptography # if not already installed + python generate_cert.py + cd ../.. + ``` + +5. **Configure the `.env` file:** + + ```bash + cp docker/.env.example docker/.env + ``` + + Edit `docker/.env` and update the following values to match your host system: + + | Variable | Description | Default | + |---|---|---| + | `HOST_UID` | Your host user ID (run `id -u`) | `1000` | + | `HOST_GID` | Your host group ID (run `id -g`) | `1000` | + | `EXPERIMENT_OUTPUT_DIR` | Absolute path for experiment output | (must set) | + | `EFFDET_MODELS_DIR` | Absolute path to model checkpoints (server) | (must set) | + | `MODEL_FULL_EVAL_DIR` | Absolute path to evaluation data (client) | (must set) | + + Most other settings (networking, ports, SSL paths) work out of the box for same-host testing. See [docker/README.Docker.md](docker/README.Docker.md) for the full reference. + +6. **Create the experiment output directory:** + ```bash + mkdir -p ~/experiment2-out + ``` + +### Running + +All Docker commands should be run from the `docker/` directory: +```bash +cd docker +``` + +**Run both client and server on the same host:** +```bash +docker compose --profile client --profile server up --build +``` + +**Run server only** (e.g., on a cloud GPU machine): +```bash +docker compose --profile server up --build +``` + +**Run client only** (when server is running elsewhere — update `QUIC_CLIENT_ADDR` in `.env` to the server's IP): +```bash +docker compose --profile client up --build +``` + +Once running, open the monitoring dashboard at **http://localhost:5000**. + +**Shut down:** +```bash +docker compose --profile client --profile server down -v +``` + +The `-v` flag removes ephemeral volumes (ZMQ sockets, health signals), giving you a clean slate for the next run. + +**Experiment output** will be logged to Parquet files in the configured output directory (default: `~/experiment2-out/`). + +For development workflows (hot-reload, rebuilding), troubleshooting, and architecture details, see [docker/README.Docker.md](docker/README.Docker.md). + +--- + +## Alternative: Manual Setup (without Docker) + +
+Click to expand manual setup instructions + +If you prefer to run each process directly on your host without Docker, follow the steps below. This requires installing all dependencies (Python, Rust, system libraries) manually on both client and server machines, and carefully managing process startup order. + +### Prerequisites + +**Client (AV) side:** +- Python 3.10; preferably managed via [uv](https://docs.astral.sh/uv/) (alternatively, via [Anaconda](https://anaconda.org/), specifically the [`Miniconda3-py310_25.11.1-1` release version on this page](https://repo.anaconda.com/miniconda/)) +- Rust 1.70+ (for QUIC transport) +- USB webcams (or video sources) +- Linux (tested on Ubuntu 20.04+) +- Needed dependencies for `OpenCV` -- (e.g. `sudo apt-get update && sudo apt-get install ffmpeg libsm6 libxext6`) + +**Server (Cloud) side:** +- Python 3.10; preferably managed via [uv](https://docs.astral.sh/uv/) (alternatively, via [Anaconda](https://anaconda.org/), specifically the [`Miniconda3-py310_25.11.1-1` release version on this page](https://repo.anaconda.com/miniconda/)) +- CUDA-capable GPU (tested on H100, A100) +- PyTorch 2.0+ +- Rust 1.70+ (for QUIC transport) +- Fine-tuned EfficientDet model checkpoints (see [Model Setup](#model-setup) above) +- Needed dependencies for `OpenCV` -- (e.g. `sudo apt-get update && sudo apt-get install ffmpeg libsm6 libxext6`) + +### Installation + +1. **Install dependencies:** + ```bash + cd turbo + uv sync + ``` + +
+ Alternative: using pip + + ```bash + pip install . + ``` +
+ +2. **Download model checkpoints and evaluation data** — follow steps 2 and 3 from the [Docker Quick Start](#quick-start-docker--recommended) above. + + After extraction, update the checkpoint paths in your server configuration file (`config/server_config_gcloud.yaml`) and model config (`src/python/model_server/model_config.yaml`) to point to the extracted checkpoint files. Also ensure the `full_eval_dir` path in `config/client_config.yaml` points to the extracted `~/full-eval/` directory. + 3. **Generate SSL Keys for QUIC:** ```bash cd src/quic @@ -188,16 +302,16 @@ For detailed architecture, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). **On the server (cloud) side:** 0. Do the following pre-run steps: - - If previous runs were done: + - If previous runs were done: - Clear all previous zeromq socket files from any previous runs, if they exist. In this example, just remove all contents of the directory containing the zeromq files: ```bash rm ~/experiment2-out/zmq/* ``` - - Stash the previous log outputs from any previous runs, if they exist, and make sure the directories for storing the log outputs produced by all parts of this system are empty. + - Stash the previous log outputs from any previous runs, if they exist, and make sure the directories for storing the log outputs produced by all parts of this system are empty. - If this is the first run: - - Make output directories to store each of the log outputs for your current run. - - Make output directories to store `ZeroMQ` IPC socket files. - + - Make output directories to store each of the log outputs for your current run. + - Make output directories to store `ZeroMQ` IPC socket files. + For reference, the author's output directory structure was created as follows: ```bash @@ -207,7 +321,7 @@ For detailed architecture, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). mkdir ~/experiment2-out/server mkdir ~/experiment2-out/quic-client-out mkdir ~/experiment2-out/quic-server-out - ``` + ``` 1. Start the QUIC server: ```bash @@ -232,22 +346,22 @@ For detailed architecture, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). ```bash rm ~/experiment2-out/zmq/* ``` - - Stash the previous log outputs from any previous runs, if they exist, and make sure the directories for storing the log outputs produced by all parts of this system are empty. + - Stash the previous log outputs from any previous runs, if they exist, and make sure the directories for storing the log outputs produced by all parts of this system are empty. - If this is the first run: - Allow ping requests (our PingHandler module needs to send pings from user-land): - ```bash + ```bash sudo sysctl net.ipv4.ping_group_range='0 2147483647' ``` - - Make output directories to store each of the log outputs for your current run. - - Make output directories to store `ZeroMQ` IPC socket files. - + - Make output directories to store each of the log outputs for your current run. + - Make output directories to store `ZeroMQ` IPC socket files. + **IMPORTANT:** The ordering of the following steps matters due to a behavior in ZeroMQ socket binding. See [docs/IPC.md](docs/IPC.md) for details. 1. Start the client processes (in a separate terminal): ```bash cd src/python - uv run client_main.py -c ../../config/client_config.yaml + uv run client_main.py -c ../../config/client_config.yaml -s ``` (or `python client_main.py ...` if using a pip-installed environment) @@ -270,6 +384,79 @@ For detailed architecture, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). **Experiment output** will be logged to Parquet files in the configured output directories (default: `~/experiment2-out/`). +
+ +## Mock Modes + +TURBO supports two independent mock modes for testing and development without requiring physical cameras or GPUs. Each can be enabled or disabled independently by setting or nulling the corresponding config key. + +### Mock Camera Mode (Client-Side) + +Replaces live USB camera capture with a static image. Each camera stream reads a pre-loaded image from disk instead of capturing from a webcam. + +**Config key:** `mock_camera_image_path` in `camera_stream_config_list` entries + +| Value | Behavior | +|---|---| +| File path (e.g. `mock_webcam_image.jpg`) | Loads the image and serves it as every frame | +| `null` | Uses the real USB camera | + +A sample mock image is included at `src/python/camera_stream/mock_webcam_image.jpg`. + +**Example** (in `config/client_config.yaml` or `docker/config/client_config_docker.yaml`): +```yaml +camera_stream_config_list: + - camera_id: 1 + # ... other fields ... + mock_camera_image_path: /home/hwei/turbo/src/python/camera_stream/mock_webcam_image.jpg +``` + +Set to `null` to use real cameras: +```yaml + mock_camera_image_path: null +``` + +### Mock Inference Mode (Server-Side) + +Skips GPU model loading and inference entirely. The server returns a pre-recorded detection result (a numpy array) instead of running EfficientDet on the GPU. Optionally simulates per-model inference latency using a CSV of benchmark timings. + +**Config keys:** `mock_inference_output_path` and `mock_model_latency_csv_path` in `server_config_list` entries + +| Key | Value | Behavior | +|---|---|---| +| `mock_inference_output_path` | File path (e.g. `example_effdet_d4_output.npy`) | Returns pre-recorded detections, skips GPU | +| `mock_inference_output_path` | `null` | Loads models and runs real GPU inference | +| `mock_model_latency_csv_path` | File path (e.g. `experiment_model_info.csv`) | Simulates realistic per-model latency in mock mode | +| `mock_model_latency_csv_path` | `null` | No simulated delay (returns immediately, logs a warning per request) | + +A sample mock output is included at `src/python/camera_stream/example_effdet_d4_output.npy`. + +**Example** (in `config/server_config_gcloud.yaml` or `docker/config/server_config_gcloud_docker.yaml`): +```yaml +server_config_list: + - service_id: 1 + # ... other fields ... + mock_inference_output_path: /home/hwei/turbo/src/python/camera_stream/example_effdet_d4_output.npy + mock_model_latency_csv_path: /home/hwei/turbo/experiment_model_info.csv +``` + +Set to `null` to use real GPU inference: +```yaml + mock_inference_output_path: null + mock_model_latency_csv_path: null +``` + +### Combining Mock Modes + +The two mock modes are fully independent — you can use any combination: + +| Camera Mock | Inference Mock | Use Case | +|---|---|---| +| Off | Off | **Production** — real cameras, real GPU inference | +| On | Off | Test the full pipeline without cameras (still needs GPU) | +| Off | On | Test camera capture and transport without GPU | +| On | On | **Full mock** — test the entire system without cameras or GPU | + ## Documentation - **[Model Setup & Reference](docs/MODELS.md)** - EfficientDet model download, configuration, and inference details @@ -298,6 +485,12 @@ An LP-based allocator runs every 500ms to select the optimal (model, compression ``` turbo/ +├── docker/ # Docker deployment (recommended) +│ ├── compose.yaml # Docker Compose orchestration +│ ├── .env.example # Template for configurable paths and settings +│ ├── config/ # Docker-specific YAML configs +│ ├── Dockerfile_turbo_* # Multi-stage Dockerfiles +│ └── README.Docker.md # Docker setup documentation ├── src/ │ ├── python/ │ │ ├── client_main.py # Client-side process orchestrator @@ -315,7 +508,7 @@ turbo/ │ ├── quic_client/ # Client binary │ ├── quic_server/ # Server binary │ └── quic_conn/ # Shared library (bandwidth management, logging) -├── config/ # YAML configuration files +├── config/ # YAML configuration files (manual setup) └── docs/ # Detailed documentation ``` @@ -333,7 +526,7 @@ turbo/ Planned features and improvements, in addition to accepted GitHub Issues/PRs: -- [ ] Docker deployment configuration +- [x] Docker deployment configuration - [x] Graceful termination of python services - [x] Graceful handling of Ctrl-C in rust processes (to kill all zmq sockets and shm files, and avoid parquet data loss) - [ ] Migration to full Rust implementation with Rust Python+numpy bindings; @@ -369,4 +562,6 @@ If you use this system in your research, please cite: ``` ## Contact -For questions and feedback, open a [GitHub Issue](https://github.com/NetSys/turbo/issues). \ No newline at end of file +For questions and feedback, open a [GitHub Issue](https://github.com/NetSys/turbo/issues). + + diff --git a/config/client_config.yaml b/config/client_config.yaml index e0ecc8a..e0b507b 100644 --- a/config/client_config.yaml +++ b/config/client_config.yaml @@ -19,9 +19,6 @@ MAX_LOG_ENTRIES: &max-log-entries 100 SLO_TIMEOUT: &slo-timeout 200 # TODO: change this QUIC_SHM_SIZE: &quic-shm-size 50000000 -# needed for ping loop -DST_IP: &dst-ip 136.118.43.70 - main_client_config_list: - service_id: 1 max_entries: *max-log-entries @@ -87,7 +84,6 @@ bandwidth_allocator_config: bidirectional_zmq_ping_handler_sockname: ping-handler ping_handler_config: - dst_ip: *dst-ip max_entries: 100 thread_concurrency: 5 bidirectional_zmq_sockname: ping-handler @@ -103,7 +99,9 @@ camera_stream_config_list: shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] zmq_kill_switch_sockname: camera-kill-1-switch - mock_camera_image_path: null # set to a file path (e.g. mock_webcam_image.jpg) to use a static image instead of webcam + # Mock camera: uncomment ONE of the following lines + # mock_camera_image_path: /home/hwei/turbo/src/python/camera_stream/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam + mock_camera_image_path: null # real mode — captures from USB camera - camera_id: 2 # FRONT LEFT usb_id: 4 # TODO: CONFIGURE THIS max_entries: *max-log-entries @@ -113,7 +111,9 @@ camera_stream_config_list: shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] zmq_kill_switch_sockname: camera-kill-2-switch - mock_camera_image_path: null + # Mock camera: uncomment ONE of the following lines + # mock_camera_image_path: /home/hwei/turbo/src/python/camera_stream/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam + mock_camera_image_path: null # real mode — captures from USB camera - camera_id: 3 # FRONT RIGHT usb_id: 8 # TODO: CONFIGURE THIS max_entries: *max-log-entries @@ -123,7 +123,9 @@ camera_stream_config_list: shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] zmq_kill_switch_sockname: camera-kill-3-switch - mock_camera_image_path: null + # Mock camera: uncomment ONE of the following lines + # mock_camera_image_path: /home/hwei/turbo/src/python/camera_stream/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam + mock_camera_image_path: null # real mode — captures from USB camera main_plotter_config: # service 4 is for junk stream diff --git a/config/server_config_gcloud.yaml b/config/server_config_gcloud.yaml index 9d2d1b4..a5fed64 100644 --- a/config/server_config_gcloud.yaml +++ b/config/server_config_gcloud.yaml @@ -38,8 +38,13 @@ server_config_list: thread_concurrency: 10 shm_filesize: *shm-filesize zmq_kill_switch_sockname: remote-server-kill-switch-1 - mock_inference_output_path: null # set to a file path (e.g. example_effdet_d4_output.npz) to skip model loading and return mock detections - mock_model_latency_csv_path: null # set to experiment_model_info.csv path to simulate per-model inference latency in mock mode + # Mock inference: uncomment ONE of the following blocks + # -- mock mode — skips GPU model loading, returns pre-recorded detections + # mock_inference_output_path: /home/hwei/turbo/src/python/camera_stream/example_effdet_d4_output.npy + # mock_model_latency_csv_path: /home/hwei/turbo/experiment_model_info.csv + # -- real mode — loads models and runs GPU inference + mock_inference_output_path: null + mock_model_latency_csv_path: null - service_id: 2 max_entries: *max-log-entries model_metadata_list: *model-list-ref @@ -51,6 +56,11 @@ server_config_list: thread_concurrency: 10 shm_filesize: *shm-filesize zmq_kill_switch_sockname: remote-server-kill-switch-2 + # Mock inference: uncomment ONE of the following blocks + # -- mock mode — skips GPU model loading, returns pre-recorded detections + # mock_inference_output_path: /home/hwei/turbo/src/python/camera_stream/example_effdet_d4_output.npy + # mock_model_latency_csv_path: /home/hwei/turbo/experiment_model_info.csv + # -- real mode — loads models and runs GPU inference mock_inference_output_path: null mock_model_latency_csv_path: null - service_id: 3 @@ -64,5 +74,10 @@ server_config_list: thread_concurrency: 10 shm_filesize: *shm-filesize zmq_kill_switch_sockname: remote-server-kill-switch-3 + # Mock inference: uncomment ONE of the following blocks + # -- mock mode — skips GPU model loading, returns pre-recorded detections + # mock_inference_output_path: /home/hwei/turbo/src/python/camera_stream/example_effdet_d4_output.npy + # mock_model_latency_csv_path: /home/hwei/turbo/experiment_model_info.csv + # -- real mode — loads models and runs GPU inference mock_inference_output_path: null mock_model_latency_csv_path: null diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 0000000..8a1d7b1 --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,47 @@ +# Host user/group IDs — set these to match your host user (id -u / id -g) +# so that bind-mounted files are accessible inside containers. +HOST_UID=1000 +HOST_GID=1000 + +# quic client-side only +# Same-host: use Docker bridge gateway (10.64.89.1). Remote: use server host's routable IP. +QUIC_CLIENT_ADDR=10.64.89.1:12345 +QUIC_CLIENT_PORT=5000 + +QUIC_DOCKER_CLIENT_CONFIG_PATH=./config/quic_config_client_docker.yaml + +# quic server-side only +QUIC_SERVER_ADDR=0.0.0.0:12345 +QUIC_SERVER_PORT=12345 + +QUIC_DOCKER_SERVER_CONFIG_PATH=./config/quic_config_gcloud_docker.yaml + +# python client config +PYTHON_DOCKER_CLIENT_CONFIG_PATH=./config/client_config_docker.yaml + +# python server config +PYTHON_DOCKER_SERVER_CONFIG_PATH=./config/server_config_gcloud_docker.yaml + +# both client and server-side +DOCKER_LOG_CONFIG_PATH=./config/logging_config_docker.yaml +EXPERIMENT_OUTPUT_DIR=/path/to/experiment2-out + +# client-side only +DASHBOARD_PORT=5000 +MODEL_FULL_EVAL_DIR=/path/to/full-eval + +# server-side only +EFFDET_MODELS_DIR=/path/to/av-models + +# for quic cargo build +SSL_KEY_PATH=./src/quic/ssl_key.pem +SSL_CERT_PATH=./src/quic/ssl_cert.pem + +# rust env vars +RUST_LOG=info +RUST_BACKTRACE=full + +# for both client and server +EXPERIMENT_MODEL_INFO_PATH=../experiment_model_info.csv +MOCK_EFFDET_OUTPUT_PATH=../src/python/camera_stream/example_effdet_d4_output.npy +MOCK_EFFDET_INPUT_PATH=../src/python/camera_stream/mock_webcam_image.jpg \ No newline at end of file diff --git a/docker/Dockerfile_turbo_python_base b/docker/Dockerfile_turbo_python_base new file mode 100644 index 0000000..76f9222 --- /dev/null +++ b/docker/Dockerfile_turbo_python_base @@ -0,0 +1,42 @@ +# syntax=docker/dockerfile:1 + +# doesn't build with torch for python3.10-alpine +FROM ghcr.io/astral-sh/uv:python3.10-trixie AS python_build + +# Enable bytecode compilation +ENV UV_COMPILE_BYTECODE=1 + +# Copy from the cache instead of linking since it's a mounted volume +ENV UV_LINK_MODE=copy + +# Omit development dependencies +ENV UV_NO_DEV=1 + +# Ensure installed tools can be executed out of the box +ENV UV_TOOL_BIN_DIR=/usr/local/bin + +WORKDIR /app + +RUN apt-get update && apt-get install ffmpeg libsm6 libxext6 --yes + +# Install the project's dependencies using the lockfile and settings +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --no-install-project --locked + +# Then, add the rest of the project source code and install it +# Installing separately from its dependencies allows optimal layer caching +COPY ./src/python /app/src/python + +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + --mount=type=bind,source=README.md,target=README.md \ + uv sync --locked + +# Place executables in the environment at the front of the path +ENV PATH="/app/.venv/bin:$PATH" + +ARG GID +RUN groupadd --system --gid ${GID} nonroot diff --git a/docker/Dockerfile_turbo_python_binary b/docker/Dockerfile_turbo_python_binary new file mode 100644 index 0000000..d7bac92 --- /dev/null +++ b/docker/Dockerfile_turbo_python_binary @@ -0,0 +1,28 @@ +# syntax=docker/dockerfile:1 + +FROM base_image AS final +WORKDIR /app + +ARG EXECUTABLE_DIR +ARG EXECUTABLE_NAME + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/go/dockerfile-user-best-practices/ +ARG GID=515 +ARG UID=999 + +# Setup a non-root user +RUN useradd --system --gid ${GID} --uid ${UID} --create-home nonroot +USER nonroot + + +ENV EXECUTABLE_DIR=${EXECUTABLE_DIR} +ENV EXECUTABLE_NAME=${EXECUTABLE_NAME} + +# What the container should run when it is started. +# Signal handling is provided by `init: true` in compose.yaml (tini as PID 1). +# `exec` is optional here — it just avoids a redundant /bin/sh parent process. +# We call python directly instead of `uv run` because uv spawns python as a +# child and may not forward signals. The venv is already on PATH +# (set in Dockerfile_turbo_python_base), so `python` resolves correctly. +CMD cd ${EXECUTABLE_DIR} && exec python ${EXECUTABLE_NAME} "-c" "/app/python_config.yaml" diff --git a/docker/Dockerfile_turbo_quic_binary b/docker/Dockerfile_turbo_quic_binary new file mode 100644 index 0000000..bd346ec --- /dev/null +++ b/docker/Dockerfile_turbo_quic_binary @@ -0,0 +1,39 @@ +# syntax=docker/dockerfile:1 + +FROM base_image +ARG APP_NAME +WORKDIR /app + +FROM alpine:3.18 AS final +ARG APP_NAME +ARG QUIC_BIND_ADDR +ARG QUIC_DOCKER_PORT + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/go/dockerfile-user-best-practices/ +ARG UID=10001 +ARG GID=999 +RUN addgroup -g "${GID}" sharedgroup && \ + adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + --ingroup sharedgroup \ + appuser +USER appuser + +# Copy the executable from the "build" stage. +COPY --from=base_image /bin/$APP_NAME /bin/server + +# Expose the port that the application listens on. +EXPOSE ${QUIC_DOCKER_PORT} + +ENV QUIC_BIND_ADDR=${QUIC_BIND_ADDR} + +# What the container should run when it is started. +# Signal handling is provided by `init: true` in compose.yaml (tini as PID 1). +# `exec` is optional here — it just avoids a redundant /bin/sh parent process. +CMD exec /bin/server "/app/quic_config.yaml" "${QUIC_BIND_ADDR}" diff --git a/docker/Dockerfile_turbo_rust_base b/docker/Dockerfile_turbo_rust_base new file mode 100644 index 0000000..ff5531f --- /dev/null +++ b/docker/Dockerfile_turbo_rust_base @@ -0,0 +1,36 @@ +# syntax=docker/dockerfile:1 + +ARG RUST_VERSION=1.93.0 + +FROM rust:${RUST_VERSION}-alpine AS rust_build +ARG APP_NAME_LIST +ARG SSL_CERT_PATH +ARG SSL_KEY_PATH +WORKDIR /app + +# Install host build dependencies. +RUN apk add --no-cache clang lld musl-dev git + +# Build the application. +# Leverage a cache mount to /usr/local/cargo/registry/ +# for downloaded dependencies, a cache mount to /usr/local/cargo/git/db +# for git repository dependencies, and a cache mount to /app/target/ for +# compiled dependencies which will speed up subsequent builds. +# Leverage a bind mount to the src directory to avoid having to copy the +# source code into the container. Once built, copy the executable to an +# output directory before the cache mounted /app/target is unmounted. +RUN --mount=type=bind,source=src/quic/quic_client,target=quic_client \ + --mount=type=bind,source=src/quic/quic_conn,target=quic_conn \ + --mount=type=bind,source=src/quic/quic_server,target=quic_server \ + --mount=type=bind,source=src/quic/Cargo.toml,target=Cargo.toml \ + --mount=type=bind,source=src/quic/Cargo.lock,target=Cargo.lock \ + --mount=type=bind,source=${SSL_CERT_PATH},target=ssl_cert.pem \ + --mount=type=bind,source=${SSL_KEY_PATH},target=ssl_key.pem \ + --mount=type=cache,target=/app/target/ \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/usr/local/cargo/registry/ \ + cargo build --locked --release && \ + IFS=',' && \ + for app_name in $APP_NAME_LIST; do \ + echo $app_name && echo $(ls ./target/release) && cp ./target/release/$app_name /bin/$app_name; \ + done diff --git a/docker/README.Docker.md b/docker/README.Docker.md new file mode 100644 index 0000000..78a6e61 --- /dev/null +++ b/docker/README.Docker.md @@ -0,0 +1,189 @@ +# Docker Setup for TURBO + +This Docker configuration containerizes the TURBO research prototype for local development and experimentation. It is **not intended for production use**. + +For setup and running instructions, see the [Quick Start (Docker)](../README.md#quick-start-docker--recommended) section in the main README. This document covers additional details: services overview, configuration reference, development workflow, architecture notes, and troubleshooting. + +## Services Overview + +The Compose file defines seven services organized into two profiles (`client` and `server`): + +| Service | Profile | Description | +|---|---|---| +| `rust_base` | client, server | Builds QUIC client/server Rust binaries (base image, not run directly) | +| `python_base` | client, server | Builds Python environment with dependencies (base image, not run directly) | +| `quic_client` | client | QUIC transport client (Rust) | +| `client_python_main` | client | Client orchestrator — camera streams, bandwidth allocator, LP solver | +| `client_python_monitor` | client | Web dashboard for real-time monitoring (Flask) | +| `quic_server` | server | QUIC transport server (Rust) | +| `server_python_main` | server | Model servers — runs EfficientDet inference on GPU | + +Services start in dependency order via health checks: `client_python_main` must be healthy before `client_python_monitor` starts, and `client_python_monitor` must be healthy before `quic_client` starts. + +## Additional Configuration + +The [Quick Start](../README.md#quick-start-docker--recommended) covers the required `.env` variables (`HOST_UID`, `HOST_GID`, `EXPERIMENT_OUTPUT_DIR`, `EFFDET_MODELS_DIR`, `MODEL_FULL_EVAL_DIR`). The following additional variables are also available: + +**SSL (usually no changes needed):** + +| Variable | Description | Default | +|---|---|---| +| `SSL_KEY_PATH` | Path to QUIC SSL key (relative to repo root) | `./src/quic/ssl_key.pem` | +| `SSL_CERT_PATH` | Path to QUIC SSL cert (relative to repo root) | `./src/quic/ssl_cert.pem` | + +**Networking (usually no changes needed for same-host testing):** + +| Variable | Description | Default | +|---|---|---| +| `QUIC_CLIENT_ADDR` | Address the QUIC client connects to | `10.64.89.1:12345` (Docker bridge gateway) | +| `QUIC_SERVER_ADDR` | Address the QUIC server binds to | `0.0.0.0:12345` | +| `QUIC_SERVER_PORT` | UDP port exposed for QUIC | `12345` | +| `DASHBOARD_PORT` | Host port for the web dashboard | `5000` | + +When running client and server on the **same host**, the default `QUIC_CLIENT_ADDR` of `10.64.89.1:12345` routes through the Docker bridge gateway to reach the server container. When running on **separate hosts**, set `QUIC_CLIENT_ADDR` to the server machine's routable IP and port. + +### Docker-specific config files + +The `docker/config/` directory contains YAML config files that mirror the main `config/` files but with container-internal paths (e.g. `/app/experiment2-out` instead of `~/experiment2-out`). These are bind-mounted into each container at runtime. + +You generally don't need to edit these unless you're changing service behavior (e.g. number of cameras, model variants, SLO timeouts). If you do, edit the files in `docker/config/` — not the ones in the repo root `config/` directory. + +### Mock Modes + +TURBO supports mock camera and mock inference modes for testing without physical cameras or GPUs. See the [Mock Modes](../README.md#mock-modes) section in the main README for full details. + +In the Docker configs (`docker/config/`), the mock files are bundled into the container at `/app/` — so the paths use `/app/mock_webcam_image.jpg` and `/app/example_effdet_d4_output.npy` instead of host-absolute paths. + +### Additional running commands + +Beyond the commands in the [Quick Start](../README.md#quick-start-docker--recommended), these are also useful: + +**Build all images without starting** (useful after Dockerfile changes): +```bash +docker compose --profile client --profile server build +``` + +**Shut down without removing volumes** (keeps ZMQ sockets and health signals): +```bash +docker compose --profile client --profile server down +``` + +**Accessing the web dashboard:** + +Once the client profile is running and all health checks pass, open the monitoring dashboard at `http://localhost:5000` (or whatever port you set for `DASHBOARD_PORT` in `.env`). + +## Development Workflow + +The Compose file supports [Docker Compose Watch](https://docs.docker.com/compose/how-tos/file-watch/) for hot-reload during development: + +```bash +docker compose --profile client --profile server watch +``` + +- **Python source changes** (`src/python/`): synced into running containers without rebuild. +- **Rust source changes** (`src/quic/`): triggers a full rebuild of the Rust base image. +- **Dependency changes** (`uv.lock`, `pyproject.toml`, `Cargo.toml`): triggers a rebuild. +- **Docker config changes** (`docker/`): triggers a rebuild. + +## Architecture Notes + +- **IPC mode: host** — All services use `ipc: host` so that ZeroMQ IPC sockets and POSIX shared memory segments are accessible across containers. This is required for the system's inter-process communication to work. +- **GPU access** — The Python services (`client_python_main`, `server_python_main`) request all available NVIDIA GPUs via `deploy.resources`. The server config assigns specific services to specific GPU devices (e.g. `cuda:0`, `cuda:1`). +- **tmpfs volumes** — ZeroMQ socket directories and health signal files use tmpfs-backed volumes for fast, ephemeral storage. +- **Signal handling** — All services use `init: true` (tini) as PID 1 for proper signal forwarding and graceful shutdown. A 30-second grace period (`stop_grace_period`) is configured for each service. When you press Ctrl+C on `docker compose up`, Docker Compose sends **SIGTERM** (not SIGINT) to each container. Without `init: true`, the application would be PID 1, and the Linux kernel silently drops signals with default handlers for PID 1 — causing the process to ignore SIGTERM and get SIGKILL'd after the grace period. The Python orchestrators (`client_main.py`, `server_main.py`) explicitly handle both SIGTERM and SIGINT to trigger graceful shutdown (ZMQ kill-switch broadcast, shared memory unlink, Parquet flush). +- **`exec` and `python` in Dockerfiles** — The Python Dockerfile uses `exec python` directly instead of `uv run`. `uv run` spawns Python as a child process and may not forward signals, which would prevent graceful shutdown. The venv is already on `PATH` (set in `Dockerfile_turbo_python_base`), so calling `python` directly works. The `exec` replaces the shell with the actual process, avoiding a redundant `/bin/sh` parent (optional with `init: true`, but good practice). +- **Custom network** — A bridge network (`quic_net`, subnet `10.64.89.0/24`, gateway `10.64.89.1`) is used for QUIC communication. On same-host deployments, the QUIC client reaches the server through the bridge gateway (which routes to the host, where the server's UDP port is published). On separate-host deployments, `QUIC_CLIENT_ADDR` is set to the server machine's routable IP instead. +- **QUIC uses UDP** — The server's port is published with the `/udp` protocol. Firewalls on the server host must allow inbound UDP on this port. +- **Rust QUIC client requires IP addresses** — The Rust QUIC client parses addresses with `SocketAddr` and cannot resolve DNS hostnames. `QUIC_CLIENT_ADDR` must always be an `ip:port` pair (e.g. `10.64.89.1:12345`), not a hostname. +- **Health signal synchronization** — `client_python_main` writes its health signal (`/health/client_main_ready`) only after all Client subprocesses have bound their `quic_rcv_zmq_socket`. This ensures the Rust QUIC client (which depends on this health signal via `client_python_monitor`) does not start until the Python ZMQ sockets are ready to accept connections. A `multiprocessing.Manager().Queue()` is used for this cross-process synchronization. + +## Path Relativity Rules + +Docker Compose uses different base directories for different path types, which can be confusing: + +| Path type | Relative to | Example | +|---|---|---| +| Volume `source` | **Compose file location** (`docker/`) | `./config/client_config_docker.yaml` → `docker/config/client_config_docker.yaml` | +| Build `context` | **Compose file location** (`docker/`) | `..` → repo root | +| Build `dockerfile` | **Build context** | `./docker/Dockerfile_turbo_python_binary` → (repo root)/docker/Dockerfile_turbo_python_binary | +| Build `args` (paths like `EXECUTABLE_DIR`) | N/A (baked into image) | `./src/python` → resolved inside the container | +| Watch `path` | **Build context** | `./uv.lock` → (repo root)/uv.lock | + +The `.env` file paths for volume mounts (e.g. `PYTHON_DOCKER_CLIENT_CONFIG_PATH`) must be relative to the compose file location (`docker/`), **not** the repo root. + +## SSL Certificates + +The QUIC binaries embed the SSL certificate and key at **compile time** via Rust's `include_str!()` macro. The `.pem` files are baked into the executable during `cargo build` and are not needed at runtime. This means the QUIC container images are self-contained — no SSL volume mounts are required. + +## Troubleshooting + +**"permission denied" on bind-mounted files:** +Make sure `HOST_UID` and `HOST_GID` in `.env` match your host user (`id -u` and `id -g`). + +**GPU not available inside containers:** +Verify the NVIDIA Container Toolkit is installed and the Docker daemon is configured to use the `nvidia` runtime. Test with: +```bash +docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi +``` + +**Services failing health checks:** +Check logs for a specific service to diagnose startup issues: +```bash +docker compose --profile client logs client_python_main +``` + +**QUIC connection failures between client and server on separate hosts:** +Make sure `QUIC_CLIENT_ADDR` in `.env` is set to the server host's routable IP (not the Docker gateway), and that the `QUIC_SERVER_PORT` UDP port is open on the server host's firewall. + +**Bind mount shows "IsADirectoryError" or creates an unexpected directory:** +When Docker bind-mounts a file but the source path doesn't exist on the host, Docker silently creates a **directory** at the target path instead of failing. This causes confusing errors like `IsADirectoryError: [Errno 21] Is a directory: '/app/python_config.yaml'`. Double-check that the source path in `.env` is correct and that the file exists. Remember that volume source paths are relative to the compose file location — see [Path Relativity Rules](#path-relativity-rules). After fixing the path, you must rebuild with `--build` since the stale directory may be cached in the image layer: +```bash +docker compose --profile server up --build --force-recreate +``` + +**`--force-recreate` vs `--build`:** +`--force-recreate` recreates containers but does **not** rebuild images. If a problem was baked into an image during a previous build (e.g. a directory created by a bad bind mount), you need `--build` to rebuild the image. Use both when in doubt: +```bash +docker compose --profile server up --build --force-recreate +``` + +**Do not use `docker compose restart`:** +`docker compose restart` stops and restarts containers but does **not** re-evaluate `depends_on` health checks. All containers restart simultaneously, bypassing the startup ordering. Always use `docker compose down && docker compose up` to ensure proper sequencing. + +**iptables errors ("Chain 'DOCKER-ISOLATION-STAGE-2' does not exist"):** +This is a known issue with **Docker 28.x** on newer Linux kernels where iptables uses the `nf_tables` backend. Docker 28 changed its network isolation chain setup in a way that is incompatible with `nf_tables`. **Docker 27.5 does not have this issue.** Fixes to try in order: +1. **Downgrade to Docker 27.5** — this is the most reliable fix. +2. Restart Docker: `sudo systemctl restart docker` +3. Switch to the legacy iptables backend: + ```bash + sudo update-alternatives --set iptables /usr/sbin/iptables-legacy + sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy + sudo systemctl restart docker + ``` + +**Stale containers after changing network config:** +If you add or modify Docker networks in `compose.yaml`, existing containers won't pick up the changes. You'll see errors like `container is not connected to the network`. Fix by recreating: +```bash +docker compose --profile client --profile server down +docker compose --profile client --profile server up +``` + +**Stale POSIX shared memory after ungraceful shutdown:** +Because all services use `ipc: host`, POSIX shared memory segments live in the host's `/dev/shm` and survive container restarts. If a container is force-killed (SIGKILL, OOM, Docker timeout, power loss) before cleanup runs, stale segments remain and cause `FileExistsError: [Errno 17] File exists` on the next startup. To clean them up: +```bash +# Check for stale segments +ls /dev/shm/*-shm + +# Remove them (server-side example) +rm /dev/shm/server-service*-shm + +# Remove them (client-side example) +rm /dev/shm/client-service*-shm +``` +Under normal graceful shutdown (Ctrl+C), the Python processes unlink their shared memory segments automatically. + +**Stale ZeroMQ sockets from a previous run:** +ZMQ socket directories use tmpfs volumes that start empty on every `docker compose up`, so stale sockets are not normally an issue. If you see ZMQ-related errors, shut down with `-v` to remove all tmpfs volumes: +```bash +docker compose --profile client --profile server down -v +``` diff --git a/docker/compose.yaml b/docker/compose.yaml new file mode 100644 index 0000000..bda88e2 --- /dev/null +++ b/docker/compose.yaml @@ -0,0 +1,288 @@ +services: + rust_base: + profiles: ['client', 'server'] + build: + context: .. + dockerfile: ./docker/Dockerfile_turbo_rust_base + args: + APP_NAME_LIST: "client,server" + SSL_CERT_PATH: ${SSL_CERT_PATH} + SSL_KEY_PATH: ${SSL_KEY_PATH} + develop: + watch: + - action: rebuild + path: ./src/quic/quic_conn + - action: rebuild + path: ./src/quic/quic_server + - action: rebuild + path: ./src/quic/quic_client + - action: rebuild + path: ./src/quic/Cargo.toml + - action: rebuild + path: ./src/quic/Cargo.lock + - action: rebuild + path: ${SSL_KEY_PATH} + - action: rebuild + path: ${SSL_CERT_PATH} + - action: rebuild + path: ./docker + + python_base: + profiles: ['client', 'server'] + build: + context: .. + dockerfile: ./docker/Dockerfile_turbo_python_base + args: + GID: ${HOST_GID} + develop: + watch: + - action: rebuild + path: ./uv.lock + - action: rebuild + path: ./pyproject.toml + - action: sync + path: ./src/python + target: /app/src/python + ignore: + - .venv/ + - action: rebuild + path: ./docker + + quic_client: + profiles: ['client'] + # Use tini as PID 1 so Docker's SIGTERM is forwarded to the actual process. + # Without init, our process would be PID 1, and the Linux kernel silently + # drops signals with default handlers for PID 1 — causing the process to + # ignore SIGTERM and get SIGKILL'd after the grace period. + init: true + ipc: host + stop_grace_period: 30s + depends_on: + client_python_monitor: + condition: service_healthy + build: + context: .. + target: final + dockerfile: ./docker/Dockerfile_turbo_quic_binary + args: + APP_NAME: "client" + QUIC_BIND_ADDR: ${QUIC_CLIENT_ADDR} + QUIC_DOCKER_PORT: ${QUIC_CLIENT_PORT} + GID: ${HOST_GID} + UID: ${HOST_UID} + additional_contexts: + base_image: "service:rust_base" + environment: + RUST_LOG: ${RUST_LOG} + RUST_BACKTRACE: ${RUST_BACKTRACE} + volumes: + - type: bind + source: ${QUIC_DOCKER_CLIENT_CONFIG_PATH} + target: /app/quic_config.yaml + read_only: true + - type: bind + source: ${EXPERIMENT_OUTPUT_DIR} + target: /app/experiment2-out + - zmq_client:/app/zmq-out + - health_signals:/health + networks: + - quic_net + + quic_server: + profiles: ['server'] + init: true + ipc: host + stop_grace_period: 30s + build: + context: .. + target: final + dockerfile: ./docker/Dockerfile_turbo_quic_binary + args: + APP_NAME: "server" + QUIC_BIND_ADDR: ${QUIC_SERVER_ADDR} + QUIC_DOCKER_PORT: ${QUIC_SERVER_PORT} + GID: ${HOST_GID} + UID: ${HOST_UID} + additional_contexts: + base_image: "service:rust_base" + environment: + RUST_LOG: ${RUST_LOG} + RUST_BACKTRACE: ${RUST_BACKTRACE} + volumes: + - type: bind + source: ${QUIC_DOCKER_SERVER_CONFIG_PATH} + target: /app/quic_config.yaml + read_only: true + - type: bind + source: ${EXPERIMENT_OUTPUT_DIR} + target: /app/experiment2-out + - zmq_server:/app/zmq-out + ports: + - "${QUIC_SERVER_PORT}:${QUIC_SERVER_PORT}/udp" + + client_python_main: + profiles: ['client'] + init: true + ipc: host + stop_grace_period: 30s + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + build: + context: .. + target: final + dockerfile: ./docker/Dockerfile_turbo_python_binary + additional_contexts: + base_image: "service:python_base" + args: + EXECUTABLE_DIR: ./src/python + EXECUTABLE_NAME: client_main.py + GID: ${HOST_GID} + UID: ${HOST_UID} + volumes: + - health_signals:/health + - type: bind + source: ${PYTHON_DOCKER_CLIENT_CONFIG_PATH} + target: /app/python_config.yaml + read_only: true + - type: bind + source: ${DOCKER_LOG_CONFIG_PATH} + target: /app/logging_config.yaml + read_only: true + - type: bind + source: ${EXPERIMENT_OUTPUT_DIR} + target: /app/experiment2-out + - zmq_client:/app/zmq-out + - type: bind + source: ${MODEL_FULL_EVAL_DIR} + target: /app/full-eval + read_only: true + - type: bind + source: ${EXPERIMENT_MODEL_INFO_PATH} + target: /app/experiment_model_info.csv + read_only: true + - type: bind + source: ${MOCK_EFFDET_INPUT_PATH} + target: /app/mock_webcam_image.jpg + read_only: true + command: > + bash -c 'cd src/python && exec python client_main.py -c /app/python_config.yaml -s ${QUIC_CLIENT_ADDR}' + healthcheck: + test: ["CMD", "test", "-f", "/health/client_main_ready"] + interval: 2s + timeout: 3s + start_period: 30s + retries: 30 + server_python_main: + profiles: ['server'] + init: true + ipc: host + stop_grace_period: 30s + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + build: + context: .. + target: final + dockerfile: ./docker/Dockerfile_turbo_python_binary + additional_contexts: + base_image: "service:python_base" + args: + EXECUTABLE_DIR: ./src/python + EXECUTABLE_NAME: server_main.py + GID: ${HOST_GID} + UID: ${HOST_UID} + volumes: + - type: bind + source: ${PYTHON_DOCKER_SERVER_CONFIG_PATH} + target: /app/python_config.yaml + read_only: true + - type: bind + source: ${DOCKER_LOG_CONFIG_PATH} + target: /app/logging_config.yaml + read_only: true + - type: bind + source: ${EXPERIMENT_OUTPUT_DIR} + target: /app/experiment2-out + - zmq_server:/app/zmq-out + - type: bind + source: ${EFFDET_MODELS_DIR} + target: /app/av-models + read_only: true + - type: bind + source: ${EXPERIMENT_MODEL_INFO_PATH} + target: /app/experiment_model_info.csv + read_only: true + - type: bind + source: ${MOCK_EFFDET_OUTPUT_PATH} + target: /app/example_effdet_d4_output.npy + read_only: true + client_python_monitor: + profiles: ['client'] + init: true + ipc: host + stop_grace_period: 30s + depends_on: + client_python_main: + condition: service_healthy + build: + context: .. + target: final + dockerfile: ./docker/Dockerfile_turbo_python_binary + additional_contexts: + base_image: "service:python_base" + args: + EXECUTABLE_DIR: ./src/python/web_frontend + EXECUTABLE_NAME: start_web_dashboard.py + GID: ${HOST_GID} + UID: ${HOST_UID} + volumes: + - health_signals:/health + - type: bind + source: ${PYTHON_DOCKER_CLIENT_CONFIG_PATH} + target: /app/python_config.yaml + read_only: true + - type: bind + source: ${EXPERIMENT_OUTPUT_DIR} + target: /app/experiment2-out + - zmq_client:/app/zmq-out + ports: + - "${DASHBOARD_PORT}:5000" + healthcheck: + test: ["CMD", "test", "-f", "/health/monitor_ready"] + interval: 2s + timeout: 3s + start_period: 30s + retries: 30 +networks: + quic_net: + ipam: + config: + - subnet: 10.64.89.0/24 + gateway: 10.64.89.1 +volumes: + health_signals: + driver: local + driver_opts: + type: tmpfs + device: tmpfs + zmq_client: + driver: local + driver_opts: + type: tmpfs + device: tmpfs + zmq_server: + driver: local + driver_opts: + type: tmpfs + device: tmpfs + + diff --git a/docker/config/client_config_docker.yaml b/docker/config/client_config_docker.yaml new file mode 100644 index 0000000..c987824 --- /dev/null +++ b/docker/config/client_config_docker.yaml @@ -0,0 +1,225 @@ +logging_config_filepath: /app/logging_config.yaml + +experiment_output_dir: /app/experiment2-out +client_subdir: client +zmq_dir: /app/zmq-out + +web_dashboard_config: + refresh_rate_seconds: 6 + plotting_loop_sleep_seconds: 2 + +model_image_size_map: &model_imagesizes + tf_efficientdet_d1: [640, 640] + tf_efficientdet_d2: [768, 768] + tf_efficientdet_d4: [1024, 1024] + tf_efficientdet_d6: [1280, 1280] + tf_efficientdet_d7x: [1536, 1536] + +MAX_LOG_ENTRIES: &max-log-entries 100 +SLO_TIMEOUT: &slo-timeout 200 # TODO: change this +QUIC_SHM_SIZE: &quic-shm-size 50000000 + +main_client_config_list: + - service_id: 1 + max_entries: *max-log-entries + thread_concurrency: 10 + camera_bidirectional_zmq_sockname: service1-camera-socket + camera_stream_shmem_filename: service1-camera-shmem + bandwidth_allocation_incoming_zmq_sockname: main-client-1-bw-subscriber + quic_rcv_zmq_sockname: car-server-outgoing-1 + quic_snd_zmq_sockname: car-server-incoming-1 + outgoing_zmq_diagnostic_sockname: car-client-diagnostics + camera_np_size: [1080, 1920, 3] + model_name_imagesize_map: *model_imagesizes + zmq_kill_switch_sockname: client-kill-1-switch + quic_snd_shm_filename: client-service1-incoming-shm + quic_rcv_shm_filename: client-service1-outgoing-shm + quic_shm_size: *quic-shm-size + SLO_TIMEOUT_MS: *slo-timeout + - service_id: 2 + max_entries: *max-log-entries + thread_concurrency: 10 + camera_bidirectional_zmq_sockname: service2-camera-socket + camera_stream_shmem_filename: service2-camera-shmem + bandwidth_allocation_incoming_zmq_sockname: main-client-2-bw-subscriber + quic_rcv_zmq_sockname: car-server-outgoing-2 + quic_snd_zmq_sockname: car-server-incoming-2 + outgoing_zmq_diagnostic_sockname: car-client-diagnostics + camera_np_size: [1080, 1920, 3] + model_name_imagesize_map: *model_imagesizes + zmq_kill_switch_sockname: client-kill-2-switch + quic_snd_shm_filename: client-service2-incoming-shm + quic_rcv_shm_filename: client-service2-outgoing-shm + quic_shm_size: *quic-shm-size + SLO_TIMEOUT_MS: *slo-timeout + - service_id: 3 + max_entries: *max-log-entries + thread_concurrency: 10 + camera_bidirectional_zmq_sockname: service3-camera-socket + camera_stream_shmem_filename: service3-camera-shmem + bandwidth_allocation_incoming_zmq_sockname: main-client-3-bw-subscriber + quic_rcv_zmq_sockname: car-server-outgoing-3 + quic_snd_zmq_sockname: car-server-incoming-3 + outgoing_zmq_diagnostic_sockname: car-client-diagnostics + camera_np_size: [1080, 1920, 3] + model_name_imagesize_map: *model_imagesizes + zmq_kill_switch_sockname: client-kill-3-switch + quic_snd_shm_filename: client-service3-incoming-shm + quic_rcv_shm_filename: client-service3-outgoing-shm + quic_shm_size: *quic-shm-size + SLO_TIMEOUT_MS: *slo-timeout + +bandwidth_allocator_config: + service_id_list: [1, 2, 3] + t_SLO: 150 #TODO: change this + parquet_eval_dir: /app/full-eval + model_info_csv_path: /app/experiment_model_info.csv + outgoing_zmq_diagnostic_sockname: car-client-diagnostics + outgoing_zmq_client_socknames: + - main-client-1-bw-subscriber + - main-client-2-bw-subscriber + - main-client-3-bw-subscriber + bidirectional_zmq_quic_sockname: car-server-bw-service + zmq_kill_switch_sockname: bandwidth-allocator-kill-switch + bidirectional_zmq_ping_handler_sockname: ping-handler + +ping_handler_config: + max_entries: 100 + thread_concurrency: 5 + bidirectional_zmq_sockname: ping-handler + zmq_kill_switch_sockname: ping-handler-kill-switch + +camera_stream_config_list: + - camera_id: 1 # FRONT + usb_id: 0 + max_entries: *max-log-entries + thread_concurrency: 10 + bidirectional_zmq_sockname: service1-camera-socket + camera_stream_shmem_filename: service1-camera-shmem + shmem_buf_size: *quic-shm-size + camera_np_size: [1080, 1920, 3] + zmq_kill_switch_sockname: camera-kill-1-switch + # Mock camera: uncomment ONE of the following lines + # mock_camera_image_path: /app/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam + mock_camera_image_path: null # real mode — captures from USB camera + - camera_id: 2 # FRONT LEFT + usb_id: 4 # TODO: CONFIGURE THIS + max_entries: *max-log-entries + thread_concurrency: 10 + bidirectional_zmq_sockname: service2-camera-socket + camera_stream_shmem_filename: service2-camera-shmem + shmem_buf_size: *quic-shm-size + camera_np_size: [1080, 1920, 3] + zmq_kill_switch_sockname: camera-kill-2-switch + # Mock camera: uncomment ONE of the following lines + # mock_camera_image_path: /app/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam + mock_camera_image_path: null # real mode — captures from USB camera + - camera_id: 3 # FRONT RIGHT + usb_id: 8 # TODO: CONFIGURE THIS + max_entries: *max-log-entries + thread_concurrency: 10 + bidirectional_zmq_sockname: service3-camera-socket + camera_stream_shmem_filename: service3-camera-shmem + shmem_buf_size: *quic-shm-size + camera_np_size: [1080, 1920, 3] + zmq_kill_switch_sockname: camera-kill-3-switch + # Mock camera: uncomment ONE of the following lines + # mock_camera_image_path: /app/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam + mock_camera_image_path: null # real mode — captures from USB camera + +main_plotter_config: +# service 4 is for junk stream + plotting_loop_sleep_seconds: 2 + zmq_incoming_diagnostic_name: car-client-diagnostics + bandwidth_allocation_plot_config: + service_id_list: [1, 2, 3] + window_size_x: 40 + bw_min_y: -10 + bw_max_y: null + utility_min_y: -.1 + utility_max_y: 1.1 + bw_x_major_loc: 15 + bw_x_minor_loc: 5 + bw_y_major_loc: 100 + bw_y_minor_loc: 20 + utility_x_major_loc: 15 + utility_x_minor_loc: 5 + utility_y_major_loc: .2 + utility_y_minor_loc: .1 + + service_status_plot_config: + - service_id: 1 + window_size_x: 40 + cnt_min_y: -3 + cnt_max_y: null + rate_min_y: -.5 + rate_max_y: 1.05 + cnt_x_major_loc: 15 + cnt_x_minor_loc: 5 + cnt_y_major_loc: 30 + cnt_y_minor_loc: 10 + rate_x_major_loc: 15 + rate_x_minor_loc: 5 + rate_y_major_loc: .2 + rate_y_minor_loc: .05 + - service_id: 2 + window_size_x: 40 + cnt_min_y: -3 + cnt_max_y: null + rate_min_y: -.5 + rate_max_y: 1.05 + cnt_x_major_loc: 15 + cnt_x_minor_loc: 5 + cnt_y_major_loc: 30 + cnt_y_minor_loc: 10 + rate_x_major_loc: 15 + rate_x_minor_loc: 5 + rate_y_major_loc: .2 + rate_y_minor_loc: .05 + - service_id: 3 + window_size_x: 40 + cnt_min_y: -3 + cnt_max_y: null + rate_min_y: -.5 + rate_max_y: 1.05 + cnt_x_major_loc: 15 + cnt_x_minor_loc: 5 + cnt_y_major_loc: 30 + cnt_y_minor_loc: 10 + rate_x_major_loc: 15 + rate_x_minor_loc: 5 + rate_y_major_loc: .2 + rate_y_minor_loc: .05 + service_utilization_plot_config: + - service_id: 1 + window_size_x: 40 + min_y: -10 + max_y: null + x_major_loc: 15 + x_minor_loc: 5 + y_major_loc: 100 + y_minor_loc: 20 + - service_id: 2 + window_size_x: 40 + min_y: -10 + max_y: null + x_major_loc: 15 + x_minor_loc: 5 + y_major_loc: 100 + y_minor_loc: 20 + - service_id: 3 + window_size_x: 40 + min_y: -10 + max_y: null + x_major_loc: 15 + x_minor_loc: 5 + y_major_loc: 100 + y_minor_loc: 20 + - service_id: 4 + window_size_x: 40 + min_y: -10 + max_y: null + x_major_loc: 15 + x_minor_loc: 5 + y_major_loc: 100 + y_minor_loc: 20 diff --git a/docker/config/logging_config_docker.yaml b/docker/config/logging_config_docker.yaml new file mode 100644 index 0000000..968f08c --- /dev/null +++ b/docker/config/logging_config_docker.yaml @@ -0,0 +1,66 @@ +version: 1 +formatters: + verbose_singlethread: + format: '%(levelname)s %(asctime)s %(name)s at %(filename)s:%(funcName)s:%(lineno)d: %(message)s' + verbose_multithread: + format: '%(levelname)s %(asctime)s %(name)s, process %(process)d:%(thread)d at %(filename)s:%(funcName)s:%(lineno)d: %(message)s' + simple_singlethread: + format: '%(levelname)s %(asctime)s %(name)s: %(message)s' + simple_multithread: + format: '%(levelname)s %(asctime)s %(name)s, process %(process)d:%(thread)d: %(message)s' + +handlers: + console: + class: logging.StreamHandler + level: INFO + formatter: verbose_multithread + stream: ext://sys.stdout + +loggers: + bandwidth_allocator: + level: INFO + handlers: [console] #todo: add any of the above file handlers as needed for each of the below loggers + propagate: no + bandwidth_visualizer: + level: INFO + handlers: [console] + propagate: no + client_main: + level: INFO + handlers: [console] + propagate: no + client: + level: INFO + handlers: [console] + propagate: no + server_main: + level: INFO + handlers: [console] + propagate: no + plotter_main: + level: INFO + handlers: [console] + propagate: no + model_server: + level: INFO + handlers: [console] + propagate: no + camera_data_stream: + level: INFO + handlers: [console] + propagate: no + main_plotter: + level: INFO + handlers: [console] + propagate: no + effdet_inference: + level: INFO + handlers: [console] + propagate: no + ping_handler: + level: INFO + handlers: [console] + propagate: no +root: + level: NOTSET + handlers: [console] \ No newline at end of file diff --git a/docker/config/quic_config_client_docker.yaml b/docker/config/quic_config_client_docker.yaml new file mode 100644 index 0000000..66eb1d3 --- /dev/null +++ b/docker/config/quic_config_client_docker.yaml @@ -0,0 +1,35 @@ +experiment_output_dir: /app/experiment2-out +zmq_dir: /app/zmq-out +quic_client_log_subdir: quic-client-out +quic_server_log_subdir: quic-server-out + +slo_timeout_ms: 100 +junk_tx_loop_interval_ms: 100 +logging_interval_ms: 500 +init_allocation: 100000000.0 +bw_update_interval_ms: 500 +bw_polling_interval_ms: 200 + +max_junk_payload_Mb: 5 # .5 Mb/s, this is the bandwidth not the payload size per send loop. +junk_restart_interval_ms: 500 + +services: [1, 2, 3, 4] + +client_enable_bw_stat_log: True +client_enable_allocation_stat_log: True +client_enable_network_stat_log: True +client_enable_incoming_image_context_log: True +client_enable_outgoing_image_context_log: True + +server_enable_bw_stat_log: True +server_enable_allocation_stat_log: True +server_enable_network_stat_log: True +server_enable_incoming_image_context_log: True +server_enable_outgoing_image_context_log: True + +image_context_log_capacity: 100 +bw_stat_log_capacity: 100 +allocation_stat_log_capacity: 100 +network_stat_log_capacity: 100 + +enable_junk_service: True #if True, the last service on the list is junk diff --git a/docker/config/quic_config_gcloud_docker.yaml b/docker/config/quic_config_gcloud_docker.yaml new file mode 100644 index 0000000..440f301 --- /dev/null +++ b/docker/config/quic_config_gcloud_docker.yaml @@ -0,0 +1,34 @@ +experiment_output_dir: /app/experiment2-out +zmq_dir: /app/zmq-out +quic_client_log_subdir: quic-client-out +quic_server_log_subdir: quic-server-out + +slo_timeout_ms: 100 +junk_tx_loop_interval_ms: 7 +logging_interval_ms: 500 +init_allocation: 100000000.0 +bw_update_interval_ms: 500 +bw_polling_interval_ms: 200 +max_junk_payload_Mb: .5 # .5 Mb/s, this is the bandwidth not the payload size per send loop. +junk_restart_interval_ms: 500 + +services: [1, 2, 3, 4] + +client_enable_bw_stat_log: True +client_enable_allocation_stat_log: True +client_enable_network_stat_log: True +client_enable_incoming_image_context_log: True +client_enable_outgoing_image_context_log: True + +server_enable_bw_stat_log: True +server_enable_allocation_stat_log: True +server_enable_network_stat_log: True +server_enable_incoming_image_context_log: True +server_enable_outgoing_image_context_log: True + +image_context_log_capacity: 100 +bw_stat_log_capacity: 100 +allocation_stat_log_capacity: 100 +network_stat_log_capacity: 100 + +enable_junk_service: True #if True, the last service on the list is junk diff --git a/docker/config/server_config_gcloud_docker.yaml b/docker/config/server_config_gcloud_docker.yaml new file mode 100644 index 0000000..6158818 --- /dev/null +++ b/docker/config/server_config_gcloud_docker.yaml @@ -0,0 +1,83 @@ +logging_config_filepath: /app/logging_config.yaml + +experiment_output_dir: /app/experiment2-out +server_subdir: server +zmq_dir: /app/zmq-out + +MAX_LOG_ENTRIES: &max-log-entries 100 +SHM_FILESIZE: &shm-filesize 50000000 + +server_model_list: &model-list-ref + - checkpoint_path: /app/av-models/tf_efficientdet_d2-waymo-open-dataset/version_2/checkpoints/epoch=9-step=419700.ckpt + num_classes: 5 + image_size: [768, 768] + base_model: "tf_efficientdet_d2" + - checkpoint_path: /app/av-models/tf_efficientdet_d4-waymo-open-dataset/version_0/checkpoints/epoch=9-step=839400.ckpt + num_classes: 5 + image_size: [1024, 1024] + base_model: "tf_efficientdet_d4" + - checkpoint_path: /app/av-models/tf_efficientdet_d6-waymo-open-dataset/version_2/checkpoints/epoch=9-step=3357600.ckpt + num_classes: 5 + image_size: [1280, 1280] + base_model: "tf_efficientdet_d6" + - checkpoint_path: /app/av-models/tf_efficientdet_d7x-waymo-open-dataset/version_1/checkpoints/epoch=8-step=1477071.ckpt + num_classes: 5 + image_size: [1536, 1536] + base_model: "tf_efficientdet_d7x" + + +server_config_list: + - service_id: 1 + max_entries: *max-log-entries + model_metadata_list: *model-list-ref + device: "cuda:0" + incoming_zmq_sockname: remote-server-outgoing-1 + incoming_shm_filename: server-service1-outgoing-shm + outgoing_zmq_sockname: remote-server-incoming-1 + outgoing_shm_filename: server-service1-incoming-shm + thread_concurrency: 10 + shm_filesize: *shm-filesize + zmq_kill_switch_sockname: remote-server-kill-switch-1 + # Mock inference: uncomment ONE of the following blocks + # -- mock mode — skips GPU model loading, returns pre-recorded detections + # mock_inference_output_path: /app/example_effdet_d4_output.npy + # mock_model_latency_csv_path: /app/experiment_model_info.csv + # -- real mode — loads models and runs GPU inference + mock_inference_output_path: null + mock_model_latency_csv_path: null + - service_id: 2 + max_entries: *max-log-entries + model_metadata_list: *model-list-ref + device: "cuda:1" + incoming_zmq_sockname: remote-server-outgoing-2 + incoming_shm_filename: server-service2-outgoing-shm + outgoing_zmq_sockname: remote-server-incoming-2 + outgoing_shm_filename: server-service2-incoming-shm + thread_concurrency: 10 + shm_filesize: *shm-filesize + zmq_kill_switch_sockname: remote-server-kill-switch-2 + # Mock inference: uncomment ONE of the following blocks + # -- mock mode — skips GPU model loading, returns pre-recorded detections + # mock_inference_output_path: /app/example_effdet_d4_output.npy + # mock_model_latency_csv_path: /app/experiment_model_info.csv + # -- real mode — loads models and runs GPU inference + mock_inference_output_path: null + mock_model_latency_csv_path: null + - service_id: 3 + max_entries: *max-log-entries + model_metadata_list: *model-list-ref + device: "cuda:2" + incoming_zmq_sockname: remote-server-outgoing-3 + incoming_shm_filename: server-service3-outgoing-shm + outgoing_zmq_sockname: remote-server-incoming-3 + outgoing_shm_filename: server-service3-incoming-shm + thread_concurrency: 10 + shm_filesize: *shm-filesize + zmq_kill_switch_sockname: remote-server-kill-switch-3 + # Mock inference: uncomment ONE of the following blocks + # -- mock mode — skips GPU model loading, returns pre-recorded detections + # mock_inference_output_path: /app/example_effdet_d4_output.npy + # mock_model_latency_csv_path: /app/experiment_model_info.csv + # -- real mode — loads models and runs GPU inference + mock_inference_output_path: null + mock_model_latency_csv_path: null diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index a2e33c1..10fcddb 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -42,7 +42,6 @@ experiment_output_dir: /path/to/experiment-out client_subdir: client zmq_dir: /path/to/experiment-out/zmq -DST_IP: &dst-ip # Cloud server IP for ICMP pings SLO_TIMEOUT: &slo-timeout 200 # Client-side SLO timeout (ms), shared via anchor QUIC_SHM_SIZE: &quic-shm-size 50000000 # Shared memory region size (bytes, ~50 MB) MAX_LOG_ENTRIES: &max-log-entries 100 # Max records in memory before spilling to Parquet @@ -54,7 +53,6 @@ MAX_LOG_ENTRIES: &max-log-entries 100 # Max records in memory before spilling | `experiment_output_dir` | **Yes** | Base output directory. `client_main.py` creates a timestamped subdirectory here (e.g., `experiment-out/client_main_2024-01-15_10-30-00/`) for each run | | `client_subdir` | No | Subdirectory name within the timestamped run directory for client Parquet logs. Default `client` | | `zmq_dir` | **Yes** | Directory for ZMQ IPC socket files. Must match across client, server, and QUIC configs. Created automatically if it doesn't exist | -| `DST_IP` | **Yes** | Public IP of your cloud server. Used by PingHandler for RTT measurement | | `SLO_TIMEOUT` | Maybe | Service-level objective timeout in ms. Frames exceeding this are dropped. Default `200` is suitable for most setups | | `QUIC_SHM_SIZE` | No | Size of each POSIX shared memory region in bytes. `50000000` (50 MB) is sufficient for HD frames | | `MAX_LOG_ENTRIES` | No | Number of log records buffered in memory before flushing to Parquet. Higher = fewer I/O flushes, more memory | @@ -199,7 +197,6 @@ camera_stream_config_list: ```yaml ping_handler_config: - dst_ip: *dst-ip max_entries: 100 thread_concurrency: 5 bidirectional_zmq_sockname: ping-handler @@ -210,7 +207,7 @@ ping_handler_config: | Parameter | Must customize? | Description | |-----------|:-:|---| -| `dst_ip` | **Yes** | IP address to ping for RTT measurement. Should be your cloud server's public IP (uses global `DST_IP` anchor) | +| `dst_ip` | N/A | Set automatically by `client_main.py` from the `--server_address` CLI argument (port stripped) | | `max_entries` | No | Log buffer size | | `thread_concurrency` | No | Thread pool size | | `bidirectional_zmq_sockname` | No | Bare ZMQ socket name. Must match the bandwidth allocator's `bidirectional_zmq_ping_handler_sockname` | @@ -480,7 +477,6 @@ mkdir -p ~/experiment-out - [ ] `logging_config_filepath` points to `config/logging_config.yaml` (absolute path) - [ ] `experiment_output_dir` is set to your chosen output directory - [ ] `zmq_dir` is set (e.g., `~/experiment-out/zmq`) and matches all other configs -- [ ] `DST_IP` is set to your cloud server's public IP address - [ ] `parquet_eval_dir` points to the directory containing pre-computed utility curve Parquet files - [ ] `model_info_csv_path` points to the `experiment_model_info.csv` file - [ ] `camera_stream_config_list[].usb_id` matches your USB camera device IDs (run `ls /dev/video*`) diff --git a/docs/IPC.md b/docs/IPC.md index 1a6765b..0ee9c85 100644 --- a/docs/IPC.md +++ b/docs/IPC.md @@ -131,7 +131,7 @@ This section documents every IPC channel between components. All connections use | Peer | Transport | Socket Name | Pattern | Direction | Purpose | |------|-----------|-------------|---------|-----------|---------| -| BandwidthAllocator | ZMQ REP (bind) | `ping-handler` | REQ/REP | BandwidthAllocator → PingHandler | Responds to RTT queries with the latest ICMP ping measurement to `DST_IP` (the cloud server) | +| BandwidthAllocator | ZMQ REP (bind) | `ping-handler` | REQ/REP | BandwidthAllocator → PingHandler | Responds to RTT queries with the latest ICMP ping measurement to the server (IP from `--server_address` CLI arg) | | client_main | ZMQ SUB (bind) | `ping-handler-kill-switch` | PUB/SUB | client_main → PingHandler | Receive "ABORT" signal for graceful shutdown | **Config**: `ping_handler_config` in `config/client_config.yaml` diff --git a/src/python/client.py b/src/python/client.py index d57151e..885f598 100644 --- a/src/python/client.py +++ b/src/python/client.py @@ -304,7 +304,7 @@ class Client: - Detailed latency logging to Parquet files """ - def __init__(self, config: ClientConfig) -> None: + def __init__(self, config: ClientConfig, ready_queue=None) -> None: self.service_id = config.service_id self._is_cleaned_up = False self.spillable_store = ClientSpillableStore( @@ -344,6 +344,9 @@ def __init__(self, config: ClientConfig) -> None: self.quic_rcv_zmq_socket = self.context.socket(zmq.REP) self.quic_rcv_zmq_socket.bind(config.quic_rcv_zmq_sockname) + if ready_queue is not None: + ready_queue.put(self.service_id) + # Create kill switch early so we can check it during the handshake wait self.quic_kill_switch = self.context.socket(zmq.SUB) self.quic_kill_switch.setsockopt_string(zmq.SUBSCRIBE, "") diff --git a/src/python/client_main.py b/src/python/client_main.py index 3d66a42..ce32028 100644 --- a/src/python/client_main.py +++ b/src/python/client_main.py @@ -37,7 +37,7 @@ import signal import traceback import yaml -from multiprocessing import Pool, Queue, Pipe +from multiprocessing import Manager, Pool, Pipe import time import os import argparse @@ -62,6 +62,13 @@ required=True, help="path to server config file", ) + parser.add_argument( + "-s", + "--server_address", + type=str, + required=True, + help="server address (host or host:port) — IP is extracted for ping handler", + ) args = parser.parse_args() @@ -120,6 +127,7 @@ def resolve_zmq(name: str) -> str: ping_doc = config_doc["ping_handler_config"] ping_doc["ping_savedir"] = str(client_dir) + ping_doc["dst_ip"] = args.server_address.split(":")[0] for key in ["bidirectional_zmq_sockname", "zmq_kill_switch_sockname"]: ping_doc[key] = resolve_zmq(ping_doc[key]) @@ -152,10 +160,10 @@ def run_bandwidth_allocator(bw_config): except GracefulShutdown: return None - def run_main_client(client_config): + def run_main_client(client_config, ready_queue): signal.signal(signal.SIGINT, signal.SIG_IGN) try: - return Client(client_config).main_loop() + return Client(client_config, ready_queue=ready_queue).main_loop() except GracefulShutdown: return None @@ -197,8 +205,9 @@ def run_ping_handler(ping_config): zmq_context = zmq.Context() - # track the original signint_handler to restore it later. + # track the original signal handlers to restore them later. original_sigint_handler = signal.getsignal(signal.SIGINT) + original_sigterm_handler = signal.getsignal(signal.SIGTERM) with Pool(processes=total_processes) as pool: early_abort = False @@ -220,6 +229,7 @@ def signal_handler(sig, frame): signal_exit = True signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) # Phase 1: Create all configs and kill switches before starting any processes. # This ensures err_callback can send ABORT to all processes if one fails early. @@ -279,16 +289,32 @@ def signal_handler(sig, frame): # Start all client processes logger.info("Starting %d client processes", num_client_processes) + manager = Manager() + client_ready_queue = manager.Queue() for client_config in client_configs: logger.debug("Launching client process for service_id=%d", client_config.service_id) proc_name = f"Client-{client_config.service_id}" proc_results.append(( proc_name, pool.apply_async( - run_main_client, [client_config], error_callback=err_callback + run_main_client, [client_config, client_ready_queue], error_callback=err_callback ) )) + # Wait for all client processes to bind quic_rcv_zmq_socket before + # signaling readiness. Rust QUIC clients (in a separate Docker container) + # depend on this health signal to start, so we must ensure the Python + # sockets are bound and ready to accept connections first. + logger.info("Waiting for %d client processes to bind quic_rcv_zmq_socket...", num_client_processes) + for _ in range(num_client_processes): + service_id = client_ready_queue.get() + logger.info("Client %d: quic_rcv_zmq_socket bound and ready", service_id) + + health_signal = Path("/health/client_main_ready") + if health_signal.parent.exists(): + health_signal.touch() + logger.info("Health signal written: %s", health_signal) + # Start bandwidth allocator process logger.info("Starting bandwidth allocator process") proc_results.append(( @@ -354,6 +380,7 @@ def signal_handler(sig, frame): logger.info("Pool will exit. If needed, press Ctrl-C again to terminate the program finally.") signal.signal(signal.SIGINT, original_sigint_handler) + signal.signal(signal.SIGTERM, original_sigterm_handler) logger.info("Exiting kill switches...") for ks in kill_switches: diff --git a/src/python/server_main.py b/src/python/server_main.py index 2ad591e..2d27541 100644 --- a/src/python/server_main.py +++ b/src/python/server_main.py @@ -103,8 +103,9 @@ def run_server(server_config): num_server_processes = len(config_doc["server_config_list"]) logger.info("Initializing process pool with %d ModelServer processes", num_server_processes) - # track the original signint_handler to restore it later. + # track the original signal handlers to restore them later. original_sigint_handler = signal.getsignal(signal.SIGINT) + original_sigterm_handler = signal.getsignal(signal.SIGTERM) zmq_context = zmq.Context() with Pool(processes=num_server_processes) as pool: @@ -127,6 +128,7 @@ def signal_handler(sig, frame): signal_exit = True signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) # Phase 1: Create all configs and kill switches before starting any processes. # This ensures err_callback can send ABORT to all processes if one fails early. @@ -205,6 +207,7 @@ def signal_handler(sig, frame): logger.info("Pool will exit. If needed, press Ctrl-C again to terminate the program finally.") signal.signal(signal.SIGINT, original_sigint_handler) + signal.signal(signal.SIGTERM, original_sigterm_handler) for ks in kill_switches: ks.close(linger=0) diff --git a/src/python/web_frontend/start_web_dashboard.py b/src/python/web_frontend/start_web_dashboard.py index c325a84..e728e5d 100755 --- a/src/python/web_frontend/start_web_dashboard.py +++ b/src/python/web_frontend/start_web_dashboard.py @@ -28,7 +28,7 @@ def parse_args(): help="Host to bind the server to (default: 0.0.0.0)", ) parser.add_argument( - "--config", type=str, help="Path to configuration file (optional)" + "-c", "--config", type=str, help="Path to configuration file (optional)" ) parser.add_argument( "--refresh-rate", diff --git a/src/python/web_frontend/web_frontend.py b/src/python/web_frontend/web_frontend.py index 0d08663..4a7e6df 100644 --- a/src/python/web_frontend/web_frontend.py +++ b/src/python/web_frontend/web_frontend.py @@ -353,12 +353,19 @@ def main(config: str): web_plotter = WebPlotterAdapter(config, web_config.plotting_loop_sleep_seconds) + # Signal readiness for Docker healthcheck (used by depends_on sequencing) + from pathlib import Path + health_signal = Path("/health/monitor_ready") + if health_signal.parent.exists(): + health_signal.touch() + LOGGER.info("Health signal written: %s", health_signal) + # Start the plotting loop web_plotter.run_plotting_loop() LOGGER.info("Starting web server on http://localhost:5000") LOGGER.info("Press Ctrl+C to stop") - socketio.run(app, host="0.0.0.0", port=5000, debug=False) + socketio.run(app, host="0.0.0.0", port=5000, debug=False, allow_unsafe_werkzeug=True) except KeyboardInterrupt: LOGGER.info("Shutting down...") From b06306668dcfc2efb0eed6c451fbac5e60ee5822 Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Sun, 1 Mar 2026 02:05:54 -0800 Subject: [PATCH 06/15] feat: move mock toggle to CLI rather than via yaml --- README.md | 62 ++++++------------- config/client_config.yaml | 12 +--- config/server_config_gcloud.yaml | 27 ++------ docker/.env.example | 6 +- docker/README.Docker.md | 9 ++- docker/compose.yaml | 4 +- docker/config/client_config_docker.yaml | 12 +--- .../config/server_config_gcloud_docker.yaml | 27 ++------ src/python/client_main.py | 19 ++++++ src/python/server_main.py | 20 ++++++ 10 files changed, 93 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index 6e1635b..5a923b1 100644 --- a/README.md +++ b/README.md @@ -388,63 +388,41 @@ If you prefer to run each process directly on your host without Docker, follow t ## Mock Modes -TURBO supports two independent mock modes for testing and development without requiring physical cameras or GPUs. Each can be enabled or disabled independently by setting or nulling the corresponding config key. +TURBO supports two independent mock modes for testing and development without requiring physical cameras or GPUs. Mock modes are enabled via **CLI flags** on `client_main.py` and `server_main.py`. The YAML config files specify which mock files to use; the CLI flag controls whether they are actually applied. ### Mock Camera Mode (Client-Side) -Replaces live USB camera capture with a static image. Each camera stream reads a pre-loaded image from disk instead of capturing from a webcam. +Replaces live USB camera capture with a static image. Pass `--mock-camera` to `client_main.py` to enable. -**Config key:** `mock_camera_image_path` in `camera_stream_config_list` entries +```bash +# Manual setup +uv run client_main.py -c ../../config/client_config.yaml -s --mock-camera +``` -| Value | Behavior | -|---|---| -| File path (e.g. `mock_webcam_image.jpg`) | Loads the image and serves it as every frame | -| `null` | Uses the real USB camera | +The image path is configured per camera in `camera_stream_config_list` via the `mock_camera_image_path` key. A sample mock image is included at `src/python/camera_stream/mock_webcam_image.jpg`. Without `--mock-camera`, this path is ignored and real USB cameras are used. -A sample mock image is included at `src/python/camera_stream/mock_webcam_image.jpg`. +### Mock Inference Mode (Server-Side) -**Example** (in `config/client_config.yaml` or `docker/config/client_config_docker.yaml`): -```yaml -camera_stream_config_list: - - camera_id: 1 - # ... other fields ... - mock_camera_image_path: /home/hwei/turbo/src/python/camera_stream/mock_webcam_image.jpg -``` +Skips GPU model loading and returns pre-recorded detection results. Pass `--mock-inference` to `server_main.py` to enable. Optionally simulates per-model inference latency using a CSV of benchmark timings. -Set to `null` to use real cameras: -```yaml - mock_camera_image_path: null +```bash +# Manual setup +uv run server_main.py -c ../../config/server_config_gcloud.yaml --mock-inference ``` -### Mock Inference Mode (Server-Side) +The mock files are configured per server in `server_config_list` via `mock_inference_output_path` (numpy array of detections) and `mock_model_latency_csv_path` (per-model latency benchmarks). A sample mock output is included at `src/python/camera_stream/example_effdet_d4_output.npy`. Without `--mock-inference`, these paths are ignored and real GPU inference is used. -Skips GPU model loading and inference entirely. The server returns a pre-recorded detection result (a numpy array) instead of running EfficientDet on the GPU. Optionally simulates per-model inference latency using a CSV of benchmark timings. +### Docker -**Config keys:** `mock_inference_output_path` and `mock_model_latency_csv_path` in `server_config_list` entries +In Docker, mock modes are controlled via environment variables in `docker/.env`: -| Key | Value | Behavior | -|---|---|---| -| `mock_inference_output_path` | File path (e.g. `example_effdet_d4_output.npy`) | Returns pre-recorded detections, skips GPU | -| `mock_inference_output_path` | `null` | Loads models and runs real GPU inference | -| `mock_model_latency_csv_path` | File path (e.g. `experiment_model_info.csv`) | Simulates realistic per-model latency in mock mode | -| `mock_model_latency_csv_path` | `null` | No simulated delay (returns immediately, logs a warning per request) | - -A sample mock output is included at `src/python/camera_stream/example_effdet_d4_output.npy`. - -**Example** (in `config/server_config_gcloud.yaml` or `docker/config/server_config_gcloud_docker.yaml`): -```yaml -server_config_list: - - service_id: 1 - # ... other fields ... - mock_inference_output_path: /home/hwei/turbo/src/python/camera_stream/example_effdet_d4_output.npy - mock_model_latency_csv_path: /home/hwei/turbo/experiment_model_info.csv +```bash +# Set to any non-empty value (e.g. "true") to enable, leave empty to disable +MOCK_CAMERA=true +MOCK_INFERENCE=true ``` -Set to `null` to use real GPU inference: -```yaml - mock_inference_output_path: null - mock_model_latency_csv_path: null -``` +See [docker/README.Docker.md](docker/README.Docker.md#mock-modes) for details. ### Combining Mock Modes diff --git a/config/client_config.yaml b/config/client_config.yaml index e0b507b..aa33f1c 100644 --- a/config/client_config.yaml +++ b/config/client_config.yaml @@ -99,9 +99,7 @@ camera_stream_config_list: shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] zmq_kill_switch_sockname: camera-kill-1-switch - # Mock camera: uncomment ONE of the following lines - # mock_camera_image_path: /home/hwei/turbo/src/python/camera_stream/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam - mock_camera_image_path: null # real mode — captures from USB camera + mock_camera_image_path: /home/hwei/turbo/src/python/camera_stream/mock_webcam_image.jpg # only used when --mock-camera is passed - camera_id: 2 # FRONT LEFT usb_id: 4 # TODO: CONFIGURE THIS max_entries: *max-log-entries @@ -111,9 +109,7 @@ camera_stream_config_list: shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] zmq_kill_switch_sockname: camera-kill-2-switch - # Mock camera: uncomment ONE of the following lines - # mock_camera_image_path: /home/hwei/turbo/src/python/camera_stream/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam - mock_camera_image_path: null # real mode — captures from USB camera + mock_camera_image_path: /home/hwei/turbo/src/python/camera_stream/mock_webcam_image.jpg # only used when --mock-camera is passed - camera_id: 3 # FRONT RIGHT usb_id: 8 # TODO: CONFIGURE THIS max_entries: *max-log-entries @@ -123,9 +119,7 @@ camera_stream_config_list: shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] zmq_kill_switch_sockname: camera-kill-3-switch - # Mock camera: uncomment ONE of the following lines - # mock_camera_image_path: /home/hwei/turbo/src/python/camera_stream/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam - mock_camera_image_path: null # real mode — captures from USB camera + mock_camera_image_path: /home/hwei/turbo/src/python/camera_stream/mock_webcam_image.jpg # only used when --mock-camera is passed main_plotter_config: # service 4 is for junk stream diff --git a/config/server_config_gcloud.yaml b/config/server_config_gcloud.yaml index a5fed64..cab9665 100644 --- a/config/server_config_gcloud.yaml +++ b/config/server_config_gcloud.yaml @@ -38,13 +38,8 @@ server_config_list: thread_concurrency: 10 shm_filesize: *shm-filesize zmq_kill_switch_sockname: remote-server-kill-switch-1 - # Mock inference: uncomment ONE of the following blocks - # -- mock mode — skips GPU model loading, returns pre-recorded detections - # mock_inference_output_path: /home/hwei/turbo/src/python/camera_stream/example_effdet_d4_output.npy - # mock_model_latency_csv_path: /home/hwei/turbo/experiment_model_info.csv - # -- real mode — loads models and runs GPU inference - mock_inference_output_path: null - mock_model_latency_csv_path: null + mock_inference_output_path: /home/hwei/turbo/src/python/camera_stream/example_effdet_d4_output.npy # only used when --mock-inference is passed + mock_model_latency_csv_path: /home/hwei/turbo/experiment_model_info.csv # only used when --mock-inference is passed - service_id: 2 max_entries: *max-log-entries model_metadata_list: *model-list-ref @@ -56,13 +51,8 @@ server_config_list: thread_concurrency: 10 shm_filesize: *shm-filesize zmq_kill_switch_sockname: remote-server-kill-switch-2 - # Mock inference: uncomment ONE of the following blocks - # -- mock mode — skips GPU model loading, returns pre-recorded detections - # mock_inference_output_path: /home/hwei/turbo/src/python/camera_stream/example_effdet_d4_output.npy - # mock_model_latency_csv_path: /home/hwei/turbo/experiment_model_info.csv - # -- real mode — loads models and runs GPU inference - mock_inference_output_path: null - mock_model_latency_csv_path: null + mock_inference_output_path: /home/hwei/turbo/src/python/camera_stream/example_effdet_d4_output.npy # only used when --mock-inference is passed + mock_model_latency_csv_path: /home/hwei/turbo/experiment_model_info.csv # only used when --mock-inference is passed - service_id: 3 max_entries: *max-log-entries model_metadata_list: *model-list-ref @@ -74,10 +64,5 @@ server_config_list: thread_concurrency: 10 shm_filesize: *shm-filesize zmq_kill_switch_sockname: remote-server-kill-switch-3 - # Mock inference: uncomment ONE of the following blocks - # -- mock mode — skips GPU model loading, returns pre-recorded detections - # mock_inference_output_path: /home/hwei/turbo/src/python/camera_stream/example_effdet_d4_output.npy - # mock_model_latency_csv_path: /home/hwei/turbo/experiment_model_info.csv - # -- real mode — loads models and runs GPU inference - mock_inference_output_path: null - mock_model_latency_csv_path: null + mock_inference_output_path: /home/hwei/turbo/src/python/camera_stream/example_effdet_d4_output.npy # only used when --mock-inference is passed + mock_model_latency_csv_path: /home/hwei/turbo/experiment_model_info.csv # only used when --mock-inference is passed diff --git a/docker/.env.example b/docker/.env.example index 8a1d7b1..a0613b5 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -44,4 +44,8 @@ RUST_BACKTRACE=full # for both client and server EXPERIMENT_MODEL_INFO_PATH=../experiment_model_info.csv MOCK_EFFDET_OUTPUT_PATH=../src/python/camera_stream/example_effdet_d4_output.npy -MOCK_EFFDET_INPUT_PATH=../src/python/camera_stream/mock_webcam_image.jpg \ No newline at end of file +MOCK_EFFDET_INPUT_PATH=../src/python/camera_stream/mock_webcam_image.jpg + +# Mock modes — set to any non-empty value (e.g. "true") to enable, leave empty to disable +MOCK_CAMERA= +MOCK_INFERENCE= \ No newline at end of file diff --git a/docker/README.Docker.md b/docker/README.Docker.md index 78a6e61..0141be5 100644 --- a/docker/README.Docker.md +++ b/docker/README.Docker.md @@ -52,7 +52,14 @@ You generally don't need to edit these unless you're changing service behavior ( TURBO supports mock camera and mock inference modes for testing without physical cameras or GPUs. See the [Mock Modes](../README.md#mock-modes) section in the main README for full details. -In the Docker configs (`docker/config/`), the mock files are bundled into the container at `/app/` — so the paths use `/app/mock_webcam_image.jpg` and `/app/example_effdet_d4_output.npy` instead of host-absolute paths. +In Docker, mock modes are toggled via environment variables in `.env`: + +| Variable | Effect when non-empty | Default | +|---|---|---| +| `MOCK_CAMERA` | Passes `--mock-camera` to `client_main.py` — uses static images instead of USB cameras | (empty — disabled) | +| `MOCK_INFERENCE` | Passes `--mock-inference` to `server_main.py` — returns pre-recorded detections instead of GPU inference | (empty — disabled) | + +The mock data files (`mock_webcam_image.jpg`, `example_effdet_d4_output.npy`) are bundled into the container at `/app/` and their paths are pre-configured in `docker/config/`. ### Additional running commands diff --git a/docker/compose.yaml b/docker/compose.yaml index bda88e2..0045233 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -170,7 +170,7 @@ services: target: /app/mock_webcam_image.jpg read_only: true command: > - bash -c 'cd src/python && exec python client_main.py -c /app/python_config.yaml -s ${QUIC_CLIENT_ADDR}' + bash -c 'cd src/python && exec python client_main.py -c /app/python_config.yaml -s ${QUIC_CLIENT_ADDR} ${MOCK_CAMERA:+--mock-camera}' healthcheck: test: ["CMD", "test", "-f", "/health/client_main_ready"] interval: 2s @@ -225,6 +225,8 @@ services: source: ${MOCK_EFFDET_OUTPUT_PATH} target: /app/example_effdet_d4_output.npy read_only: true + command: > + bash -c 'cd src/python && exec python server_main.py -c /app/python_config.yaml ${MOCK_INFERENCE:+--mock-inference}' client_python_monitor: profiles: ['client'] init: true diff --git a/docker/config/client_config_docker.yaml b/docker/config/client_config_docker.yaml index c987824..4ad680a 100644 --- a/docker/config/client_config_docker.yaml +++ b/docker/config/client_config_docker.yaml @@ -99,9 +99,7 @@ camera_stream_config_list: shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] zmq_kill_switch_sockname: camera-kill-1-switch - # Mock camera: uncomment ONE of the following lines - # mock_camera_image_path: /app/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam - mock_camera_image_path: null # real mode — captures from USB camera + mock_camera_image_path: /app/mock_webcam_image.jpg # only used when --mock-camera is passed - camera_id: 2 # FRONT LEFT usb_id: 4 # TODO: CONFIGURE THIS max_entries: *max-log-entries @@ -111,9 +109,7 @@ camera_stream_config_list: shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] zmq_kill_switch_sockname: camera-kill-2-switch - # Mock camera: uncomment ONE of the following lines - # mock_camera_image_path: /app/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam - mock_camera_image_path: null # real mode — captures from USB camera + mock_camera_image_path: /app/mock_webcam_image.jpg # only used when --mock-camera is passed - camera_id: 3 # FRONT RIGHT usb_id: 8 # TODO: CONFIGURE THIS max_entries: *max-log-entries @@ -123,9 +119,7 @@ camera_stream_config_list: shmem_buf_size: *quic-shm-size camera_np_size: [1080, 1920, 3] zmq_kill_switch_sockname: camera-kill-3-switch - # Mock camera: uncomment ONE of the following lines - # mock_camera_image_path: /app/mock_webcam_image.jpg # mock mode — serves a static image instead of webcam - mock_camera_image_path: null # real mode — captures from USB camera + mock_camera_image_path: /app/mock_webcam_image.jpg # only used when --mock-camera is passed main_plotter_config: # service 4 is for junk stream diff --git a/docker/config/server_config_gcloud_docker.yaml b/docker/config/server_config_gcloud_docker.yaml index 6158818..0c14dde 100644 --- a/docker/config/server_config_gcloud_docker.yaml +++ b/docker/config/server_config_gcloud_docker.yaml @@ -38,13 +38,8 @@ server_config_list: thread_concurrency: 10 shm_filesize: *shm-filesize zmq_kill_switch_sockname: remote-server-kill-switch-1 - # Mock inference: uncomment ONE of the following blocks - # -- mock mode — skips GPU model loading, returns pre-recorded detections - # mock_inference_output_path: /app/example_effdet_d4_output.npy - # mock_model_latency_csv_path: /app/experiment_model_info.csv - # -- real mode — loads models and runs GPU inference - mock_inference_output_path: null - mock_model_latency_csv_path: null + mock_inference_output_path: /app/example_effdet_d4_output.npy # only used when --mock-inference is passed + mock_model_latency_csv_path: /app/experiment_model_info.csv # only used when --mock-inference is passed - service_id: 2 max_entries: *max-log-entries model_metadata_list: *model-list-ref @@ -56,13 +51,8 @@ server_config_list: thread_concurrency: 10 shm_filesize: *shm-filesize zmq_kill_switch_sockname: remote-server-kill-switch-2 - # Mock inference: uncomment ONE of the following blocks - # -- mock mode — skips GPU model loading, returns pre-recorded detections - # mock_inference_output_path: /app/example_effdet_d4_output.npy - # mock_model_latency_csv_path: /app/experiment_model_info.csv - # -- real mode — loads models and runs GPU inference - mock_inference_output_path: null - mock_model_latency_csv_path: null + mock_inference_output_path: /app/example_effdet_d4_output.npy # only used when --mock-inference is passed + mock_model_latency_csv_path: /app/experiment_model_info.csv # only used when --mock-inference is passed - service_id: 3 max_entries: *max-log-entries model_metadata_list: *model-list-ref @@ -74,10 +64,5 @@ server_config_list: thread_concurrency: 10 shm_filesize: *shm-filesize zmq_kill_switch_sockname: remote-server-kill-switch-3 - # Mock inference: uncomment ONE of the following blocks - # -- mock mode — skips GPU model loading, returns pre-recorded detections - # mock_inference_output_path: /app/example_effdet_d4_output.npy - # mock_model_latency_csv_path: /app/experiment_model_info.csv - # -- real mode — loads models and runs GPU inference - mock_inference_output_path: null - mock_model_latency_csv_path: null + mock_inference_output_path: /app/example_effdet_d4_output.npy # only used when --mock-inference is passed + mock_model_latency_csv_path: /app/experiment_model_info.csv # only used when --mock-inference is passed diff --git a/src/python/client_main.py b/src/python/client_main.py index ce32028..3cfe34b 100644 --- a/src/python/client_main.py +++ b/src/python/client_main.py @@ -69,6 +69,11 @@ required=True, help="server address (host or host:port) — IP is extracted for ping handler", ) + parser.add_argument( + "--mock-camera", + action="store_true", + help="enable mock camera mode — use static images from paths in config instead of USB cameras", + ) args = parser.parse_args() @@ -135,6 +140,20 @@ def resolve_zmq(name: str) -> str: doc["camera_savedir"] = str(client_dir) for key in ["bidirectional_zmq_sockname", "zmq_kill_switch_sockname"]: doc[key] = resolve_zmq(doc[key]) + if not args.mock_camera: + doc["mock_camera_image_path"] = None + + if args.mock_camera: + logger.warning( + "\n" + "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" + "@ WARNING: MOCK CAMERA MODE IS ENABLED! @\n" + "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" + "All camera streams will serve a static image from disk\n" + "instead of capturing from USB cameras.\n" + "This is intended for testing only.\n" + "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" + ) plotter_doc = config_doc["main_plotter_config"] plotter_doc["zmq_incoming_diagnostic_name"] = resolve_zmq( diff --git a/src/python/server_main.py b/src/python/server_main.py index 2d27541..3db40ab 100644 --- a/src/python/server_main.py +++ b/src/python/server_main.py @@ -54,6 +54,11 @@ required=True, help="path to server config file", ) + parser.add_argument( + "--mock-inference", + action="store_true", + help="enable mock inference mode — skip GPU model loading and return pre-recorded detections from paths in config", + ) args = parser.parse_args() @@ -87,6 +92,21 @@ doc["server_log_savedir"] = str(server_dir) for key in ["incoming_zmq_sockname", "outgoing_zmq_sockname", "zmq_kill_switch_sockname"]: doc[key] = f"ipc://{zmq_dir / doc[key]}" + if not args.mock_inference: + doc["mock_inference_output_path"] = None + doc["mock_model_latency_csv_path"] = None + + if args.mock_inference: + logger.warning( + "\n" + "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" + "@ WARNING: MOCK INFERENCE MODE IS ENABLED! @\n" + "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" + "All model servers will skip GPU model loading and return\n" + "pre-recorded detection results instead of running inference.\n" + "This is intended for testing only.\n" + "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" + ) logger.info("experiment starting.") From 735ff43a3811d1f4cb37b6da38b1868dcde6b2c2 Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Sun, 1 Mar 2026 14:14:54 -0800 Subject: [PATCH 07/15] fix: remove Cargo.lock from .gitignore --- .gitignore | 1 - src/quic/Cargo.lock | 4936 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 4936 insertions(+), 1 deletion(-) create mode 100644 src/quic/Cargo.lock diff --git a/.gitignore b/.gitignore index b5956bd..db26615 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,6 @@ bandwidth_allocation/.DS_Store **/.vscode/* **/target/* -**/Cargo.lock # we don't want your SSL certs/keys **/*.key diff --git a/src/quic/Cargo.lock b/src/quic/Cargo.lock new file mode 100644 index 0000000..6079877 --- /dev/null +++ b/src/quic/Cargo.lock @@ -0,0 +1,4936 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "argminmax" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f13d10a41ac8d2ec79ee34178d61e6f47a29c2edfe7ef1721c7383b0359e65" +dependencies = [ + "num-traits", +] + +[[package]] +name = "array-init-cursor" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed51fe0f224d1d4ea768be38c51f9f831dee9d05c163c11fba0b8c44387b1fc3" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "atoi_simd" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a49e05797ca52e312a0c658938b7d00693ef037799ef7187678f212d7684cf" +dependencies = [ + "debug_unsafe", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atomic_float" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.1.3", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "comfy-table" +version = "7.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" +dependencies = [ + "crossterm", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "config" +version = "0.15.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1eb4fb07bc7f012422df02766c7bd5971effb894f573865642f06fa3265440" +dependencies = [ + "async-trait", + "convert_case", + "json5", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "winnow", + "yaml-rust2", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "parking_lot", + "rustix 0.38.44", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctrlc" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881c5d0a13b2f1498e2306e82cbada78390e152d4b1378fb28a84f4dcd0dc4f3" +dependencies = [ + "dispatch", + "nix", + "windows-sys 0.61.2", +] + +[[package]] +name = "cuckoofilter" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b810a8449931679f64cd7eef1bbd0fa315801b6d5d9cdc1ace2804d6529eee18" +dependencies = [ + "byteorder", + "fnv", + "rand 0.7.3", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "debug_unsafe" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85d3cef41d236720ed453e102153a53e4cc3d2fde848c0078a50cf249e8e3e5b" + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "ethnum" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "libz-rs-sys", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs4" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +dependencies = [ + "rustix 1.0.8", + "windows-sys 0.59.0", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "h2" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hash_hasher" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b4b9ebce26001bad2e6366295f64e381c1e9c479109202149b9e15e154973e9" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", + "rayon", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", + "rayon", + "serde", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.4", +] + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "byteorder", + "num-traits", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.0", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown 0.15.4", + "serde", +] + +[[package]] +name = "intrusive-collections" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +dependencies = [ + "memoffset", +] + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.2", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libz-rs-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" +dependencies = [ + "zlib-rs", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" +dependencies = [ + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memmap2" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "now" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89e9874397a1f0a52fc1f197a8effd9735223cb2390e9dcc83ac6cd02923d0" +dependencies = [ + "chrono", +] + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "object_store" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc4f07659e11cd45a341cd24d71e683e3be65d9ff1f8150061678fe60437496" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "chrono", + "form_urlencoded", + "futures", + "http", + "http-body-util", + "humantime", + "hyper", + "itertools 0.14.0", + "parking_lot", + "percent-encoding", + "quick-xml", + "rand 0.9.2", + "reqwest", + "ring", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror 2.0.12", + "tokio", + "tracing", + "url", + "walkdir", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +dependencies = [ + "memchr", + "thiserror 2.0.12", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "planus" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1691dd09e82f428ce8d6310bd6d5da2557c82ff17694d2a32cad7242aea89f" +dependencies = [ + "array-init-cursor", +] + +[[package]] +name = "polars" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0c10a02dc15223de108e0625bf152efb8dc3b181c5916b5ee335e40dcda735a" +dependencies = [ + "getrandom 0.2.16", + "polars-arrow", + "polars-core", + "polars-error", + "polars-io", + "polars-lazy", + "polars-ops", + "polars-parquet", + "polars-sql", + "polars-time", + "polars-utils", + "version_check", +] + +[[package]] +name = "polars-arrow" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef8c08875db45de6f71660ef15a686e459ab0dddae302b2870d66625fd3ba65" +dependencies = [ + "atoi_simd", + "bitflags", + "bytemuck", + "chrono", + "chrono-tz", + "dyn-clone", + "either", + "ethnum", + "getrandom 0.2.16", + "hashbrown 0.15.4", + "itoa", + "lz4", + "num-traits", + "polars-arrow-format", + "polars-error", + "polars-schema", + "polars-utils", + "serde", + "simdutf8", + "streaming-iterator", + "strum_macros", + "version_check", + "zstd", +] + +[[package]] +name = "polars-arrow-format" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b0ef2474af9396b19025b189d96e992311e6a47f90c53cd998b36c4c64b84c" +dependencies = [ + "planus", + "serde", +] + +[[package]] +name = "polars-compute" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54171a366ec0bd3431ce184568e731de14060c20e19f306706cb073e7a98dc1" +dependencies = [ + "atoi_simd", + "bytemuck", + "chrono", + "either", + "fast-float2", + "hashbrown 0.15.4", + "itoa", + "num-traits", + "polars-arrow", + "polars-error", + "polars-utils", + "rand 0.8.5", + "ryu", + "serde", + "skiplist", + "strength_reduce", + "strum_macros", + "version_check", +] + +[[package]] +name = "polars-core" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d13c50b27ed6df2f6a9d156e9a4960e9373bf65fc763e0be2738efa5457d915" +dependencies = [ + "bitflags", + "bytemuck", + "chrono", + "chrono-tz", + "comfy-table", + "either", + "hashbrown 0.14.5", + "hashbrown 0.15.4", + "indexmap", + "itoa", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-error", + "polars-row", + "polars-schema", + "polars-utils", + "rand 0.8.5", + "rand_distr", + "rayon", + "regex", + "serde", + "serde_json", + "strum_macros", + "version_check", + "xxhash-rust", +] + +[[package]] +name = "polars-error" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa800b7240c7326e54d6b95df8376e3ee760e4cb4170fe38de1f42d14d719ffc" +dependencies = [ + "object_store", + "parking_lot", + "polars-arrow-format", + "regex", + "signal-hook", + "simdutf8", +] + +[[package]] +name = "polars-expr" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0f1673599bee079c94d7d7e7e0a31818c7c88060dcf6ec82691a99e1eb5cf9" +dependencies = [ + "bitflags", + "hashbrown 0.15.4", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-io", + "polars-ops", + "polars-plan", + "polars-row", + "polars-time", + "polars-utils", + "rand 0.8.5", + "rayon", + "recursive", +] + +[[package]] +name = "polars-io" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db23d90c17ab03ccded685e5a88aa8ee43fd851376463a9533ed56ba66d1538" +dependencies = [ + "async-trait", + "atoi_simd", + "blake3", + "bytes", + "chrono", + "fast-float2", + "fs4", + "futures", + "glob", + "hashbrown 0.15.4", + "home", + "itoa", + "memchr", + "memmap2", + "num-traits", + "object_store", + "percent-encoding", + "polars-arrow", + "polars-core", + "polars-error", + "polars-parquet", + "polars-schema", + "polars-time", + "polars-utils", + "rayon", + "regex", + "reqwest", + "ryu", + "serde", + "serde_json", + "simdutf8", + "tokio", + "tokio-util", + "url", +] + +[[package]] +name = "polars-lazy" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03708e73a20fd7ca9fb0003e620daff5c488c9e2b287640289de78c5b135ead7" +dependencies = [ + "bitflags", + "chrono", + "either", + "memchr", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-ops", + "polars-pipe", + "polars-plan", + "polars-stream", + "polars-time", + "polars-utils", + "rayon", + "version_check", +] + +[[package]] +name = "polars-mem-engine" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52126424d9612132b0b8b4b995ea25a42d79559d111063ef705a370dfa742d46" +dependencies = [ + "futures", + "memmap2", + "polars-arrow", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "rayon", + "recursive", + "tokio", +] + +[[package]] +name = "polars-ops" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e308800c11d5c6d0ddef16186ca026691aedeeb2210d458cdea997520907917" +dependencies = [ + "argminmax", + "base64 0.22.1", + "bytemuck", + "chrono", + "chrono-tz", + "either", + "hashbrown 0.15.4", + "hex", + "indexmap", + "libm", + "memchr", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-schema", + "polars-utils", + "rayon", + "regex", + "regex-syntax 0.8.5", + "strum_macros", + "unicode-normalization", + "unicode-reverse", + "version_check", +] + +[[package]] +name = "polars-parquet" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e186177e24e217ce5b9bf441028417faeeef01b7e793fa815e1d26a53c38400" +dependencies = [ + "async-stream", + "base64 0.22.1", + "brotli", + "bytemuck", + "ethnum", + "flate2", + "futures", + "hashbrown 0.15.4", + "lz4", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-error", + "polars-parquet-format", + "polars-utils", + "serde", + "simdutf8", + "snap", + "streaming-decompression", + "zstd", +] + +[[package]] +name = "polars-parquet-format" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c025243dcfe8dbc57e94d9f82eb3bef10b565ab180d5b99bed87fd8aea319ce1" +dependencies = [ + "async-trait", + "futures", +] + +[[package]] +name = "polars-pipe" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59780346591510f17e26a2cb7a5f812167e9487163baf6570a53a4fab3e004f3" +dependencies = [ + "crossbeam-channel", + "crossbeam-queue", + "enum_dispatch", + "futures", + "hashbrown 0.15.4", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-expr", + "polars-io", + "polars-ops", + "polars-plan", + "polars-row", + "polars-utils", + "rayon", + "uuid", + "version_check", +] + +[[package]] +name = "polars-plan" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb0432eed610f25f436b519c007d4c6e2607c645ff27324aaaafda34ef51bf" +dependencies = [ + "bitflags", + "bytemuck", + "bytes", + "chrono", + "chrono-tz", + "either", + "futures", + "hashbrown 0.15.4", + "memmap2", + "num-traits", + "percent-encoding", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-io", + "polars-ops", + "polars-parquet", + "polars-time", + "polars-utils", + "rayon", + "recursive", + "regex", + "strum_macros", + "version_check", +] + +[[package]] +name = "polars-row" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be6a32d19ccae4dad60ef0394972419c771b393d31e1b141a89c69de5b33990" +dependencies = [ + "bitflags", + "bytemuck", + "polars-arrow", + "polars-compute", + "polars-error", + "polars-utils", +] + +[[package]] +name = "polars-schema" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b56ea2026a869b9bae7d8bd861b420d72e7bca4cf4b757368874577ac666b6" +dependencies = [ + "indexmap", + "polars-error", + "polars-utils", + "serde", + "version_check", +] + +[[package]] +name = "polars-sql" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ada7336bd78c67d896f9a13082b02e4a159c6c6fba777dcc152c0b1f126453b" +dependencies = [ + "bitflags", + "hex", + "polars-core", + "polars-error", + "polars-lazy", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "rand 0.8.5", + "regex", + "serde", + "sqlparser", +] + +[[package]] +name = "polars-stream" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81638ef3da43d632da0250386bf4f042fe514005f2a6d59d9e6118194888eb7" +dependencies = [ + "async-channel", + "async-trait", + "atomic-waker", + "bitflags", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "futures", + "memmap2", + "parking_lot", + "percent-encoding", + "pin-project-lite", + "polars-arrow", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-ops", + "polars-parquet", + "polars-plan", + "polars-utils", + "rand 0.8.5", + "rayon", + "recursive", + "slotmap", + "tokio", + "version_check", +] + +[[package]] +name = "polars-time" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706ea1e67d5bfcfd9e5ca59070fb5ad591d42b9729d4a60af1b320e645ea158e" +dependencies = [ + "atoi_simd", + "bytemuck", + "chrono", + "chrono-tz", + "now", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-ops", + "polars-utils", + "rayon", + "regex", + "strum_macros", +] + +[[package]] +name = "polars-utils" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c50cd0dac46936771793eb22cb7aeeef97d6aaa4f832f4209637e73178d39aa" +dependencies = [ + "bincode", + "bytemuck", + "bytes", + "compact_str", + "flate2", + "foldhash", + "hashbrown 0.15.4", + "indexmap", + "libc", + "memmap2", + "num-traits", + "polars-error", + "rand 0.8.5", + "raw-cpuid", + "rayon", + "regex", + "rmp-serde", + "serde", + "serde_ignored", + "serde_json", + "slotmap", + "stacker", + "sysinfo", + "version_check", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" +dependencies = [ + "cc", +] + +[[package]] +name = "quic_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "atomic_float", + "bytes", + "chrono", + "clap", + "config", + "ctrlc", + "env_logger", + "hdrhistogram", + "libc", + "log", + "polars", + "quic_conn", + "rcgen", + "rustls", + "s2n-quic", + "serde", + "serde_json", + "tokio", + "tokio-context", + "tracing", + "tracing-subscriber", + "zeromq", +] + +[[package]] +name = "quic_conn" +version = "0.1.0" +dependencies = [ + "anyhow", + "atomic_float", + "bytes", + "clap", + "config", + "ctrlc", + "env_logger", + "hdrhistogram", + "libc", + "log", + "polars", + "rcgen", + "rustls", + "s2n-quic", + "serde", + "serde_json", + "tokio", + "tokio-context", + "tracing", + "tracing-subscriber", + "zeromq", +] + +[[package]] +name = "quic_server" +version = "0.1.0" +dependencies = [ + "anyhow", + "atomic_float", + "bytes", + "chrono", + "clap", + "config", + "ctrlc", + "env_logger", + "hdrhistogram", + "libc", + "log", + "polars", + "quic_conn", + "rcgen", + "rustls", + "s2n-quic", + "serde", + "serde_json", + "tokio", + "tokio-context", + "tracing", + "tracing-subscriber", + "zeromq", +] + +[[package]] +name = "quick-xml" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash 2.1.1", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.10", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-cpuid" +version = "11.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "recursive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" +dependencies = [ + "recursive-proc-macro-impl", + "stacker", +] + +[[package]] +name = "recursive-proc-macro-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "redox_syscall" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags", + "serde", + "serde_derive", +] + +[[package]] +name = "rust-ini" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7295b7ce3bf4806b419dc3420745998b447178b7005e2011947b38fc5aa6791" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.23.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069a8df149a16b1a12dcc31497c3396a173844be3cac4bd40c9e7671fef96671" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "s2n-codec" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb01109923c706ce362d9652fb31b40afcbfc2a2a7c724f4586657f41e29e2bb" +dependencies = [ + "byteorder", + "bytes", + "zerocopy", +] + +[[package]] +name = "s2n-quic" +version = "1.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a2652ec7ca318cc15fc042ae57fcbb74320ecb8a8c5b56787faca87d160bbc" +dependencies = [ + "bytes", + "cfg-if", + "cuckoofilter", + "futures", + "hash_hasher", + "rand 0.9.2", + "rand_chacha 0.9.0", + "s2n-codec", + "s2n-quic-core", + "s2n-quic-crypto", + "s2n-quic-platform", + "s2n-quic-tls-default", + "s2n-quic-transport", + "tokio", + "zerocopy", + "zeroize", +] + +[[package]] +name = "s2n-quic-core" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a849cbf43c5083bf413fdd434403819dff9899b8951b8bd6610f61e0f3499c" +dependencies = [ + "atomic-waker", + "byteorder", + "bytes", + "cfg-if", + "crossbeam-utils", + "hex-literal", + "num-rational", + "num-traits", + "once_cell", + "pin-project-lite", + "s2n-codec", + "subtle", + "tracing", + "zerocopy", +] + +[[package]] +name = "s2n-quic-crypto" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abdc40e0f1d5add749020bf0537a8fb9439a2181e291eba7b07323c70d98816" +dependencies = [ + "aws-lc-rs", + "cfg-if", + "lazy_static", + "s2n-codec", + "s2n-quic-core", + "zeroize", +] + +[[package]] +name = "s2n-quic-platform" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b23ae94ac4993d016882cb2f9c5a6b5dd15d0aa7f0dd95a36b3bed8b900aa3" +dependencies = [ + "cfg-if", + "futures", + "lazy_static", + "libc", + "s2n-quic-core", + "socket2 0.6.0", + "tokio", +] + +[[package]] +name = "s2n-quic-rustls" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d96dff62cd1bb3066bff552326b890fc0acd0ff4d1e5077e199bd484d86cda" +dependencies = [ + "bytes", + "rustls", + "rustls-pemfile", + "s2n-codec", + "s2n-quic-core", + "s2n-quic-crypto", +] + +[[package]] +name = "s2n-quic-tls" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25517d8be812ed2698054511bc7911cc4b04b7a46743608dbc5c77c3a8f07f31" +dependencies = [ + "bytes", + "errno", + "libc", + "s2n-codec", + "s2n-quic-core", + "s2n-quic-crypto", + "s2n-tls", +] + +[[package]] +name = "s2n-quic-tls-default" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0c1d0f8eda3a545d958aec8b109fb288184fd7a5b3f1dbd6aaaee1fb989909" +dependencies = [ + "s2n-quic-rustls", + "s2n-quic-tls", +] + +[[package]] +name = "s2n-quic-transport" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f141ee894b67b9cea4428dda7608b3e2da66e0a183067441c60f080a8734b91d" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "hashbrown 0.15.4", + "intrusive-collections", + "once_cell", + "s2n-codec", + "s2n-quic-core", + "siphasher", + "smallvec", +] + +[[package]] +name = "s2n-tls" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7795b2643472ca0c92abd9649fbbc96ce6b90ce88f8a75412abe49c580db89f8" +dependencies = [ + "errno", + "hex", + "libc", + "pin-project-lite", + "s2n-tls-sys", +] + +[[package]] +name = "s2n-tls-sys" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1f57616b8f5b24e38f5506a1ed22133088b5d307565953d3fded6e42714639" +dependencies = [ + "aws-lc-rs", + "cc", + "libc", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_ignored" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b516445dac1e3535b6d658a7b528d771153dfb272ed4180ca4617a20550365ff" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "skiplist" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eec25f46463fcdc5e02f388c2780b1b58e01be81a8378e62ec60931beccc3f6" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "sqlparser" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a528114c392209b3264855ad491fcce534b94a38771b0a0b97a79379275ce8" +dependencies = [ + "log", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stacker" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "streaming-decompression" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6cc3b19bfb128a8ad11026086e31d3ce9ad23f8ea37354b31383a187c44cf3" +dependencies = [ + "fallible-streaming-iterator", +] + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "windows", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2 0.6.0", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-context" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf0b8394dd5ca9a1b726c629390154c19222dfd7467a4b56f1ced90adee3958" +dependencies = [ + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-reverse" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6f4888ebc23094adfb574fdca9fdc891826287a6397d2cd28802ffd6f20c76" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yaml-rust2" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ce2a4ff45552406d02501cea6c18d8a7e50228e7736a872951fe2fe75c91be7" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeromq" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a4528179201f6eecf211961a7d3276faa61554c82651ecc66387f68fc3004bd" +dependencies = [ + "async-trait", + "asynchronous-codec", + "bytes", + "crossbeam-queue", + "dashmap", + "futures-channel", + "futures-io", + "futures-task", + "futures-util", + "log", + "num-traits", + "once_cell", + "parking_lot", + "rand 0.8.5", + "regex", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "uuid", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zlib-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] From 92c239d7b2833cfd07027791e5e4bce43fb9efea Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Sun, 1 Mar 2026 19:09:54 -0800 Subject: [PATCH 08/15] feat: add pre-built docker setup --- .env.example | 51 +++++++ README.md | 48 +++---- compose.yaml | 192 ++++++++++++++++++++++++++ docker/Dockerfile_turbo_python_base | 3 - docker/Dockerfile_turbo_python_binary | 5 +- docker/Dockerfile_turbo_quic_binary | 4 +- docker/README.Docker.md | 143 ++++++++++++++++++- docker/compose.yaml | 2 - 8 files changed, 408 insertions(+), 40 deletions(-) create mode 100644 .env.example create mode 100644 compose.yaml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..217ce54 --- /dev/null +++ b/.env.example @@ -0,0 +1,51 @@ +# DockerHub image settings +DOCKERHUB_USERNAME=hbwei +IMAGE_TAG=v0.0.1 + +# Host user/group IDs — set these to match your host user (id -u / id -g) +# so that bind-mounted files are accessible inside containers. +HOST_UID=1000 +HOST_GID=1000 + +# quic client-side only +# Same-host: use Docker bridge gateway (10.64.89.1). Remote: use server host's routable IP. +QUIC_CLIENT_ADDR=10.64.89.1:12345 +QUIC_CLIENT_PORT=5000 + +QUIC_DOCKER_CLIENT_CONFIG_PATH=./docker/config/quic_config_client_docker.yaml + +# quic server-side only +QUIC_SERVER_ADDR=0.0.0.0:12345 +QUIC_SERVER_PORT=12345 + +QUIC_DOCKER_SERVER_CONFIG_PATH=./docker/config/quic_config_gcloud_docker.yaml + +# python client config +PYTHON_DOCKER_CLIENT_CONFIG_PATH=./docker/config/client_config_docker.yaml + +# python server config +PYTHON_DOCKER_SERVER_CONFIG_PATH=./docker/config/server_config_gcloud_docker.yaml + +# both client and server-side +DOCKER_LOG_CONFIG_PATH=./docker/config/logging_config_docker.yaml +EXPERIMENT_OUTPUT_DIR=/path/to/experiment2-out + +# client-side only +DASHBOARD_PORT=5000 +MODEL_FULL_EVAL_DIR=/path/to/full-eval + +# server-side only +EFFDET_MODELS_DIR=/path/to/av-models + +# rust env vars +RUST_LOG=info +RUST_BACKTRACE=full + +# for both client and server +EXPERIMENT_MODEL_INFO_PATH=./experiment_model_info.csv +MOCK_EFFDET_OUTPUT_PATH=./src/python/camera_stream/example_effdet_d4_output.npy +MOCK_EFFDET_INPUT_PATH=./src/python/camera_stream/mock_webcam_image.jpg + +# Mock modes — set to any non-empty value (e.g. "true") to enable, leave empty to disable +MOCK_CAMERA= +MOCK_INFERENCE= diff --git a/README.md b/README.md index 5a923b1..3480dad 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,9 @@ For detailed architecture, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). ## Quick Start (Docker) — Recommended -The recommended way to run TURBO is with Docker. The Docker setup automatically orchestrates all processes — 2 on the server (QUIC server + model servers) and 3 on the client (client orchestrator + web dashboard + QUIC client) — handling startup ordering, ZMQ socket management, and inter-process communication for you. +The recommended way to run TURBO is with Docker using pre-built container images published on [DockerHub](https://hub.docker.com/u/hbwei). The Docker setup automatically orchestrates all processes — 2 on the server (QUIC server + model servers) and 3 on the client (client orchestrator + web dashboard + QUIC client) — handling startup ordering, ZMQ socket management, and inter-process communication for you. + +> **Pre-built images vs. building from source:** The root-level `compose.yaml` and `.env.example` are configured to pull pre-built images from DockerHub. To build Docker images from source instead (e.g., for development or customization), see the [`docker/`](docker/) directory which contains Dockerfiles, a separate `compose.yaml` for building, and [docker/README.Docker.md](docker/README.Docker.md#building-from-source) for full instructions. ### Prerequisites @@ -154,21 +156,13 @@ docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU unzip full-eval.zip -d ~ ``` -4. **Generate SSL keys for QUIC:** - ```bash - cd src/quic - pip install cryptography # if not already installed - python generate_cert.py - cd ../.. - ``` - -5. **Configure the `.env` file:** +4. **Configure the `.env` file:** ```bash - cp docker/.env.example docker/.env + cp .env.example .env ``` - Edit `docker/.env` and update the following values to match your host system: + Edit `.env` and update the following values to match your host system: | Variable | Description | Default | |---|---|---| @@ -178,33 +172,33 @@ docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU | `EFFDET_MODELS_DIR` | Absolute path to model checkpoints (server) | (must set) | | `MODEL_FULL_EVAL_DIR` | Absolute path to evaluation data (client) | (must set) | - Most other settings (networking, ports, SSL paths) work out of the box for same-host testing. See [docker/README.Docker.md](docker/README.Docker.md) for the full reference. + Most other settings (networking, ports) work out of the box for same-host testing. See [docker/README.Docker.md](docker/README.Docker.md#additional-configuration) for the full configuration reference. -6. **Create the experiment output directory:** +5. **Create the experiment output directory:** ```bash mkdir -p ~/experiment2-out ``` ### Running -All Docker commands should be run from the `docker/` directory: +**Pull the pre-built images:** ```bash -cd docker +docker compose --profile client --profile server pull ``` **Run both client and server on the same host:** ```bash -docker compose --profile client --profile server up --build +docker compose --profile client --profile server up ``` **Run server only** (e.g., on a cloud GPU machine): ```bash -docker compose --profile server up --build +docker compose --profile server up ``` **Run client only** (when server is running elsewhere — update `QUIC_CLIENT_ADDR` in `.env` to the server's IP): ```bash -docker compose --profile client up --build +docker compose --profile client up ``` Once running, open the monitoring dashboard at **http://localhost:5000**. @@ -218,7 +212,7 @@ The `-v` flag removes ephemeral volumes (ZMQ sockets, health signals), giving yo **Experiment output** will be logged to Parquet files in the configured output directory (default: `~/experiment2-out/`). -For development workflows (hot-reload, rebuilding), troubleshooting, and architecture details, see [docker/README.Docker.md](docker/README.Docker.md). +For troubleshooting, architecture details, and development workflows, see [docker/README.Docker.md](docker/README.Docker.md). --- @@ -414,7 +408,7 @@ The mock files are configured per server in `server_config_list` via `mock_infer ### Docker -In Docker, mock modes are controlled via environment variables in `docker/.env`: +In Docker, mock modes are controlled via environment variables in `.env` (or `docker/.env` when building from source): ```bash # Set to any non-empty value (e.g. "true") to enable, leave empty to disable @@ -463,12 +457,14 @@ An LP-based allocator runs every 500ms to select the optimal (model, compression ``` turbo/ -├── docker/ # Docker deployment (recommended) -│ ├── compose.yaml # Docker Compose orchestration -│ ├── .env.example # Template for configurable paths and settings -│ ├── config/ # Docker-specific YAML configs +├── compose.yaml # Docker Compose for pre-built images (recommended) +├── .env.example # Template for configurable paths and settings +├── docker/ # Building Docker images from source +│ ├── compose.yaml # Docker Compose for building from source +│ ├── .env.example # Template for build-from-source settings +│ ├── config/ # Docker-specific YAML configs (shared by both workflows) │ ├── Dockerfile_turbo_* # Multi-stage Dockerfiles -│ └── README.Docker.md # Docker setup documentation +│ └── README.Docker.md # Build-from-source docs, configuration reference, troubleshooting ├── src/ │ ├── python/ │ │ ├── client_main.py # Client-side process orchestrator diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..cbe6138 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,192 @@ +services: + quic_client: + profiles: ['client'] + init: true + ipc: host + stop_grace_period: 30s + user: "${HOST_UID}:${HOST_GID}" + image: ${DOCKERHUB_USERNAME}/turbo-quic-client:${IMAGE_TAG:-latest} + depends_on: + client_python_monitor: + condition: service_healthy + environment: + RUST_LOG: ${RUST_LOG} + RUST_BACKTRACE: ${RUST_BACKTRACE} + volumes: + - type: bind + source: ${QUIC_DOCKER_CLIENT_CONFIG_PATH} + target: /app/quic_config.yaml + read_only: true + - type: bind + source: ${EXPERIMENT_OUTPUT_DIR} + target: /app/experiment2-out + - zmq_client:/app/zmq-out + - health_signals:/health + networks: + - quic_net + + quic_server: + profiles: ['server'] + init: true + ipc: host + stop_grace_period: 30s + user: "${HOST_UID}:${HOST_GID}" + image: ${DOCKERHUB_USERNAME}/turbo-quic-server:${IMAGE_TAG:-latest} + environment: + RUST_LOG: ${RUST_LOG} + RUST_BACKTRACE: ${RUST_BACKTRACE} + volumes: + - type: bind + source: ${QUIC_DOCKER_SERVER_CONFIG_PATH} + target: /app/quic_config.yaml + read_only: true + - type: bind + source: ${EXPERIMENT_OUTPUT_DIR} + target: /app/experiment2-out + - zmq_server:/app/zmq-out + ports: + - "${QUIC_SERVER_PORT}:${QUIC_SERVER_PORT}/udp" + + client_python_main: + profiles: ['client'] + init: true + ipc: host + stop_grace_period: 30s + user: "${HOST_UID}:${HOST_GID}" + image: ${DOCKERHUB_USERNAME}/turbo-python-client:${IMAGE_TAG:-latest} + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + volumes: + - health_signals:/health + - type: bind + source: ${PYTHON_DOCKER_CLIENT_CONFIG_PATH} + target: /app/python_config.yaml + read_only: true + - type: bind + source: ${DOCKER_LOG_CONFIG_PATH} + target: /app/logging_config.yaml + read_only: true + - type: bind + source: ${EXPERIMENT_OUTPUT_DIR} + target: /app/experiment2-out + - zmq_client:/app/zmq-out + - type: bind + source: ${MODEL_FULL_EVAL_DIR} + target: /app/full-eval + read_only: true + - type: bind + source: ${EXPERIMENT_MODEL_INFO_PATH} + target: /app/experiment_model_info.csv + read_only: true + - type: bind + source: ${MOCK_EFFDET_INPUT_PATH} + target: /app/mock_webcam_image.jpg + read_only: true + command: > + bash -c 'cd src/python && exec python client_main.py -c /app/python_config.yaml -s ${QUIC_CLIENT_ADDR} ${MOCK_CAMERA:+--mock-camera}' + healthcheck: + test: ["CMD", "test", "-f", "/health/client_main_ready"] + interval: 2s + timeout: 3s + start_period: 30s + retries: 30 + + server_python_main: + profiles: ['server'] + init: true + ipc: host + stop_grace_period: 30s + user: "${HOST_UID}:${HOST_GID}" + image: ${DOCKERHUB_USERNAME}/turbo-python-server:${IMAGE_TAG:-latest} + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + volumes: + - type: bind + source: ${PYTHON_DOCKER_SERVER_CONFIG_PATH} + target: /app/python_config.yaml + read_only: true + - type: bind + source: ${DOCKER_LOG_CONFIG_PATH} + target: /app/logging_config.yaml + read_only: true + - type: bind + source: ${EXPERIMENT_OUTPUT_DIR} + target: /app/experiment2-out + - zmq_server:/app/zmq-out + - type: bind + source: ${EFFDET_MODELS_DIR} + target: /app/av-models + read_only: true + - type: bind + source: ${EXPERIMENT_MODEL_INFO_PATH} + target: /app/experiment_model_info.csv + read_only: true + - type: bind + source: ${MOCK_EFFDET_OUTPUT_PATH} + target: /app/example_effdet_d4_output.npy + read_only: true + command: > + bash -c 'cd src/python && exec python server_main.py -c /app/python_config.yaml ${MOCK_INFERENCE:+--mock-inference}' + + client_python_monitor: + profiles: ['client'] + init: true + ipc: host + stop_grace_period: 30s + user: "${HOST_UID}:${HOST_GID}" + image: ${DOCKERHUB_USERNAME}/turbo-python-monitor:${IMAGE_TAG:-latest} + depends_on: + client_python_main: + condition: service_healthy + volumes: + - health_signals:/health + - type: bind + source: ${PYTHON_DOCKER_CLIENT_CONFIG_PATH} + target: /app/python_config.yaml + read_only: true + - type: bind + source: ${EXPERIMENT_OUTPUT_DIR} + target: /app/experiment2-out + - zmq_client:/app/zmq-out + ports: + - "${DASHBOARD_PORT}:5000" + healthcheck: + test: ["CMD", "test", "-f", "/health/monitor_ready"] + interval: 2s + timeout: 3s + start_period: 30s + retries: 30 + +networks: + quic_net: + ipam: + config: + - subnet: 10.64.89.0/24 + gateway: 10.64.89.1 + +volumes: + health_signals: + driver: local + driver_opts: + type: tmpfs + device: tmpfs + zmq_client: + driver: local + driver_opts: + type: tmpfs + device: tmpfs + zmq_server: + driver: local + driver_opts: + type: tmpfs + device: tmpfs diff --git a/docker/Dockerfile_turbo_python_base b/docker/Dockerfile_turbo_python_base index 76f9222..11c43b7 100644 --- a/docker/Dockerfile_turbo_python_base +++ b/docker/Dockerfile_turbo_python_base @@ -37,6 +37,3 @@ RUN --mount=type=cache,target=/root/.cache/uv \ # Place executables in the environment at the front of the path ENV PATH="/app/.venv/bin:$PATH" - -ARG GID -RUN groupadd --system --gid ${GID} nonroot diff --git a/docker/Dockerfile_turbo_python_binary b/docker/Dockerfile_turbo_python_binary index d7bac92..5c9f2c9 100644 --- a/docker/Dockerfile_turbo_python_binary +++ b/docker/Dockerfile_turbo_python_binary @@ -8,10 +8,11 @@ ARG EXECUTABLE_NAME # Create a non-privileged user that the app will run under. # See https://docs.docker.com/go/dockerfile-user-best-practices/ -ARG GID=515 -ARG UID=999 +ARG GID=1000 +ARG UID=1000 # Setup a non-root user +RUN groupadd --system --gid ${GID} nonroot RUN useradd --system --gid ${GID} --uid ${UID} --create-home nonroot USER nonroot diff --git a/docker/Dockerfile_turbo_quic_binary b/docker/Dockerfile_turbo_quic_binary index bd346ec..cba2805 100644 --- a/docker/Dockerfile_turbo_quic_binary +++ b/docker/Dockerfile_turbo_quic_binary @@ -11,8 +11,8 @@ ARG QUIC_DOCKER_PORT # Create a non-privileged user that the app will run under. # See https://docs.docker.com/go/dockerfile-user-best-practices/ -ARG UID=10001 -ARG GID=999 +ARG UID=1000 +ARG GID=1000 RUN addgroup -g "${GID}" sharedgroup && \ adduser \ --disabled-password \ diff --git a/docker/README.Docker.md b/docker/README.Docker.md index 0141be5..13a2fdf 100644 --- a/docker/README.Docker.md +++ b/docker/README.Docker.md @@ -1,8 +1,141 @@ -# Docker Setup for TURBO +# Docker: Building from Source & Reference -This Docker configuration containerizes the TURBO research prototype for local development and experimentation. It is **not intended for production use**. +This directory contains everything needed to **build Docker images from source** — Dockerfiles, a build-oriented `compose.yaml`, and shared configuration files. It is intended for development and customization, **not** for running pre-built images. -For setup and running instructions, see the [Quick Start (Docker)](../README.md#quick-start-docker--recommended) section in the main README. This document covers additional details: services overview, configuration reference, development workflow, architecture notes, and troubleshooting. +- **To run TURBO using pre-built images** (recommended), use the root-level `compose.yaml` and `.env.example`. See the [Quick Start (Docker)](../README.md#quick-start-docker--recommended) section in the main README. +- **To build images from source**, use the `compose.yaml` and `.env.example` in this directory. See [Building from Source](#building-from-source) below. + +The `docker/config/` directory contains Docker-specific YAML configs shared by both workflows. + +## Building from Source + +If you want to build the Docker images locally instead of using the pre-built images (e.g., for development or customization), follow these steps. + +### Prerequisites + +- [Docker Engine](https://docs.docker.com/engine/install/) 24.0+ with [Docker Compose V2](https://docs.docker.com/compose/install/) +- [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) (for GPU inference) +- Linux (tested on Ubuntu 20.04+) + +Verify your setup: +```bash +docker compose version # should be v2.20+ +nvidia-smi # should show your GPU(s) +docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU in Docker +``` + +### Setup + +1. **Clone the repository:** + ```bash + git clone https://github.com/NetSys/turbo.git + cd turbo + ``` + +2. **Download fine-tuned EfficientDet model checkpoints (server only):** + + The system uses custom EfficientDet models (D1, D2, D4, D6, D7x) fine-tuned on the [Waymo Open Dataset](https://waymo.com/open/) for 5-class object detection (vehicle, pedestrian, cyclist, sign, unknown). + + Our fine-tuned models can be downloaded and extracted as follows: + + ```bash + # Download the model archive + wget https://storage.googleapis.com/turbo-nines-2026/av-models.zip + + # Extract to your home directory (creates ~/av-models/) + unzip av-models.zip -d ~ + ``` + + See [docs/MODELS.md](../docs/MODELS.md) for detailed model information. + + > **IMPORTANT — Waymo Open Dataset License Notice** + > + > The fine-tuned EfficientDet model weights provided above were developed using the [Waymo Open Dataset](https://waymo.com/open/) and are released under the [Waymo Dataset License Agreement for Non-Commercial Use](https://waymo.com/open/terms/). By downloading or using these model weights, you agree that: + > + > 1. These models are for **non-commercial use only**. Any use, modification, or redistribution is subject to the terms of the [Waymo Dataset License Agreement for Non-Commercial Use](https://waymo.com/open/terms/), including the non-commercial restrictions therein. + > 2. Any further downstream use or modification of these models is subject to the same agreement. + > 3. A statement of the applicable Waymo Dataset License terms is included in this repository at [WAYMO_LICENSE](../WAYMO_LICENSE). The full agreement is available at [waymo.com/open/terms](https://waymo.com/open/terms/). + > + > These models were made using the Waymo Open Dataset, provided by Waymo LLC. + +3. **Download pre-computed evaluation data (client only):** + + The client requires pre-computed full evaluation data for utility curve computation. Download and extract as follows: + + ```bash + # Download the evaluation data archive + wget https://storage.googleapis.com/turbo-nines-2026/full-eval.zip + + # Extract to your home directory (creates ~/full-eval/) + unzip full-eval.zip -d ~ + ``` + +4. **Generate SSL keys for QUIC:** + + The QUIC binaries embed SSL certificates at compile time via Rust's `include_str!()` macro (see [SSL Certificates](#ssl-certificates)). You must generate them before building: + + ```bash + cd src/quic + pip install cryptography # if not already installed + python generate_cert.py + cd ../.. + ``` + +5. **Configure the `.env` file:** + + ```bash + cp docker/.env.example docker/.env + ``` + + Edit `docker/.env` and update the following values to match your host system: + + | Variable | Description | Default | + |---|---|---| + | `HOST_UID` | Your host user ID (run `id -u`) | `1000` | + | `HOST_GID` | Your host group ID (run `id -g`) | `1000` | + | `EXPERIMENT_OUTPUT_DIR` | Absolute path for experiment output | (must set) | + | `EFFDET_MODELS_DIR` | Absolute path to model checkpoints (server) | (must set) | + | `MODEL_FULL_EVAL_DIR` | Absolute path to evaluation data (client) | (must set) | + + Most other settings (networking, ports, SSL paths) work out of the box for same-host testing. See [Additional Configuration](#additional-configuration) below for the full reference. + +6. **Create the experiment output directory:** + ```bash + mkdir -p ~/experiment2-out + ``` + +### Build and Run + +All Docker commands should be run from the `docker/` directory: +```bash +cd docker +``` + +**Build and run both client and server on the same host:** +```bash +docker compose --profile client --profile server up --build +``` + +**Build and run server only** (e.g., on a cloud GPU machine): +```bash +docker compose --profile server up --build +``` + +**Build and run client only** (when server is running elsewhere — update `QUIC_CLIENT_ADDR` in `.env` to the server's IP): +```bash +docker compose --profile client up --build +``` + +Once running, open the monitoring dashboard at **http://localhost:5000**. + +**Shut down:** +```bash +docker compose --profile client --profile server down -v +``` + +The `-v` flag removes ephemeral volumes (ZMQ sockets, health signals), giving you a clean slate for the next run. + +**Experiment output** will be logged to Parquet files in the configured output directory (default: `~/experiment2-out/`). ## Services Overview @@ -22,7 +155,7 @@ Services start in dependency order via health checks: `client_python_main` must ## Additional Configuration -The [Quick Start](../README.md#quick-start-docker--recommended) covers the required `.env` variables (`HOST_UID`, `HOST_GID`, `EXPERIMENT_OUTPUT_DIR`, `EFFDET_MODELS_DIR`, `MODEL_FULL_EVAL_DIR`). The following additional variables are also available: +The [Setup](#setup) section above covers the required `.env` variables (`HOST_UID`, `HOST_GID`, `EXPERIMENT_OUTPUT_DIR`, `EFFDET_MODELS_DIR`, `MODEL_FULL_EVAL_DIR`). The following additional variables are also available: **SSL (usually no changes needed):** @@ -63,7 +196,7 @@ The mock data files (`mock_webcam_image.jpg`, `example_effdet_d4_output.npy`) ar ### Additional running commands -Beyond the commands in the [Quick Start](../README.md#quick-start-docker--recommended), these are also useful: +Beyond the commands in [Build and Run](#build-and-run), these are also useful: **Build all images without starting** (useful after Dockerfile changes): ```bash diff --git a/docker/compose.yaml b/docker/compose.yaml index 0045233..1e7a01d 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -32,8 +32,6 @@ services: build: context: .. dockerfile: ./docker/Dockerfile_turbo_python_base - args: - GID: ${HOST_GID} develop: watch: - action: rebuild From e9fd8f33c8dfd789a2401d28b31d514477693d13 Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Sun, 1 Mar 2026 20:42:48 -0800 Subject: [PATCH 09/15] docs: update README with refinements for pre-reqs and setup steps --- README.md | 36 ++++++++++++++--------- docker/README.Docker.md | 63 +++++++---------------------------------- 2 files changed, 34 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 3480dad..a312bba 100644 --- a/README.md +++ b/README.md @@ -99,20 +99,32 @@ The recommended way to run TURBO is with Docker using pre-built container images ### Prerequisites -- [Docker Engine](https://docs.docker.com/engine/install/) 24.0+ with [Docker Compose V2](https://docs.docker.com/compose/install/) -- [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) (for GPU inference) +- [Docker Engine](https://docs.docker.com/engine/install/) 24.0+ with the [Docker Compose V2 plugin](https://docs.docker.com/compose/install/linux/) (see notes below). **Avoid Docker 28** — it seems to be incompatible with `nf_tables` for iptables, which can cause networking issues with container-to-container communication. Docker 27 is recommended. +- [NVIDIA GPU drivers](https://www.nvidia.com/en-us/drivers/) and [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) (**server only** — needed for GPU inference; not required on the client, or if using [mock inference mode](#mock-modes)) +- USB webcams (**client only** — or use [mock camera mode](#mock-modes) for testing without cameras) - Linux (tested on Ubuntu 20.04+) +- Disk space: + - **Client:** ~50 GB (7 GB for evaluation data; 10 GB for Docker images; ~30 GB recommended minimum for experiment output) + - **Server:** ~13 GB (2 GB for model checkpoints; 10 GB for Docker images; 1 GB recommended for experiment output) + +> **No GPU?** You can run the server side without a GPU by setting `MOCK_INFERENCE=true` in your `.env` file, which skips GPU model loading and returns pre-recorded detection results. See [Mock Modes](#mock-modes) for details. If using mock inference, you can skip the NVIDIA Container Toolkit prerequisite and the model checkpoint download (step 2). + +> **Docker Compose V2:** This project requires the Docker Compose V2 *plugin* (the `docker compose` subcommand), not the legacy standalone `docker-compose` binary. If `docker compose version` shows an error or is not found, install the plugin following the [official instructions](https://docs.docker.com/compose/install/linux/). On Ubuntu: `sudo apt-get install docker-compose-plugin`. Verify your setup: ```bash docker compose version # should be v2.20+ + +# Server only — skip these if running client only or using mock inference nvidia-smi # should show your GPU(s) docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU in Docker ``` ### Setup -1. **Clone the repository:** +> **Running on separate machines?** If you are running the client and server on different hosts, perform steps 1, 4, and 5 on **both** machines. Step 2 is server-only and step 3 is client-only. + +1. **Clone the repository (both client and server):** ```bash git clone https://github.com/NetSys/turbo.git cd turbo @@ -156,7 +168,7 @@ docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU unzip full-eval.zip -d ~ ``` -4. **Configure the `.env` file:** +4. **Configure the `.env` file (both client and server):** ```bash cp .env.example .env @@ -164,17 +176,17 @@ docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU Edit `.env` and update the following values to match your host system: - | Variable | Description | Default | + | Variable | Description | Quickstart value | |---|---|---| | `HOST_UID` | Your host user ID (run `id -u`) | `1000` | | `HOST_GID` | Your host group ID (run `id -g`) | `1000` | - | `EXPERIMENT_OUTPUT_DIR` | Absolute path for experiment output | (must set) | - | `EFFDET_MODELS_DIR` | Absolute path to model checkpoints (server) | (must set) | - | `MODEL_FULL_EVAL_DIR` | Absolute path to evaluation data (client) | (must set) | + | `EXPERIMENT_OUTPUT_DIR` | Absolute path for experiment output | `~/experiment2-out` | + | `EFFDET_MODELS_DIR` | Absolute path to model checkpoints (server) | `~/av-models` | + | `MODEL_FULL_EVAL_DIR` | Absolute path to evaluation data (client) | `~/full-eval` | - Most other settings (networking, ports) work out of the box for same-host testing. See [docker/README.Docker.md](docker/README.Docker.md#additional-configuration) for the full configuration reference. + If you followed the download steps above, set `EFFDET_MODELS_DIR=~/av-models` and `MODEL_FULL_EVAL_DIR=~/full-eval`. Most other settings (networking, ports) work out of the box for same-host testing. See [docker/README.Docker.md](docker/README.Docker.md#additional-configuration) for the full configuration reference. -5. **Create the experiment output directory:** +5. **Create the experiment output directory (both client and server):** ```bash mkdir -p ~/experiment2-out ``` @@ -201,7 +213,7 @@ docker compose --profile server up docker compose --profile client up ``` -Once running, open the monitoring dashboard at **http://localhost:5000**. +Once the client is running, open the monitoring dashboard at **http://localhost:5000**. **Shut down:** ```bash @@ -229,7 +241,6 @@ If you prefer to run each process directly on your host without Docker, follow t - Python 3.10; preferably managed via [uv](https://docs.astral.sh/uv/) (alternatively, via [Anaconda](https://anaconda.org/), specifically the [`Miniconda3-py310_25.11.1-1` release version on this page](https://repo.anaconda.com/miniconda/)) - Rust 1.70+ (for QUIC transport) - USB webcams (or video sources) -- Linux (tested on Ubuntu 20.04+) - Needed dependencies for `OpenCV` -- (e.g. `sudo apt-get update && sudo apt-get install ffmpeg libsm6 libxext6`) **Server (Cloud) side:** @@ -237,7 +248,6 @@ If you prefer to run each process directly on your host without Docker, follow t - CUDA-capable GPU (tested on H100, A100) - PyTorch 2.0+ - Rust 1.70+ (for QUIC transport) -- Fine-tuned EfficientDet model checkpoints (see [Model Setup](#model-setup) above) - Needed dependencies for `OpenCV` -- (e.g. `sudo apt-get update && sudo apt-get install ffmpeg libsm6 libxext6`) ### Installation diff --git a/docker/README.Docker.md b/docker/README.Docker.md index 13a2fdf..d3f85c2 100644 --- a/docker/README.Docker.md +++ b/docker/README.Docker.md @@ -14,63 +14,22 @@ If you want to build the Docker images locally instead of using the pre-built im ### Prerequisites - [Docker Engine](https://docs.docker.com/engine/install/) 24.0+ with [Docker Compose V2](https://docs.docker.com/compose/install/) -- [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) (for GPU inference) +- [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) (**server only** — needed for GPU inference; not required on the client, or if using [mock inference mode](../README.md#mock-modes)) +- Rust 1.70+ (for building QUIC transport binaries; install from [rustup.rs](https://rustup.rs/)) +- USB webcams (**client only** — or use [mock camera mode](../README.md#mock-modes) for testing without cameras) - Linux (tested on Ubuntu 20.04+) -Verify your setup: -```bash -docker compose version # should be v2.20+ -nvidia-smi # should show your GPU(s) -docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU in Docker -``` +See the main README's [Prerequisites](../README.md#prerequisites) section for Docker version warnings, disk space requirements, and setup verification commands. ### Setup -1. **Clone the repository:** - ```bash - git clone https://github.com/NetSys/turbo.git - cd turbo - ``` - -2. **Download fine-tuned EfficientDet model checkpoints (server only):** +> **Running on separate machines?** If you are running the client and server on different hosts, perform all steps on **both** machines. Within step 1, the model download is server-only and the eval data download is client-only. - The system uses custom EfficientDet models (D1, D2, D4, D6, D7x) fine-tuned on the [Waymo Open Dataset](https://waymo.com/open/) for 5-class object detection (vehicle, pedestrian, cyclist, sign, unknown). +1. **Complete shared setup steps:** - Our fine-tuned models can be downloaded and extracted as follows: - - ```bash - # Download the model archive - wget https://storage.googleapis.com/turbo-nines-2026/av-models.zip - - # Extract to your home directory (creates ~/av-models/) - unzip av-models.zip -d ~ - ``` - - See [docs/MODELS.md](../docs/MODELS.md) for detailed model information. - - > **IMPORTANT — Waymo Open Dataset License Notice** - > - > The fine-tuned EfficientDet model weights provided above were developed using the [Waymo Open Dataset](https://waymo.com/open/) and are released under the [Waymo Dataset License Agreement for Non-Commercial Use](https://waymo.com/open/terms/). By downloading or using these model weights, you agree that: - > - > 1. These models are for **non-commercial use only**. Any use, modification, or redistribution is subject to the terms of the [Waymo Dataset License Agreement for Non-Commercial Use](https://waymo.com/open/terms/), including the non-commercial restrictions therein. - > 2. Any further downstream use or modification of these models is subject to the same agreement. - > 3. A statement of the applicable Waymo Dataset License terms is included in this repository at [WAYMO_LICENSE](../WAYMO_LICENSE). The full agreement is available at [waymo.com/open/terms](https://waymo.com/open/terms/). - > - > These models were made using the Waymo Open Dataset, provided by Waymo LLC. - -3. **Download pre-computed evaluation data (client only):** - - The client requires pre-computed full evaluation data for utility curve computation. Download and extract as follows: - - ```bash - # Download the evaluation data archive - wget https://storage.googleapis.com/turbo-nines-2026/full-eval.zip - - # Extract to your home directory (creates ~/full-eval/) - unzip full-eval.zip -d ~ - ``` + Follow **steps 1–3** from the [Quick Start Setup](../README.md#setup) in the main README to clone the repository, download model checkpoints (server only), and download evaluation data (client only). -4. **Generate SSL keys for QUIC:** +2. **Generate SSL keys for QUIC (both client and server):** The QUIC binaries embed SSL certificates at compile time via Rust's `include_str!()` macro (see [SSL Certificates](#ssl-certificates)). You must generate them before building: @@ -81,7 +40,7 @@ docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU cd ../.. ``` -5. **Configure the `.env` file:** +3. **Configure the `.env` file (both client and server):** ```bash cp docker/.env.example docker/.env @@ -99,7 +58,7 @@ docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU Most other settings (networking, ports, SSL paths) work out of the box for same-host testing. See [Additional Configuration](#additional-configuration) below for the full reference. -6. **Create the experiment output directory:** +4. **Create the experiment output directory (both client and server):** ```bash mkdir -p ~/experiment2-out ``` @@ -126,7 +85,7 @@ docker compose --profile server up --build docker compose --profile client up --build ``` -Once running, open the monitoring dashboard at **http://localhost:5000**. +Once the client is running, open the monitoring dashboard at **http://localhost:5000**. **Shut down:** ```bash From 5fdf4a83b0917c733026987bea53e673f2b42807 Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Sun, 1 Mar 2026 22:44:17 -0800 Subject: [PATCH 10/15] feat: split gpu docker compose into separate declaration; update docker requirements --- .env.example | 4 ++-- README.md | 35 +++++++++++++++++++++-------------- compose.gpu.yaml | 11 +++++++++++ compose.yaml | 14 -------------- docker/README.Docker.md | 14 ++++++++++---- docker/compose.gpu.yaml | 11 +++++++++++ docker/compose.yaml | 14 -------------- 7 files changed, 55 insertions(+), 48 deletions(-) create mode 100644 compose.gpu.yaml create mode 100644 docker/compose.gpu.yaml diff --git a/.env.example b/.env.example index 217ce54..1714fc1 100644 --- a/.env.example +++ b/.env.example @@ -30,11 +30,11 @@ PYTHON_DOCKER_SERVER_CONFIG_PATH=./docker/config/server_config_gcloud_docker.yam DOCKER_LOG_CONFIG_PATH=./docker/config/logging_config_docker.yaml EXPERIMENT_OUTPUT_DIR=/path/to/experiment2-out -# client-side only +# client-side only (required even in mock camera mode — bandwidth allocator needs eval data) DASHBOARD_PORT=5000 MODEL_FULL_EVAL_DIR=/path/to/full-eval -# server-side only +# server-side only (not needed in mock inference mode, but must point to a valid directory — an empty one is fine) EFFDET_MODELS_DIR=/path/to/av-models # rust env vars diff --git a/README.md b/README.md index a312bba..b157a09 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,10 @@ The recommended way to run TURBO is with Docker using pre-built container images - USB webcams (**client only** — or use [mock camera mode](#mock-modes) for testing without cameras) - Linux (tested on Ubuntu 20.04+) - Disk space: - - **Client:** ~50 GB (7 GB for evaluation data; 10 GB for Docker images; ~30 GB recommended minimum for experiment output) - - **Server:** ~13 GB (2 GB for model checkpoints; 10 GB for Docker images; 1 GB recommended for experiment output) + - **Client:** ~50 GB (7 GB for evaluation data; 10 GB for Docker images; ~30 GB recommended minimum for experiment output). Note: evaluation data is required even in [mock camera mode](#mock-modes) — the bandwidth allocator always needs it for utility curve computation. + - **Server:** ~13 GB (2 GB for model checkpoints; 10 GB for Docker images; 1 GB recommended for experiment output). With [mock inference mode](#mock-modes), model checkpoints are not needed, reducing this to ~11 GB. -> **No GPU?** You can run the server side without a GPU by setting `MOCK_INFERENCE=true` in your `.env` file, which skips GPU model loading and returns pre-recorded detection results. See [Mock Modes](#mock-modes) for details. If using mock inference, you can skip the NVIDIA Container Toolkit prerequisite and the model checkpoint download (step 2). +> **No GPU?** You can run the server side without a GPU by setting `MOCK_INFERENCE=true` in your `.env` file and omitting the `-f compose.gpu.yaml` override. This skips GPU model loading and returns pre-recorded detection results. See [Mock Modes](#mock-modes) for details. If using mock inference, you can skip the NVIDIA Container Toolkit prerequisite and the model checkpoint download (step 2), but you must still set `EFFDET_MODELS_DIR` to a valid (possibly empty) directory since Docker bind-mounts it unconditionally — e.g., `mkdir -p ~/av-models`. > **Docker Compose V2:** This project requires the Docker Compose V2 *plugin* (the `docker compose` subcommand), not the legacy standalone `docker-compose` binary. If `docker compose version` shows an error or is not found, install the plugin following the [official instructions](https://docs.docker.com/compose/install/linux/). On Ubuntu: `sudo apt-get install docker-compose-plugin`. @@ -198,14 +198,16 @@ docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU docker compose --profile client --profile server pull ``` -**Run both client and server on the same host:** +**GPU setup:** If the server host has NVIDIA GPUs (required for real inference, not needed for [mock inference](#mock-modes)), include the GPU override file by adding `-f compose.gpu.yaml` to all `docker compose` commands. Non-GPU hosts can omit it. + +**Run both client and server on the same host (with GPU):** ```bash -docker compose --profile client --profile server up +docker compose -f compose.yaml -f compose.gpu.yaml --profile client --profile server up ``` **Run server only** (e.g., on a cloud GPU machine): ```bash -docker compose --profile server up +docker compose -f compose.yaml -f compose.gpu.yaml --profile server up ``` **Run client only** (when server is running elsewhere — update `QUIC_CLIENT_ADDR` in `.env` to the server's IP): @@ -213,6 +215,11 @@ docker compose --profile server up docker compose --profile client up ``` +**Run server with mock inference (no GPU needed):** +```bash +docker compose --profile server up +``` + Once the client is running, open the monitoring dashboard at **http://localhost:5000**. **Shut down:** @@ -396,7 +403,7 @@ TURBO supports two independent mock modes for testing and development without re ### Mock Camera Mode (Client-Side) -Replaces live USB camera capture with a static image. Pass `--mock-camera` to `client_main.py` to enable. +Replaces live USB camera capture with a static image. Pass `--mock-camera` to `client_main.py` to enable. Note that the `full-eval` evaluation data is still required — the bandwidth allocator uses it for utility curve computation regardless of camera mode. ```bash # Manual setup @@ -407,7 +414,7 @@ The image path is configured per camera in `camera_stream_config_list` via the ` ### Mock Inference Mode (Server-Side) -Skips GPU model loading and returns pre-recorded detection results. Pass `--mock-inference` to `server_main.py` to enable. Optionally simulates per-model inference latency using a CSV of benchmark timings. +Skips GPU model loading and returns pre-recorded detection results. Pass `--mock-inference` to `server_main.py` to enable. Optionally simulates per-model inference latency using a CSV of benchmark timings. The `av-models` model checkpoints are **not needed** in this mode (model loading is completely bypassed), though the Docker bind mount for `EFFDET_MODELS_DIR` still requires an existing directory — an empty one is fine. ```bash # Manual setup @@ -432,12 +439,12 @@ See [docker/README.Docker.md](docker/README.Docker.md#mock-modes) for details. The two mock modes are fully independent — you can use any combination: -| Camera Mock | Inference Mock | Use Case | -|---|---|---| -| Off | Off | **Production** — real cameras, real GPU inference | -| On | Off | Test the full pipeline without cameras (still needs GPU) | -| Off | On | Test camera capture and transport without GPU | -| On | On | **Full mock** — test the entire system without cameras or GPU | +| Camera Mock | Inference Mock | Use Case | Data Required | +|---|---|---|---| +| Off | Off | **Production** — real cameras, real GPU inference | `full-eval` + `av-models` | +| On | Off | Test the full pipeline without cameras (still needs GPU) | `full-eval` + `av-models` | +| Off | On | Test camera capture and transport without GPU | `full-eval` only | +| On | On | **Full mock** — test the entire system without cameras or GPU | `full-eval` only | ## Documentation diff --git a/compose.gpu.yaml b/compose.gpu.yaml new file mode 100644 index 0000000..4724c1c --- /dev/null +++ b/compose.gpu.yaml @@ -0,0 +1,11 @@ +# GPU override — include this file on hosts with NVIDIA GPUs. +# Usage: docker compose -f compose.yaml -f compose.gpu.yaml --profile server up +services: + server_python_main: + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] diff --git a/compose.yaml b/compose.yaml index cbe6138..0e3ed41 100644 --- a/compose.yaml +++ b/compose.yaml @@ -54,13 +54,6 @@ services: stop_grace_period: 30s user: "${HOST_UID}:${HOST_GID}" image: ${DOCKERHUB_USERNAME}/turbo-python-client:${IMAGE_TAG:-latest} - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: all - capabilities: [gpu] volumes: - health_signals:/health - type: bind @@ -103,13 +96,6 @@ services: stop_grace_period: 30s user: "${HOST_UID}:${HOST_GID}" image: ${DOCKERHUB_USERNAME}/turbo-python-server:${IMAGE_TAG:-latest} - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: all - capabilities: [gpu] volumes: - type: bind source: ${PYTHON_DOCKER_SERVER_CONFIG_PATH} diff --git a/docker/README.Docker.md b/docker/README.Docker.md index d3f85c2..d966f4e 100644 --- a/docker/README.Docker.md +++ b/docker/README.Docker.md @@ -15,7 +15,6 @@ If you want to build the Docker images locally instead of using the pre-built im - [Docker Engine](https://docs.docker.com/engine/install/) 24.0+ with [Docker Compose V2](https://docs.docker.com/compose/install/) - [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) (**server only** — needed for GPU inference; not required on the client, or if using [mock inference mode](../README.md#mock-modes)) -- Rust 1.70+ (for building QUIC transport binaries; install from [rustup.rs](https://rustup.rs/)) - USB webcams (**client only** — or use [mock camera mode](../README.md#mock-modes) for testing without cameras) - Linux (tested on Ubuntu 20.04+) @@ -70,14 +69,16 @@ All Docker commands should be run from the `docker/` directory: cd docker ``` -**Build and run both client and server on the same host:** +**GPU setup:** If the server host has NVIDIA GPUs (required for real inference, not needed for [mock inference](../README.md#mock-modes)), include the GPU override file by adding `-f compose.gpu.yaml` to all `docker compose` commands. Non-GPU hosts can omit it. + +**Build and run both client and server on the same host (with GPU):** ```bash -docker compose --profile client --profile server up --build +docker compose -f compose.yaml -f compose.gpu.yaml --profile client --profile server up --build ``` **Build and run server only** (e.g., on a cloud GPU machine): ```bash -docker compose --profile server up --build +docker compose -f compose.yaml -f compose.gpu.yaml --profile server up --build ``` **Build and run client only** (when server is running elsewhere — update `QUIC_CLIENT_ADDR` in `.env` to the server's IP): @@ -85,6 +86,11 @@ docker compose --profile server up --build docker compose --profile client up --build ``` +**Build and run server with mock inference (no GPU needed):** +```bash +docker compose --profile server up --build +``` + Once the client is running, open the monitoring dashboard at **http://localhost:5000**. **Shut down:** diff --git a/docker/compose.gpu.yaml b/docker/compose.gpu.yaml new file mode 100644 index 0000000..d82cdd0 --- /dev/null +++ b/docker/compose.gpu.yaml @@ -0,0 +1,11 @@ +# GPU override — include this file on hosts with NVIDIA GPUs. +# Usage (from docker/ directory): docker compose -f compose.yaml -f compose.gpu.yaml --profile server up --build +services: + server_python_main: + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] diff --git a/docker/compose.yaml b/docker/compose.yaml index 1e7a01d..5ce63a4 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -123,13 +123,6 @@ services: init: true ipc: host stop_grace_period: 30s - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: all - capabilities: [gpu] build: context: .. target: final @@ -180,13 +173,6 @@ services: init: true ipc: host stop_grace_period: 30s - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: all - capabilities: [gpu] build: context: .. target: final From 859bdc949cf16a1d0979c6fe72524344e0b10249 Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Mon, 2 Mar 2026 00:53:02 -0800 Subject: [PATCH 11/15] docs: update config info; add troubleshooting info --- README.md | 4 ++++ docker/README.Docker.md | 53 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b157a09..758cc6c 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,10 @@ docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU If you followed the download steps above, set `EFFDET_MODELS_DIR=~/av-models` and `MODEL_FULL_EVAL_DIR=~/full-eval`. Most other settings (networking, ports) work out of the box for same-host testing. See [docker/README.Docker.md](docker/README.Docker.md#additional-configuration) for the full configuration reference. + > **Hardware-specific YAML config changes:** Depending on your hardware, you may also need to edit the Docker YAML config files in [`docker/config/`](docker/config/): + > - **GPU device assignment (server):** [`docker/config/server_config_gcloud_docker.yaml`](docker/config/server_config_gcloud_docker.yaml) assigns each of the 3 model services to a separate GPU (`cuda:0`, `cuda:1`, `cuda:2`). If you have fewer than 3 GPUs, update the `device` fields to match your setup (e.g., set all to `"cuda:0"` for a single-GPU machine). + > - **USB camera IDs (client):** [`docker/config/client_config_docker.yaml`](docker/config/client_config_docker.yaml) maps cameras to USB device IDs (`usb_id: 0`, `4`, `8`). If using real cameras (not mock mode), update these to match your system's device IDs. + 5. **Create the experiment output directory (both client and server):** ```bash mkdir -p ~/experiment2-out diff --git a/docker/README.Docker.md b/docker/README.Docker.md index d966f4e..3a7c85b 100644 --- a/docker/README.Docker.md +++ b/docker/README.Docker.md @@ -144,7 +144,14 @@ When running client and server on the **same host**, the default `QUIC_CLIENT_AD The `docker/config/` directory contains YAML config files that mirror the main `config/` files but with container-internal paths (e.g. `/app/experiment2-out` instead of `~/experiment2-out`). These are bind-mounted into each container at runtime. -You generally don't need to edit these unless you're changing service behavior (e.g. number of cameras, model variants, SLO timeouts). If you do, edit the files in `docker/config/` — not the ones in the repo root `config/` directory. +Most settings work out of the box, but the following **hardware-specific values** may need to be updated to match your setup: + +| File | Setting | Default | When to change | +|---|---|---|---| +| `server_config_gcloud_docker.yaml` | `device` per service | `cuda:0`, `cuda:1`, `cuda:2` | If you have fewer than 3 GPUs — e.g., set all to `"cuda:0"` for a single-GPU machine | +| `client_config_docker.yaml` | `usb_id` per camera | `0`, `4`, `8` | If using real cameras (not mock mode) — update to match your system's USB device IDs | + +For other changes (e.g. number of cameras, model variants, SLO timeouts), edit the files in `docker/config/` — not the ones in the repo root `config/` directory. ### Mock Modes @@ -273,6 +280,19 @@ docker compose --profile client --profile server down docker compose --profile client --profile server up ``` +**Network subnet overlap ("Pool overlaps with other one on this address space"):** +Both `compose.yaml` (pre-built) and `docker/compose.yaml` (build-from-source) define `quic_net` with the same `10.64.89.0/24` subnet. If you previously ran one workflow and then switch to the other without cleaning up, the old network still exists under a different project name (e.g. `docker_quic_net` vs `turbo_quic_net`), and Docker refuses to create a second network with the same subnet. Remove the stale network: +```bash +# List networks to find the conflicting one +docker network ls + +# Remove the stale network (e.g. from a previous build-from-source run) +docker network rm docker_quic_net docker_default + +# Or from a previous pre-built run +docker network rm turbo_quic_net turbo_default +``` + **Stale POSIX shared memory after ungraceful shutdown:** Because all services use `ipc: host`, POSIX shared memory segments live in the host's `/dev/shm` and survive container restarts. If a container is force-killed (SIGKILL, OOM, Docker timeout, power loss) before cleanup runs, stale segments remain and cause `FileExistsError: [Errno 17] File exists` on the next startup. To clean them up: ```bash @@ -292,3 +312,34 @@ ZMQ socket directories use tmpfs volumes that start empty on every `docker compo ```bash docker compose --profile client --profile server down -v ``` + +**QUIC handshake timeout (`MaxHandshakeDurationExceeded`) on same-host deployment:** +If the QUIC client fails with `MaxHandshakeDurationExceeded` when running client and server on the same host, the most likely cause is **orphaned Docker bridge interfaces** that conflict with the `quic_net` subnet (`10.64.89.0/24`). This happens when Docker fails to clean up Linux bridge interfaces after removing networks — for example, after a `docker compose down`, a Docker daemon restart, or a system reboot. The orphaned bridge retains the same IP (`10.64.89.1/24`) as the new `quic_net` bridge, creating duplicate routes in the kernel. Packets from the QUIC client to the gateway (`10.64.89.1`) get routed to the dead orphan bridge instead of the active one, so they never reach the host and the server never sees the connection attempt. + +To diagnose, check if containers on `quic_net` can reach the gateway at all: +```bash +docker run --rm --network _quic_net alpine:3.18 ping -c2 -W2 10.64.89.1 +``` +If this shows 100% packet loss, check for orphaned bridge interfaces: +```bash +# List all bridge interfaces on the system +ip -br link show type bridge + +# Compare against active Docker networks +docker network ls + +# Bridge names use the format br-. +# Any bridge that doesn't match an active Docker network ID is orphaned. +``` + +To fix, remove the orphaned bridges and restart the compose stack: +```bash +# Remove each orphaned bridge (example — use the actual interface names from the step above) +sudo ip link delete br-XXXXXXXXXXXX + +# Restart +docker compose down +docker compose --profile client --profile server up +``` + +To prevent this, always shut down cleanly with `docker compose down` before restarting Docker or the host. If orphaned bridges recur, a Docker daemon restart (`sudo systemctl restart docker`) followed by removing any remaining orphans is the most reliable fix. From 62b8fe7f078e3aa7737e9de1d0d27838415b4277 Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Mon, 2 Mar 2026 01:55:25 -0800 Subject: [PATCH 12/15] refactor: rename some .env ip vars --- .env.example | 3 +-- README.md | 2 +- compose.yaml | 3 ++- docker/.env.example | 3 +-- docker/Dockerfile_turbo_quic_binary | 4 ---- docker/README.Docker.md | 12 ++++++------ docker/compose.yaml | 6 ++---- 7 files changed, 13 insertions(+), 20 deletions(-) diff --git a/.env.example b/.env.example index 1714fc1..b87f06b 100644 --- a/.env.example +++ b/.env.example @@ -9,8 +9,7 @@ HOST_GID=1000 # quic client-side only # Same-host: use Docker bridge gateway (10.64.89.1). Remote: use server host's routable IP. -QUIC_CLIENT_ADDR=10.64.89.1:12345 -QUIC_CLIENT_PORT=5000 +QUIC_CLIENT_REMOTE_ADDR=10.64.89.1:12345 QUIC_DOCKER_CLIENT_CONFIG_PATH=./docker/config/quic_config_client_docker.yaml diff --git a/README.md b/README.md index 758cc6c..853a2db 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ docker compose -f compose.yaml -f compose.gpu.yaml --profile client --profile se docker compose -f compose.yaml -f compose.gpu.yaml --profile server up ``` -**Run client only** (when server is running elsewhere — update `QUIC_CLIENT_ADDR` in `.env` to the server's IP): +**Run client only** (when server is running elsewhere — update `QUIC_CLIENT_REMOTE_ADDR` in `.env` to the server's IP): ```bash docker compose --profile client up ``` diff --git a/compose.yaml b/compose.yaml index 0e3ed41..1cd82e9 100644 --- a/compose.yaml +++ b/compose.yaml @@ -12,6 +12,7 @@ services: environment: RUST_LOG: ${RUST_LOG} RUST_BACKTRACE: ${RUST_BACKTRACE} + QUIC_BIND_ADDR: ${QUIC_CLIENT_REMOTE_ADDR} volumes: - type: bind source: ${QUIC_DOCKER_CLIENT_CONFIG_PATH} @@ -81,7 +82,7 @@ services: target: /app/mock_webcam_image.jpg read_only: true command: > - bash -c 'cd src/python && exec python client_main.py -c /app/python_config.yaml -s ${QUIC_CLIENT_ADDR} ${MOCK_CAMERA:+--mock-camera}' + bash -c 'cd src/python && exec python client_main.py -c /app/python_config.yaml -s ${QUIC_CLIENT_REMOTE_ADDR} ${MOCK_CAMERA:+--mock-camera}' healthcheck: test: ["CMD", "test", "-f", "/health/client_main_ready"] interval: 2s diff --git a/docker/.env.example b/docker/.env.example index a0613b5..2c61d60 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -5,8 +5,7 @@ HOST_GID=1000 # quic client-side only # Same-host: use Docker bridge gateway (10.64.89.1). Remote: use server host's routable IP. -QUIC_CLIENT_ADDR=10.64.89.1:12345 -QUIC_CLIENT_PORT=5000 +QUIC_CLIENT_REMOTE_ADDR=10.64.89.1:12345 QUIC_DOCKER_CLIENT_CONFIG_PATH=./config/quic_config_client_docker.yaml diff --git a/docker/Dockerfile_turbo_quic_binary b/docker/Dockerfile_turbo_quic_binary index cba2805..5fd7963 100644 --- a/docker/Dockerfile_turbo_quic_binary +++ b/docker/Dockerfile_turbo_quic_binary @@ -7,7 +7,6 @@ WORKDIR /app FROM alpine:3.18 AS final ARG APP_NAME ARG QUIC_BIND_ADDR -ARG QUIC_DOCKER_PORT # Create a non-privileged user that the app will run under. # See https://docs.docker.com/go/dockerfile-user-best-practices/ @@ -28,9 +27,6 @@ USER appuser # Copy the executable from the "build" stage. COPY --from=base_image /bin/$APP_NAME /bin/server -# Expose the port that the application listens on. -EXPOSE ${QUIC_DOCKER_PORT} - ENV QUIC_BIND_ADDR=${QUIC_BIND_ADDR} # What the container should run when it is started. diff --git a/docker/README.Docker.md b/docker/README.Docker.md index 3a7c85b..a5182f4 100644 --- a/docker/README.Docker.md +++ b/docker/README.Docker.md @@ -81,7 +81,7 @@ docker compose -f compose.yaml -f compose.gpu.yaml --profile client --profile se docker compose -f compose.yaml -f compose.gpu.yaml --profile server up --build ``` -**Build and run client only** (when server is running elsewhere — update `QUIC_CLIENT_ADDR` in `.env` to the server's IP): +**Build and run client only** (when server is running elsewhere — update `QUIC_CLIENT_REMOTE_ADDR` in `.env` to the server's IP): ```bash docker compose --profile client up --build ``` @@ -133,12 +133,12 @@ The [Setup](#setup) section above covers the required `.env` variables (`HOST_UI | Variable | Description | Default | |---|---|---| -| `QUIC_CLIENT_ADDR` | Address the QUIC client connects to | `10.64.89.1:12345` (Docker bridge gateway) | +| `QUIC_CLIENT_REMOTE_ADDR` | Address the QUIC client connects to | `10.64.89.1:12345` (Docker bridge gateway) | | `QUIC_SERVER_ADDR` | Address the QUIC server binds to | `0.0.0.0:12345` | | `QUIC_SERVER_PORT` | UDP port exposed for QUIC | `12345` | | `DASHBOARD_PORT` | Host port for the web dashboard | `5000` | -When running client and server on the **same host**, the default `QUIC_CLIENT_ADDR` of `10.64.89.1:12345` routes through the Docker bridge gateway to reach the server container. When running on **separate hosts**, set `QUIC_CLIENT_ADDR` to the server machine's routable IP and port. +When running client and server on the **same host**, the default `QUIC_CLIENT_REMOTE_ADDR` of `10.64.89.1:12345` routes through the Docker bridge gateway to reach the server container. When running on **separate hosts**, set `QUIC_CLIENT_REMOTE_ADDR` to the server machine's routable IP and port. ### Docker-specific config files @@ -204,9 +204,9 @@ docker compose --profile client --profile server watch - **tmpfs volumes** — ZeroMQ socket directories and health signal files use tmpfs-backed volumes for fast, ephemeral storage. - **Signal handling** — All services use `init: true` (tini) as PID 1 for proper signal forwarding and graceful shutdown. A 30-second grace period (`stop_grace_period`) is configured for each service. When you press Ctrl+C on `docker compose up`, Docker Compose sends **SIGTERM** (not SIGINT) to each container. Without `init: true`, the application would be PID 1, and the Linux kernel silently drops signals with default handlers for PID 1 — causing the process to ignore SIGTERM and get SIGKILL'd after the grace period. The Python orchestrators (`client_main.py`, `server_main.py`) explicitly handle both SIGTERM and SIGINT to trigger graceful shutdown (ZMQ kill-switch broadcast, shared memory unlink, Parquet flush). - **`exec` and `python` in Dockerfiles** — The Python Dockerfile uses `exec python` directly instead of `uv run`. `uv run` spawns Python as a child process and may not forward signals, which would prevent graceful shutdown. The venv is already on `PATH` (set in `Dockerfile_turbo_python_base`), so calling `python` directly works. The `exec` replaces the shell with the actual process, avoiding a redundant `/bin/sh` parent (optional with `init: true`, but good practice). -- **Custom network** — A bridge network (`quic_net`, subnet `10.64.89.0/24`, gateway `10.64.89.1`) is used for QUIC communication. On same-host deployments, the QUIC client reaches the server through the bridge gateway (which routes to the host, where the server's UDP port is published). On separate-host deployments, `QUIC_CLIENT_ADDR` is set to the server machine's routable IP instead. +- **Custom network** — A bridge network (`quic_net`, subnet `10.64.89.0/24`, gateway `10.64.89.1`) is used for QUIC communication. On same-host deployments, the QUIC client reaches the server through the bridge gateway (which routes to the host, where the server's UDP port is published). On separate-host deployments, `QUIC_CLIENT_REMOTE_ADDR` is set to the server machine's routable IP instead. - **QUIC uses UDP** — The server's port is published with the `/udp` protocol. Firewalls on the server host must allow inbound UDP on this port. -- **Rust QUIC client requires IP addresses** — The Rust QUIC client parses addresses with `SocketAddr` and cannot resolve DNS hostnames. `QUIC_CLIENT_ADDR` must always be an `ip:port` pair (e.g. `10.64.89.1:12345`), not a hostname. +- **Rust QUIC client requires IP addresses** — The Rust QUIC client parses addresses with `SocketAddr` and cannot resolve DNS hostnames. `QUIC_CLIENT_REMOTE_ADDR` must always be an `ip:port` pair (e.g. `10.64.89.1:12345`), not a hostname. - **Health signal synchronization** — `client_python_main` writes its health signal (`/health/client_main_ready`) only after all Client subprocesses have bound their `quic_rcv_zmq_socket`. This ensures the Rust QUIC client (which depends on this health signal via `client_python_monitor`) does not start until the Python ZMQ sockets are ready to accept connections. A `multiprocessing.Manager().Queue()` is used for this cross-process synchronization. ## Path Relativity Rules @@ -245,7 +245,7 @@ docker compose --profile client logs client_python_main ``` **QUIC connection failures between client and server on separate hosts:** -Make sure `QUIC_CLIENT_ADDR` in `.env` is set to the server host's routable IP (not the Docker gateway), and that the `QUIC_SERVER_PORT` UDP port is open on the server host's firewall. +Make sure `QUIC_CLIENT_REMOTE_ADDR` in `.env` is set to the server host's routable IP (not the Docker gateway), and that the `QUIC_SERVER_PORT` UDP port is open on the server host's firewall. **Bind mount shows "IsADirectoryError" or creates an unexpected directory:** When Docker bind-mounts a file but the source path doesn't exist on the host, Docker silently creates a **directory** at the target path instead of failing. This causes confusing errors like `IsADirectoryError: [Errno 21] Is a directory: '/app/python_config.yaml'`. Double-check that the source path in `.env` is correct and that the file exists. Remember that volume source paths are relative to the compose file location — see [Path Relativity Rules](#path-relativity-rules). After fixing the path, you must rebuild with `--build` since the stale directory may be cached in the image layer: diff --git a/docker/compose.yaml b/docker/compose.yaml index 5ce63a4..fb075f9 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -64,8 +64,7 @@ services: dockerfile: ./docker/Dockerfile_turbo_quic_binary args: APP_NAME: "client" - QUIC_BIND_ADDR: ${QUIC_CLIENT_ADDR} - QUIC_DOCKER_PORT: ${QUIC_CLIENT_PORT} + QUIC_BIND_ADDR: ${QUIC_CLIENT_REMOTE_ADDR} GID: ${HOST_GID} UID: ${HOST_UID} additional_contexts: @@ -98,7 +97,6 @@ services: args: APP_NAME: "server" QUIC_BIND_ADDR: ${QUIC_SERVER_ADDR} - QUIC_DOCKER_PORT: ${QUIC_SERVER_PORT} GID: ${HOST_GID} UID: ${HOST_UID} additional_contexts: @@ -161,7 +159,7 @@ services: target: /app/mock_webcam_image.jpg read_only: true command: > - bash -c 'cd src/python && exec python client_main.py -c /app/python_config.yaml -s ${QUIC_CLIENT_ADDR} ${MOCK_CAMERA:+--mock-camera}' + bash -c 'cd src/python && exec python client_main.py -c /app/python_config.yaml -s ${QUIC_CLIENT_REMOTE_ADDR} ${MOCK_CAMERA:+--mock-camera}' healthcheck: test: ["CMD", "test", "-f", "/health/client_main_ready"] interval: 2s From fdc686ac8dafda7374b2367f0fe2ee5ed77259b3 Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Mon, 2 Mar 2026 02:36:04 -0800 Subject: [PATCH 13/15] docs: add troubleshooting info when connecting docker client network to external ip --- docker/README.Docker.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docker/README.Docker.md b/docker/README.Docker.md index a5182f4..8755ad3 100644 --- a/docker/README.Docker.md +++ b/docker/README.Docker.md @@ -343,3 +343,40 @@ docker compose --profile client --profile server up ``` To prevent this, always shut down cleanly with `docker compose down` before restarting Docker or the host. If orphaned bridges recur, a Docker daemon restart (`sudo systemctl restart docker`) followed by removing any remaining orphans is the most reliable fix. + +**QUIC handshake timeout (`MaxHandshakeDurationExceeded`) on separate-host deployment — stale nftables rules:** +If the QUIC client container can't reach an external server IP (but the host can), the cause is likely **stale nftables/iptables rules** referencing old Docker bridge interfaces. Docker generates firewall rules (FORWARD, MASQUERADE, etc.) that reference bridge interfaces by name (e.g. `br-a1b2c3d4e5f6`). When Docker networks are recreated (e.g. after `docker compose down` + `up`, or a Docker restart), the new network may get a different bridge name, but the old nftables rules persist — referencing bridges that no longer exist. Since the FORWARD chain has `policy drop`, traffic from the new bridge is silently dropped and never reaches the external network. + +To diagnose, confirm the packet is being dropped inside Docker networking: +```bash +# Send a UDP packet from inside a container on quic_net +docker run --rm --network _quic_net alpine:3.18 \ + sh -c 'echo test | nc -u -w1 ' + +# Meanwhile, on the server host, check if the packet arrives +sudo tcpdump -i any udp port +``` +If the packet never arrives at the server (but a similar test from the host works), check the nftables rules: +```bash +sudo nft list ruleset +``` +Look at the `DOCKER-FORWARD` chain in `table ip filter` — the `iifname` accept rules and `MASQUERADE` rules should reference bridge interfaces that actually exist (`ip link show type bridge`). If they reference bridges that don't exist, the rules are stale. + +To fix, flush the stale nftables rules and let Docker regenerate them: +```bash +# Stop all compose projects +docker compose --profile client --profile server down + +# Remove unused networks +docker network prune -f + +# Flush ALL nftables rules (Docker will regenerate its own on restart) +sudo nft flush ruleset + +# Restart Docker to regenerate clean rules +sudo systemctl restart docker + +# Bring the stack back up +docker compose --profile client up -d +``` +The key step is `nft flush ruleset` — without it, `systemctl restart docker` may not replace the stale rules. After the restart, verify that `sudo nft list chain ip filter DOCKER-FORWARD` references bridges matching the output of `ip link show type bridge`. From 86b6c04f1618ad2bd065a2e183581fe68995bcea Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Mon, 2 Mar 2026 13:03:24 -0800 Subject: [PATCH 14/15] docs: update mock mode docs to clarify usage --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 853a2db..346cde3 100644 --- a/README.md +++ b/README.md @@ -403,33 +403,36 @@ If you prefer to run each process directly on your host without Docker, follow t ## Mock Modes -TURBO supports two independent mock modes for testing and development without requiring physical cameras or GPUs. Mock modes are enabled via **CLI flags** on `client_main.py` and `server_main.py`. The YAML config files specify which mock files to use; the CLI flag controls whether they are actually applied. +TURBO supports two independent mock modes for testing and development without requiring physical cameras or GPUs: -### Mock Camera Mode (Client-Side) +- **Mock Camera** (client-side): Replaces live USB camera capture with a static image. Note that the `full-eval` evaluation data is still required — the bandwidth allocator uses it for utility curve computation regardless of camera mode. +- **Mock Inference** (server-side): Skips GPU model loading and returns pre-recorded detection results. Optionally simulates per-model inference latency using a CSV of benchmark timings. The `av-models` model checkpoints are **not needed** in this mode (model loading is completely bypassed), though the Docker bind mount for `EFFDET_MODELS_DIR` still requires an existing directory — an empty one is fine. -Replaces live USB camera capture with a static image. Pass `--mock-camera` to `client_main.py` to enable. Note that the `full-eval` evaluation data is still required — the bandwidth allocator uses it for utility curve computation regardless of camera mode. +### Enabling Mock Modes -```bash -# Manual setup -uv run client_main.py -c ../../config/client_config.yaml -s --mock-camera -``` +Follow the instructions for whichever setup method you used — [Quick Start (Docker)](#quick-start-docker--recommended), [Building from Source](docker/README.Docker.md#building-from-source), or [Manual Setup](#alternative-manual-setup-without-docker). -The image path is configured per camera in `camera_stream_config_list` via the `mock_camera_image_path` key. A sample mock image is included at `src/python/camera_stream/mock_webcam_image.jpg`. Without `--mock-camera`, this path is ignored and real USB cameras are used. +#### Docker with Pre-Built Images (Recommended) -### Mock Inference Mode (Server-Side) +Set environment variables in `.env` (at the repo root): -Skips GPU model loading and returns pre-recorded detection results. Pass `--mock-inference` to `server_main.py` to enable. Optionally simulates per-model inference latency using a CSV of benchmark timings. The `av-models` model checkpoints are **not needed** in this mode (model loading is completely bypassed), though the Docker bind mount for `EFFDET_MODELS_DIR` still requires an existing directory — an empty one is fine. +```bash +# Set to any non-empty value (e.g. "true") to enable, leave empty to disable +MOCK_CAMERA=true +MOCK_INFERENCE=true +``` + +Then run as usual — for example, to run both client and server in full mock mode (no cameras, no GPU): ```bash -# Manual setup -uv run server_main.py -c ../../config/server_config_gcloud.yaml --mock-inference +docker compose --profile client --profile server up ``` -The mock files are configured per server in `server_config_list` via `mock_inference_output_path` (numpy array of detections) and `mock_model_latency_csv_path` (per-model latency benchmarks). A sample mock output is included at `src/python/camera_stream/example_effdet_d4_output.npy`. Without `--mock-inference`, these paths are ignored and real GPU inference is used. +Omit `-f compose.gpu.yaml` when using mock inference, since no GPU is needed. -### Docker +#### Docker Building from Source -In Docker, mock modes are controlled via environment variables in `.env` (or `docker/.env` when building from source): +Set environment variables in `docker/.env`: ```bash # Set to any non-empty value (e.g. "true") to enable, leave empty to disable @@ -437,8 +440,34 @@ MOCK_CAMERA=true MOCK_INFERENCE=true ``` +Then build and run from the `docker/` directory: + +```bash +cd docker +docker compose --profile client --profile server up --build +``` + See [docker/README.Docker.md](docker/README.Docker.md#mock-modes) for details. +#### Manual Setup (without Docker) + +Pass CLI flags to the Python entry points: + +```bash +# Client-side: mock camera +uv run client_main.py -c ../../config/client_config.yaml -s --mock-camera + +# Server-side: mock inference +uv run server_main.py -c ../../config/server_config_gcloud.yaml --mock-inference +``` + +### Mock File Configuration + +The YAML config files specify which mock files to use; the CLI flag (or Docker env var) controls whether they are actually applied. + +- **Mock camera image:** Configured per camera in `camera_stream_config_list` via the `mock_camera_image_path` key. A sample mock image is included at `src/python/camera_stream/mock_webcam_image.jpg`. Without mock camera enabled, this path is ignored and real USB cameras are used. +- **Mock inference output:** Configured per server in `server_config_list` via `mock_inference_output_path` (numpy array of detections) and `mock_model_latency_csv_path` (per-model latency benchmarks). A sample mock output is included at `src/python/camera_stream/example_effdet_d4_output.npy`. Without mock inference enabled, these paths are ignored and real GPU inference is used. + ### Combining Mock Modes The two mock modes are fully independent — you can use any combination: From 13ea2604eb278b0bc39fecdee8129cf02e0d99a4 Mon Sep 17 00:00:00 2001 From: Hongbo Wei Date: Mon, 2 Mar 2026 13:16:26 -0800 Subject: [PATCH 15/15] docs: update setup steps to highlight 3 options --- README.md | 26 +++++++++++++++++--------- docker/README.Docker.md | 4 ++-- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 346cde3..9ecdd77 100644 --- a/README.md +++ b/README.md @@ -91,11 +91,11 @@ Running on a GPU-equipped cloud instance (e.g., H100): For detailed architecture, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). -## Quick Start (Docker) — Recommended +## Quick Start (Docker with Pre-Built Images) — Recommended The recommended way to run TURBO is with Docker using pre-built container images published on [DockerHub](https://hub.docker.com/u/hbwei). The Docker setup automatically orchestrates all processes — 2 on the server (QUIC server + model servers) and 3 on the client (client orchestrator + web dashboard + QUIC client) — handling startup ordering, ZMQ socket management, and inter-process communication for you. -> **Pre-built images vs. building from source:** The root-level `compose.yaml` and `.env.example` are configured to pull pre-built images from DockerHub. To build Docker images from source instead (e.g., for development or customization), see the [`docker/`](docker/) directory which contains Dockerfiles, a separate `compose.yaml` for building, and [docker/README.Docker.md](docker/README.Docker.md#building-from-source) for full instructions. +> **Other setup methods:** To build Docker images from source instead of using pre-built images, see [Alternative 1: Docker Building from Source](#alternative-1-docker-building-from-source). To run without Docker at all, see [Alternative 2: Manual Setup](#alternative-2-manual-setup-without-docker). ### Prerequisites @@ -239,12 +239,20 @@ For troubleshooting, architecture details, and development workflows, see [docke --- -## Alternative: Manual Setup (without Docker) +## Alternative 1: Docker Building from Source + +If you want to build the Docker images locally instead of using the pre-built images (e.g., for development or customization), see [docker/README.Docker.md](docker/README.Docker.md#building-from-source) for full setup, build, and run instructions. The `docker/` directory contains its own `compose.yaml`, `.env.example`, and Dockerfiles. + +--- + +## Alternative 2: Manual Setup (without Docker) + +> **This approach is discouraged.** Manual setup requires installing all dependencies (Python, Rust, system libraries) by hand on both client and server machines, carefully managing process startup order, and manually cleaning up ZMQ sockets and shared memory between runs. The Docker-based methods above handle all of this automatically. Use manual setup only if you have a specific reason to avoid Docker.
Click to expand manual setup instructions -If you prefer to run each process directly on your host without Docker, follow the steps below. This requires installing all dependencies (Python, Rust, system libraries) manually on both client and server machines, and carefully managing process startup order. +Follow the steps below to run each process directly on your host without Docker. ### Prerequisites @@ -277,7 +285,7 @@ If you prefer to run each process directly on your host without Docker, follow t ```
-2. **Download model checkpoints and evaluation data** — follow steps 2 and 3 from the [Docker Quick Start](#quick-start-docker--recommended) above. +2. **Download model checkpoints and evaluation data** — follow steps 2 and 3 from the [Quick Start](#quick-start-docker-with-pre-built-images--recommended) above. After extraction, update the checkpoint paths in your server configuration file (`config/server_config_gcloud.yaml`) and model config (`src/python/model_server/model_config.yaml`) to point to the extracted checkpoint files. Also ensure the `full_eval_dir` path in `config/client_config.yaml` points to the extracted `~/full-eval/` directory. @@ -410,9 +418,9 @@ TURBO supports two independent mock modes for testing and development without re ### Enabling Mock Modes -Follow the instructions for whichever setup method you used — [Quick Start (Docker)](#quick-start-docker--recommended), [Building from Source](docker/README.Docker.md#building-from-source), or [Manual Setup](#alternative-manual-setup-without-docker). +Follow the instructions below that correspond to the setup method you used — [Quick Start](#quick-start-docker-with-pre-built-images--recommended), [Alternative 1](#alternative-1-docker-building-from-source), or [Alternative 2](#alternative-2-manual-setup-without-docker). -#### Docker with Pre-Built Images (Recommended) +#### Quick Start: Docker with Pre-Built Images Set environment variables in `.env` (at the repo root): @@ -430,7 +438,7 @@ docker compose --profile client --profile server up Omit `-f compose.gpu.yaml` when using mock inference, since no GPU is needed. -#### Docker Building from Source +#### Alternative 1: Docker Building from Source Set environment variables in `docker/.env`: @@ -449,7 +457,7 @@ docker compose --profile client --profile server up --build See [docker/README.Docker.md](docker/README.Docker.md#mock-modes) for details. -#### Manual Setup (without Docker) +#### Alternative 2: Manual Setup (without Docker) Pass CLI flags to the Python entry points: diff --git a/docker/README.Docker.md b/docker/README.Docker.md index 8755ad3..a5cd3db 100644 --- a/docker/README.Docker.md +++ b/docker/README.Docker.md @@ -2,14 +2,14 @@ This directory contains everything needed to **build Docker images from source** — Dockerfiles, a build-oriented `compose.yaml`, and shared configuration files. It is intended for development and customization, **not** for running pre-built images. -- **To run TURBO using pre-built images** (recommended), use the root-level `compose.yaml` and `.env.example`. See the [Quick Start (Docker)](../README.md#quick-start-docker--recommended) section in the main README. +- **To run TURBO using pre-built images** (recommended), use the root-level `compose.yaml` and `.env.example`. See the [Quick Start (Docker with Pre-Built Images)](../README.md#quick-start-docker-with-pre-built-images--recommended) section in the main README. - **To build images from source**, use the `compose.yaml` and `.env.example` in this directory. See [Building from Source](#building-from-source) below. The `docker/config/` directory contains Docker-specific YAML configs shared by both workflows. ## Building from Source -If you want to build the Docker images locally instead of using the pre-built images (e.g., for development or customization), follow these steps. +This section corresponds to [Alternative 1: Docker Building from Source](../README.md#alternative-1-docker-building-from-source) in the main README. If you want to build the Docker images locally instead of using the pre-built images (e.g., for development or customization), follow these steps. ### Prerequisites