diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 18a947f..0a849e3 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -72,13 +72,22 @@ jobs: issues: write pull-requests: write - env: - CAS_ENDPOINT: localhost:9094 - BENCHMARK_SERVICES: 1 - steps: - uses: actions/checkout@v4 + - name: Install Podman + run: | + sudo apt-get update + sudo apt-get install -y podman + podman --version + + - name: Use Podman for OCI image loading + run: | + # rules_oci oci_load prefers 'docker' over 'podman'. Make 'docker' + # resolve to podman so images are loaded into the Podman store. + sudo ln -sf $(command -v podman) /usr/local/bin/docker + docker --version + - name: Install Bazel uses: bazelbuild/setup-bazelisk@v3 @@ -91,19 +100,19 @@ jobs: ${{ runner.os }}-bazel- - name: Build binaries - run: bazel build //:all + run: bazel build //:server //:worker //:cache //:bes - name: Run tests run: bazel test //... - name: Build and load images run: | - for image in server worker cache; do + for image in server worker cache bes; do echo "=== Building ${image} ===" bazel run "//oci:${image}.amd64.image.load" done echo "=== Images built ===" - docker images | grep ferrisrbe || true + podman images | grep ferrisrbe || true - name: Install Kind uses: helm/kind-action@v1 @@ -113,9 +122,10 @@ jobs: - name: Load images into Kind run: | - kind load docker-image ferrisrbe/server:latest --name ferrisrbe-test - kind load docker-image ferrisrbe/worker:latest --name ferrisrbe-test - kind load docker-image ferrisrbe/cache:latest --name ferrisrbe-test + podman save ferrisrbe/server:latest | kind load image-archive - --name ferrisrbe-test + podman save ferrisrbe/worker:latest | kind load image-archive - --name ferrisrbe-test + podman save ferrisrbe/cache:latest | kind load image-archive - --name ferrisrbe-test + podman save ferrisrbe/bes:latest | kind load image-archive - --name ferrisrbe-test echo "=== Verify images in Kind ===" kubectl get nodes -o json | jq -r '.items[0].status.images[] | select(.names[] | contains("ferrisrbe")) | .names[0]' || true @@ -128,7 +138,8 @@ jobs: --set worker.image.pullPolicy=Never \ --set cache.image.repository=ferrisrbe/cache \ --set cache.image.pullPolicy=Never \ - --set bes.enabled=false \ + --set bes.image.repository=ferrisrbe/bes \ + --set bes.image.pullPolicy=Never \ --set server.livenessProbe.enabled=false \ --set server.readinessProbe.enabled=false \ --set worker.livenessProbe.enabled=false \ @@ -149,6 +160,8 @@ jobs: kubectl describe deployment ferrisrbe-server -n rbe || true echo "=== Describe worker deployment ===" kubectl describe deployment ferrisrbe-worker -n rbe || true + echo "=== Describe BES deployment ===" + kubectl describe deployment ferrisrbe-bes -n rbe || true - name: Wait for deployment run: | @@ -171,6 +184,13 @@ jobs: kubectl logs -l app.kubernetes.io/component=worker -n rbe --tail=100 || true exit 1 } + kubectl rollout status deployment/ferrisrbe-bes -n rbe --timeout=120s || { + echo "=== BES deployment failed ===" + kubectl get pods -n rbe + kubectl describe deployment ferrisrbe-bes -n rbe + kubectl logs -l app.kubernetes.io/component=bes -n rbe --tail=100 || true + exit 1 + } - name: Port forward run: | @@ -191,57 +211,22 @@ jobs: run: | kind delete cluster --name ferrisrbe-test || true - - name: Install benchmark dependencies - run: | - sudo apt-get update - sudo apt-get install -y python3-pip bc netcat-openbsd - pip3 install grpcio grpcio-tools - - - name: Start benchmark cache service - run: | - docker run -d --name rbe-cache \ - -p 9094:9094 \ - -p 8080:8080 \ - -e RUST_LOG=info \ - -e RBE_CACHE_PORT=9094 \ - -e RBE_CACHE_HTTP_PORT=8080 \ - -e RBE_CACHE_DIR=/data \ - -e RBE_CACHE_MAX_SIZE_GB=1 \ - -e RBE_CACHE_AC_MAX_SIZE_GB=1 \ - ferrisrbe/cache:latest - - - name: Wait for benchmark cache service - run: | - for i in $(seq 1 30); do - if nc -zv localhost 9094 2>/dev/null; then - echo "Cache service is ready" - exit 0 - fi - echo "Waiting for cache service... ($i/30)" - sleep 1 - done - echo "Cache service did not become ready" - docker logs rbe-cache - exit 1 - - - name: Run full benchmark suite + - name: Run benchmark suite id: benchmark run: | - cd benchmark - ./scripts/benchmark-ci.sh full + bazel run //benchmark:run -- full --cache-image ferrisrbe/cache:latest continue-on-error: true - name: Compare PR vs official release id: compare run: | - cd benchmark - ./scripts/compare-branches.sh ferrisrbe/server:latest + bazel run //benchmark:run -- compare --image ferrisrbe/server:latest continue-on-error: true - name: Check regression thresholds id: regression run: | - python3 benchmark/scripts/check-regression.py benchmark/results/benchmark_data.json + bazel run //benchmark/scripts:check_regression -- benchmark/results/benchmark_data.json continue-on-error: true - name: Upload benchmark results diff --git a/.gitignore b/.gitignore index bc87199..016befa 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,9 @@ target MODULE.bazel.lock node_modules ui/dist -.bes \ No newline at end of file +.bes +_site/ + +# Benchmark generated results +benchmark/results/* +!benchmark/results/.gitkeep diff --git a/MODULE.bazel b/MODULE.bazel index 5d3b6e3..1aae0f6 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -11,6 +11,7 @@ bazel_dep(name = "aspect_bazel_lib", version = "2.14.0") bazel_dep(name = "platforms", version = "0.0.11") bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "hermetic_cc_toolchain", version = "3.1.1") +bazel_dep(name = "rules_python", version = "1.0.0") toolchains = use_extension("@hermetic_cc_toolchain//toolchain:ext.bzl", "toolchains") use_repo(toolchains, "zig_sdk") @@ -96,3 +97,18 @@ use_repo( "debian_bookworm_slim_linux_amd64", "debian_bookworm_slim_linux_arm64_v8", ) + +python = use_extension("@rules_python//python/extensions:python.bzl", "python") +python.toolchain( + is_default = True, + python_version = "3.11", +) +use_repo(python, "python_3_11", "python_versions") + +pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip") +pip.parse( + hub_name = "benchmark_pip", + python_version = "3.11", + requirements_lock = "//benchmark:requirements.txt", +) +use_repo(pip, "benchmark_pip") diff --git a/README.md b/README.md index ffe2650..7905682 100644 --- a/README.md +++ b/README.md @@ -179,37 +179,30 @@ Compare to Java-based alternatives that idle at 500MB+ and spike to 4GB+ during ### Running Benchmarks -The benchmark suite tests eight critical dimensions of RBE performance: +The benchmark suite tests eight critical dimensions of RBE performance and is now fully Bazelized: ```bash -cd benchmark +# Run the full container-native benchmark suite +bazel run //benchmark:run -- full -# 1. Memory footprint (baseline) -./scripts/benchmark.sh +# Or run the lightweight PR validation suite +bazel run //benchmark:run -- light -# 2. Execution API throughput (Zero-GC advantage) -./scripts/execution-load-test.py --actions 1000 --concurrent 50 +# Compare the local image against the official Docker Hub release +bazel run //benchmark:run -- compare --image ferrisrbe/server:latest -# 3. Action Cache performance (L1 DashMap + L2 disk-backed) -./scripts/action-cache-test.py --operations 10000 --concurrent 100 - -# 4. Multi-level scheduler (no head-of-line blocking) -./scripts/noisy-neighbor-test.py --slow 10 --fast 50 - -# 5. O(1) Streaming (constant memory with large files) -./scripts/o1-streaming-test.py --large-sizes 5 10 --small-count 1000 - -# 6. Connection churn (resource cleanup) -./scripts/connection-churn-test.py --connections 1000 --disconnect-rate 0.3 +# Generate a markdown report from existing JSON results +bazel run //benchmark:run -- report +``` -# 7. Cache stampede (thundering herd protection) -./scripts/cache-stampede-test.py --requests 10000 --concurrent 100 +Individual benchmark scripts are also available as Bazel targets, for example: -# 8. Cold start time (<100ms vs 5-30s JVM) -./scripts/cold-start-test.sh +```bash +bazel run //benchmark/scripts:execution_load_test -- --actions 1000 --concurrent 50 +bazel run //benchmark/scripts:action_cache_test -- --operations 10000 --concurrent 100 ``` -See [benchmark/README.md](benchmark/README.md) for detailed benchmark documentation and [benchmark/results/BENCHMARK_RESULTS.md](benchmark/results/BENCHMARK_RESULTS.md) for results. +See [benchmark/README.md](benchmark/README.md) for detailed benchmark documentation. ## Quick Start diff --git a/benchmark/BUILD.bazel b/benchmark/BUILD.bazel new file mode 100644 index 0000000..5e25e08 --- /dev/null +++ b/benchmark/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_python//python:defs.bzl", "py_binary") + +package(default_visibility = ["//visibility:public"]) + +py_binary( + name = "run", + srcs = ["orchestrator.py"], + main = "orchestrator.py", + deps = [ + "//benchmark/scripts:benchmark_lib", + ], +) + +# Convenience target to build every benchmark artefact without running tests. +filegroup( + name = "all", + srcs = [ + ":run", + "//benchmark/proto:reapi_py_proto", + "//benchmark/scripts:all", + ], +) diff --git a/benchmark/CI_CD.md b/benchmark/CI_CD.md index cc8c0ba..f45aadd 100644 --- a/benchmark/CI_CD.md +++ b/benchmark/CI_CD.md @@ -81,39 +81,34 @@ variables: build: stage: build - image: rust:1.85 + image: bazelbuild/bazelisk:latest script: - - apt-get update && apt-get install -y protobuf-compiler - - cargo build --release --bin rbe-server + - bazel build //:all artifacts: paths: - - target/release/rbe-server + - bazel-bin/ expire_in: 1 hour benchmark:light: stage: benchmark - image: python:3.11 + image: bazelbuild/bazelisk:latest dependencies: - build script: - - pip install grpcio grpcio-tools - - cd benchmark && ./scripts/benchmark-ci.sh light + - bazel run //benchmark:run -- light --cache-image ferrisrbe/cache:latest artifacts: paths: - benchmark/results/ - reports: - junit: benchmark/results/junit.xml only: - merge_requests benchmark:full: stage: benchmark - image: python:3.11 + image: bazelbuild/bazelisk:latest dependencies: - build script: - - pip install grpcio grpcio-tools - - cd benchmark && ./scripts/benchmark-ci.sh full + - bazel run //benchmark:run -- full --cache-image ferrisrbe/cache:latest artifacts: paths: - benchmark/results/ @@ -127,15 +122,14 @@ benchmark:full: Test the CI pipeline locally before pushing: ```bash -# Build release binary -cargo build --release --bin rbe-server +# Build all binaries and images +bazel build //:all # Run lightweight CI benchmark -cd benchmark -./scripts/benchmark-ci.sh light +bazel run //benchmark:run -- light # Check results -cat results/benchmark_summary.md +cat benchmark/results/benchmark_summary.md ``` ## Regression Thresholds @@ -151,7 +145,7 @@ The CI system checks these thresholds: | `churn_cleanup_rate` | > 95% | Proper resource cleanup | | `streaming_delta_mb` | < 100 MB | O(1) streaming verified | -To modify thresholds, edit `scripts/check-regression.py`: +To modify thresholds, edit `benchmark/scripts/check-regression.py`: ```python THRESHOLDS = { @@ -211,9 +205,8 @@ When comparing against main: 2. **Identify the specific metric** that regressed 3. **Reproduce locally**: ```bash - cd benchmark - ./scripts/benchmark.sh - ./scripts/check-regression.py results/benchmark_data.json + bazel run //benchmark:run -- light + bazel run //benchmark/scripts:check_regression -- benchmark/results/benchmark_data.json ``` 4. **Optimize** the code causing regression 5. **Re-run CI** after fixes @@ -231,27 +224,17 @@ When comparing against main: ### Custom Benchmark Duration -For longer-running benchmarks: +For longer-running benchmarks, run the full suite: ```yaml - name: Run extended benchmark - run: | - cd benchmark - ./scripts/benchmark-ci.sh full - env: - BENCHMARK_DURATION: 300 # 5 minutes per test + run: bazel run //benchmark:run -- full ``` -### Selective Benchmarks - -Run only specific benchmarks: +### Compare Against Official Release ```bash -# Only memory and cold start -./scripts/benchmark-ci.sh light --tests memory,cold_start - -# Skip streaming tests (slow) -./scripts/benchmark-ci.sh full --skip streaming +bazel run //benchmark:run -- compare --image ferrisrbe/server:latest ``` ### Baseline Comparison @@ -259,10 +242,10 @@ Run only specific benchmarks: Compare against a specific baseline: ```bash -python3 scripts/check-regression.py \ - results/benchmark_data.json \ - --baseline results/baseline.json \ - --output report.md +bazel run //benchmark/scripts:check_regression -- \ + benchmark/results/benchmark_data.json \ + --baseline benchmark/results/baseline.json \ + --output benchmark/results/report.md ``` ## Troubleshooting @@ -270,13 +253,13 @@ python3 scripts/check-regression.py \ ### "Server failed to start" - Check if port 9092 is available -- Verify binary exists: `ls target/release/rbe-server` -- Check server logs: `cat results/rbe-server.log` +- Verify the image is built: `bazel run //oci:server_load_amd64` +- Check server logs: `podman logs ferrisrbe-benchmark-server-` ### "Benchmark timeout" - Increase timeout in workflow: `timeout-minutes: 30` -- Reduce load: edit `benchmark-ci.sh` to use fewer actions +- Reduce load: edit `LIGHT_TESTS` / `FULL_TESTS` in `benchmark/orchestrator.py` ### "No baseline for comparison" diff --git a/benchmark/README.md b/benchmark/README.md index 9f5fb54..126b2c7 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -20,22 +20,22 @@ This suite tests seven critical dimensions of RBE performance: | Dimension | Test Script | FerrisRBE Advantage | |-----------|-------------|---------------------| -| **Memory Footprint** | `benchmark-ci.sh` | 18-300x less memory (6.7MB vs GBs) | +| **Memory Footprint** | `//benchmark:run` | 18-300x less memory (6.7MB vs GBs) | | **Execution Throughput** | `execution-load-test.py` | Zero-GC = consistent p99 latency | | **Action Cache Performance** | `action-cache-test.py` | DashMap (lock-free) vs Redis/DB | | **Scheduler Fairness** | `noisy-neighbor-test.py` | Multi-level vs FIFO (no HoL blocking) | | **O(1) Streaming** | `o1-streaming-test.py` | Constant memory regardless of blob size | | **Connection Churn** | `connection-churn-test.py` | Immediate resource cleanup (Tokio) | | **Cache Stampede** | `cache-stampede-test.py` | Request coalescing prevents overload | -| **Cold Start** | `benchmark-ci.sh` | <100ms vs 5-30s JVM warmup | +| **Cold Start** | `//benchmark:run` | <100ms vs 5-30s JVM warmup | ## 🚀 Quick Start (Container Mode) ### Prerequisites ```bash -# Docker (required) -docker --version +# Podman (required) +podman --version # Python dependencies python3 -m pip install grpcio grpcio-tools @@ -51,24 +51,23 @@ bazel --version bazel run //oci:server_load_amd64 # 2. Run container-native benchmarks -cd benchmark -./scripts/benchmark-ci.sh light +bazel run //benchmark:run -- light # Or run full suite -./scripts/benchmark-ci.sh full +bazel run //benchmark:run -- full ``` -### Using Docker Compose (Alternative) +### Using Podman Compose (Alternative) ```bash # Start services -docker-compose -f docker-compose.benchmark.yml up -d +podman-compose -f docker-compose.benchmark.yml up -d # Run benchmarks against running container -./scripts/execution-load-test.py --server localhost:9092 +bazel run //benchmark/scripts:execution_load_test -- --server localhost:9092 # Cleanup -docker-compose -f docker-compose.benchmark.yml down -v +podman-compose -f docker-compose.benchmark.yml down -v ``` ### Generate Report from Results @@ -77,10 +76,7 @@ After running benchmarks, generate a comprehensive markdown report: ```bash # Generate report from all JSON results -./scripts/generate-report.sh - -# Or specify custom paths -./scripts/generate-report.sh ../results ../results/my-report.md +bazel run //benchmark:run -- report ``` This creates: @@ -99,7 +95,7 @@ Instead of compiling `main` branch (slow), compare your PR against the **officia ```bash # Compare PR image against xangcastle/ferris-server:latest -./scripts/compare-branches.sh ferrisrbe/server:latest +bazel run //benchmark:run -- compare --image ferrisrbe/server:latest ``` This is **much faster** (~30s vs ~5-10min) and tests the **actual artifact users run**. @@ -125,24 +121,29 @@ This is **much faster** (~30s vs ~5-10min) and tests the **actual artifact users ``` benchmark/ ├── README.md # This file +├── BUILD.bazel # Bazel entry points +├── requirements.txt # Python dependencies for rules_python ├── docker-compose.benchmark.yml # Optimized benchmark setup ├── docker-compose.ferrisrbe.yml # Full stack (dev) ├── config/ # Configuration files -├── scripts/ -│ ├── benchmark-ci.sh # ⭐ Main CI script (container-native) -│ ├── compare-branches.sh # ⭐ Compare PR vs official release -│ ├── generate-report.sh # ⭐ Generate markdown report from results -│ ├── check-regression.py # Regression detection -│ ├── execution-load-test.py # Execution API throughput -│ ├── action-cache-test.py # AC performance (DashMap) -│ ├── noisy-neighbor-test.py # Scheduler fairness -│ ├── o1-streaming-test.py # O(1) streaming (large files) -│ ├── connection-churn-test.py # Abrupt disconnections -│ ├── cache-stampede-test.py # Thundering herd test +├── proto/ # Bazel-generated Python proto stubs +│ ├── BUILD.bazel +│ └── generate_protos.py +├── scripts/ # Python benchmark scripts +│ ├── BUILD.bazel # Bazel targets for each script +│ ├── benchmark_lib.py # Shared benchmark helpers +│ ├── check-regression.py # Regression detection +│ ├── execution-load-test.py # Execution API throughput +│ ├── action-cache-test.py # AC performance (DashMap) +│ ├── noisy-neighbor-test.py # Scheduler fairness +│ ├── o1-streaming-test.py # O(1) streaming (large files) +│ ├── connection-churn-test.py # Abrupt disconnections +│ ├── cache-stampede-test.py # Thundering herd test │ └── ... +├── orchestrator.py # Main Bazel-runnable orchestrator └── results/ # Generated results - ├── BENCHMARK_REPORT_*.md # Auto-generated reports - └── LATEST_REPORT.md # Symlink to latest report + ├── BENCHMARK_REPORT_*.md # Auto-generated reports + └── LATEST_REPORT.md # Symlink to latest report ``` ## 🐕 Dogfooding with Bazel @@ -156,8 +157,8 @@ bazel run //oci:server_load_amd64 # Build ARM64 image (for Mac M1/M2) bazel run //oci:server_load -# The benchmark scripts use this image automatically -./scripts/benchmark-ci.sh light +# The benchmark orchestrator uses this image automatically +bazel run //benchmark:run -- light ``` ## 🔬 Benchmark Details @@ -167,7 +168,7 @@ bazel run //oci:server_load Measures idle memory consumption inside the container with resource limits. ```bash -./scripts/benchmark-ci.sh light # Includes memory test +bazel run //benchmark:run -- light # Includes memory test ``` **Why container mode matters:** Tests actual memory usage with Docker limits, not host OS memory. @@ -177,7 +178,7 @@ Measures idle memory consumption inside the container with resource limits. Measures container startup + server ready time. ```bash -./scripts/benchmark-ci.sh light # Includes cold start test +bazel run //benchmark:run -- light # Includes cold start test ``` **Container cold start includes:** @@ -190,18 +191,18 @@ Measures container startup + server ready time. Tests concurrent action execution via the Execution API against the container. ```bash -./scripts/execution-load-test.py \ +bazel run //benchmark/scripts:execution_load_test -- \ --server localhost:9092 \ --actions 1000 \ --concurrent 50 ``` -### 4. O(1) Streaming with Container Monitoring +### 4. O(1) Streaming -Tests memory usage when streaming large files, monitoring actual container stats: +Tests memory usage when streaming large files: ```bash -./scripts/o1-streaming-test.py \ +bazel run //benchmark/scripts:o1_streaming_test -- \ --server localhost:9092 \ --large-sizes 5 10 \ --small-count 1000 \ @@ -254,14 +255,14 @@ The repository includes a GitHub Actions workflow (`.github/workflows/benchmark. bazel run //oci:server_load_amd64 # Run benchmarks -./scripts/benchmark-ci.sh light +bazel run //benchmark:run -- light ``` ### Against Official Release ```bash # Compare local build against Docker Hub -./scripts/compare-branches.sh ferrisrbe/server:latest +bazel run //benchmark:run -- compare --image ferrisrbe/server:latest ``` ### Other Solutions @@ -269,20 +270,20 @@ bazel run //oci:server_load_amd64 Each solution has a corresponding `docker-compose.*.yml` file: ```bash -# Test Buildfarm (upstream Docker image) -docker-compose -f docker-compose.buildfarm.yml up -d -./scripts/execution-load-test.py --server localhost:9092 -docker-compose -f docker-compose.buildfarm.yml down -v - -# Test Buildbarn (upstream Docker images) -docker-compose -f docker-compose.buildbarn.yml up -d -./scripts/execution-load-test.py --server localhost:9092 -docker-compose -f docker-compose.buildbarn.yml down -v - -# Test NativeLink (upstream Docker image) -docker-compose -f docker-compose.nativelink.yml up -d -./scripts/execution-load-test.py --server localhost:9092 -docker-compose -f docker-compose.nativelink.yml down -v +# Test Buildfarm (upstream container image) +podman-compose -f docker-compose.buildfarm.yml up -d +bazel run //benchmark/scripts:execution_load_test -- --server localhost:9092 +podman-compose -f docker-compose.buildfarm.yml down -v + +# Test Buildbarn (upstream container images) +podman-compose -f docker-compose.buildbarn.yml up -d +bazel run //benchmark/scripts:execution_load_test -- --server localhost:9092 +podman-compose -f docker-compose.buildbarn.yml down -v + +# Test NativeLink (upstream container image) +podman-compose -f docker-compose.nativelink.yml up -d +bazel run //benchmark/scripts:execution_load_test -- --server localhost:9092 +podman-compose -f docker-compose.nativelink.yml down -v ``` ## 📝 Implementation Notes diff --git a/benchmark/config/grafana/dashboards/dashboard.yml b/benchmark/config/grafana/dashboards/dashboard.yml deleted file mode 100644 index f4b3078..0000000 --- a/benchmark/config/grafana/dashboards/dashboard.yml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: 1 - -providers: - - name: 'default' - orgId: 1 - folder: '' - type: file - disableDeletion: false - editable: true - options: - path: /etc/grafana/provisioning/dashboards diff --git a/benchmark/config/grafana/dashboards/rbe-benchmark.json b/benchmark/config/grafana/dashboards/rbe-benchmark.json deleted file mode 100644 index 54e1297..0000000 --- a/benchmark/config/grafana/dashboards/rbe-benchmark.json +++ /dev/null @@ -1,293 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": null, - "links": [], - "panels": [ - { - "collapsed": false, - "gridPos": {"h": 1, "w": 24, "x": 0, "y": 0}, - "id": 1, - "panels": [], - "title": "Memory Comparison - RBE Servers", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": {"h": 8, "w": 12, "x": 0, "y": 1}, - "id": 2, - "legend": { - "avg": true, - "current": true, - "max": true, - "min": true, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 2, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "container_memory_usage_bytes{name=\"ferrisrbe-server\"} / 1024 / 1024", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "FerrisRBE Server (MB)", - "refId": "A" - }, - { - "expr": "container_memory_usage_bytes{name=\"buildfarm-server\"} / 1024 / 1024", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "Buildfarm Server (MB)", - "refId": "B" - }, - { - "expr": "container_memory_usage_bytes{name=\"buildbarn-frontend\"} / 1024 / 1024", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "Buildbarn Frontend (MB)", - "refId": "C" - }, - { - "expr": "container_memory_usage_bytes{name=\"buildbuddy-server\"} / 1024 / 1024", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "BuildBuddy Server (MB)", - "refId": "D" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Memory Usage Over Time (MB)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": "Memory (MB)", - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 0, - "gridPos": {"h": 8, "w": 12, "x": 12, "y": 1}, - "id": 3, - "legend": { - "avg": true, - "current": true, - "max": true, - "min": true, - "show": true, - "values": true - }, - "lines": true, - "linewidth": 2, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 2, - "points": true, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(container_cpu_usage_seconds_total{name=\"ferrisrbe-server\"}[1m]) * 100", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "FerrisRBE Server (%)", - "refId": "A" - }, - { - "expr": "rate(container_cpu_usage_seconds_total{name=\"buildfarm-server\"}[1m]) * 100", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "Buildfarm Server (%)", - "refId": "B" - }, - { - "expr": "rate(container_cpu_usage_seconds_total{name=\"buildbarn-frontend\"}[1m]) * 100", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "Buildbarn Frontend (%)", - "refId": "C" - }, - { - "expr": "rate(container_cpu_usage_seconds_total{name=\"buildbuddy-server\"}[1m]) * 100", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "BuildBuddy Server (%)", - "refId": "D" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "CPU Usage Over Time (%)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": "CPU (%)", - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "collapsed": false, - "gridPos": {"h": 1, "w": 24, "x": 0, "y": 9}, - "id": 4, - "panels": [], - "title": "Benchmark Results", - "type": "row" - }, - { - "columns": [], - "datasource": "Prometheus", - "fontSize": "100%", - "gridPos": {"h": 8, "w": 24, "x": 0, "y": 10}, - "id": 5, - "links": [], - "scroll": true, - "showHeader": true, - "sort": {"col": 0, "desc": false}, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "alias": "", - "colorMode": null, - "colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], - "targets": [ - { - "expr": "benchmark_memory_usage_mb", - "format": "table", - "instant": true, - "intervalFactor": 1, - "refId": "A" - } - ], - "title": "Memory Benchmark Results", - "transform": "table", - "type": "table" - } - ], - "schemaVersion": 16, - "style": "dark", - "tags": ["rbe", "benchmark"], - "templating": {"list": []}, - "time": {"from": "now-30m", "to": "now"}, - "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] - }, - "timezone": "", - "title": "RBE Benchmark Comparison", - "uid": "rbe-benchmark", - "version": 1 -} diff --git a/benchmark/config/grafana/datasources/datasources.yml b/benchmark/config/grafana/datasources/datasources.yml deleted file mode 100644 index e7f18a3..0000000 --- a/benchmark/config/grafana/datasources/datasources.yml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: 1 - -datasources: - - name: Prometheus - type: prometheus - access: proxy - url: http://prometheus:9090 - isDefault: true - editable: false - jsonData: - timeInterval: 5s - httpMethod: POST diff --git a/benchmark/config/prometheus.yml b/benchmark/config/prometheus.yml deleted file mode 100644 index d981039..0000000 --- a/benchmark/config/prometheus.yml +++ /dev/null @@ -1,49 +0,0 @@ -global: - scrape_interval: 5s - evaluation_interval: 5s - external_labels: - monitor: 'benchmark' - -scrape_configs: - # Prometheus self-monitoring - - job_name: 'prometheus' - static_configs: - - targets: ['localhost:9090'] - - # cAdvisor - Container metrics - - job_name: 'cadvisor' - static_configs: - - targets: ['cadvisor:8080'] - metric_relabel_configs: - - source_labels: [name] - target_label: container_name - - # Node Exporter - Host metrics - - job_name: 'node-exporter' - static_configs: - - targets: ['node-exporter:9100'] - - # FerrisRBE via cAdvisor (fallback) - - job_name: 'ferrisrbe-cadvisor' - static_configs: - - targets: ['cadvisor:8080'] - relabel_configs: - - source_labels: [__name__] - regex: 'container_memory_usage_bytes|container_cpu_usage_seconds_total' - action: keep - - # Buildfarm Server (JMX if available) - - job_name: 'buildfarm-server' - static_configs: - - targets: ['buildfarm-server:9093'] - - # BuildBuddy Metrics - - job_name: 'buildbuddy-server' - static_configs: - - targets: ['buildbuddy-server:9093'] - - # Custom benchmark metrics - - job_name: 'benchmark-results' - static_configs: - - targets: ['host.docker.internal:9096'] - metrics_path: /benchmark-metrics diff --git a/benchmark/docker-compose.benchmark.yml b/benchmark/docker-compose.benchmark.yml index d55ba46..12a68c0 100644 --- a/benchmark/docker-compose.benchmark.yml +++ b/benchmark/docker-compose.benchmark.yml @@ -16,9 +16,8 @@ # bazel run //oci:cache_load_amd64 # # # Run benchmarks -# cd benchmark # docker-compose -f docker-compose.benchmark.yml up -d -# ./scripts/benchmark-ci.sh light +# bazel run //benchmark:run -- light # docker-compose -f docker-compose.benchmark.yml down version: "3.8" diff --git a/benchmark/docker-compose.ferrisrbe.yml b/benchmark/docker-compose.ferrisrbe.yml index 83b2802..0bc7dc8 100644 --- a/benchmark/docker-compose.ferrisrbe.yml +++ b/benchmark/docker-compose.ferrisrbe.yml @@ -6,8 +6,8 @@ # bazel run //oci:worker_load # bazel run //oci:cache_load # -# Or use: -# cd benchmark && ./scripts/build-with-bazel.sh image +# Then run the benchmark orchestrator: +# bazel run //benchmark:run -- light version: "3.8" diff --git a/benchmark/docker-compose.monitoring.yml b/benchmark/docker-compose.monitoring.yml deleted file mode 100644 index 5423162..0000000 --- a/benchmark/docker-compose.monitoring.yml +++ /dev/null @@ -1,103 +0,0 @@ -# Monitoring Stack for Benchmarking -# Prometheus + Grafana + cAdvisor for container metrics -version: "3.8" - -services: - # === PROMETHEUS === - prometheus: - image: prom/prometheus:v2.48.0 - container_name: benchmark-prometheus - ports: - - "9090:9090" - volumes: - - ./config/prometheus.yml:/etc/prometheus/prometheus.yml:ro - - prometheus-data:/prometheus - command: - - '--config.file=/etc/prometheus/prometheus.yml' - - '--storage.tsdb.path=/prometheus' - - '--storage.tsdb.retention.time=7d' - - '--web.console.libraries=/usr/share/prometheus/console_libraries' - - '--web.console.templates=/usr/share/prometheus/consoles' - - '--web.enable-lifecycle' - networks: - - benchmark-network - deploy: - resources: - limits: - memory: 2G - - # === GRAFANA === - grafana: - image: grafana/grafana:10.2.3 - container_name: benchmark-grafana - ports: - - "3000:3000" - environment: - - GF_SECURITY_ADMIN_USER=admin - - GF_SECURITY_ADMIN_PASSWORD=benchmark - - GF_USERS_ALLOW_SIGN_UP=false - - GF_SERVER_ROOT_URL=http://localhost:3000 - - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource - volumes: - - ./config/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro - - ./config/grafana/datasources:/etc/grafana/provisioning/datasources:ro - - grafana-data:/var/lib/grafana - networks: - - benchmark-network - depends_on: - - prometheus - deploy: - resources: - limits: - memory: 512M - - # === CADVISOR (Container Metrics) === - cadvisor: - image: gcr.io/cadvisor/cadvisor:v0.47.2 - container_name: benchmark-cadvisor - ports: - - "8081:8080" - volumes: - - /:/rootfs:ro - - /var/run:/var/run:ro - - /sys:/sys:ro - - /var/lib/docker/:/var/lib/docker:ro - - /dev/disk/:/dev/disk:ro - privileged: true - networks: - - benchmark-network - deploy: - resources: - limits: - memory: 256M - - # === NODE EXPORTER (Host Metrics) === - node-exporter: - image: prom/node-exporter:v1.7.0 - container_name: benchmark-node-exporter - ports: - - "9100:9100" - volumes: - - /proc:/host/proc:ro - - /sys:/host/sys:ro - - /:/rootfs:ro - command: - - '--path.procfs=/host/proc' - - '--path.rootfs=/rootfs' - - '--path.sysfs=/host/sys' - - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)' - networks: - - benchmark-network - deploy: - resources: - limits: - memory: 128M - -volumes: - prometheus-data: - grafana-data: - -networks: - benchmark-network: - driver: bridge - external: true # Shared with benchmark stacks diff --git a/benchmark/orchestrator.py b/benchmark/orchestrator.py new file mode 100644 index 0000000..d871339 --- /dev/null +++ b/benchmark/orchestrator.py @@ -0,0 +1,826 @@ +#!/usr/bin/env python3 +"""Container-native benchmark orchestrator for FerrisRBE. + +This script replaces the old Bash orchestration scripts +(`benchmark-ci.sh`, `benchmark-local.sh`, `compare-branches.sh`, +`generate-report.sh`, etc.) with a single Bazel-runnable entry point. + +Usage: + bazel run //benchmark:run -- light + bazel run //benchmark:run -- full + bazel run //benchmark:run -- compare --image ferrisrbe/server:latest + bazel run //benchmark:run -- report +""" + +import argparse +import json +import os +import shutil +import subprocess +import sys +import time +from datetime import datetime, timezone +from pathlib import Path +from typing import Dict, List, Optional, Tuple + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +WORKSPACE_ROOT = Path(__file__).resolve().parents[1] +BENCHMARK_DIR = WORKSPACE_ROOT / "benchmark" +RESULTS_DIR = BENCHMARK_DIR / "results" + +DEFAULT_SERVER_IMAGE = os.environ.get("BENCHMARK_SERVER_IMAGE", "ferrisrbe/server:latest") +DEFAULT_WORKER_IMAGE = os.environ.get("BENCHMARK_WORKER_IMAGE", "ferrisrbe/worker:latest") +DEFAULT_CACHE_IMAGE = os.environ.get("BENCHMARK_CACHE_IMAGE", "xangcastle/ferris-cache:latest") +OFFICIAL_SERVER_IMAGE = os.environ.get("BENCHMARK_OFFICIAL_IMAGE", "xangcastle/ferris-server:latest") + +CAS_ENDPOINT = os.environ.get("CAS_ENDPOINT", "localhost:9094") +WORKER_MEMORY_LIMIT = os.environ.get("WORKER_MEMORY_LIMIT", "512m") +SERVER_MEMORY_LIMIT = os.environ.get("SERVER_MEMORY_LIMIT", "512m") + +CONTAINER_PREFIX = "ferrisrbe-benchmark" +NETWORK_NAME = "ferrisrbe-benchmark-net" + +LIGHT_TESTS = { + "memory": True, + "execution": {"actions": 100, "concurrent": 10}, + "action_cache": {"operations": 1000, "concurrent": 20}, + "cold_start": True, + "connection_churn": {"connections": 100, "disconnect_rate": 0.3}, +} + +FULL_TESTS = { + "memory": True, + "execution": {"actions": 1000, "concurrent": 50}, + "action_cache": {"operations": 10000, "concurrent": 100}, + "noisy_neighbor": {"slow": 10, "fast": 50}, + "o1_streaming": {"large_sizes": [1], "small_count": 100}, + "connection_churn": {"connections": 1000, "disconnect_rate": 0.3}, + "cache_stampede": {"requests": 10000, "concurrent": 100}, + "cas_load": {"blobs": 100, "size": 1048576, "concurrent": 10}, + "cold_start": True, +} + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def log_info(msg: str) -> None: + print(f"[INFO] {msg}") + + +def log_success(msg: str) -> None: + print(f"[SUCCESS] {msg}") + + +def log_warn(msg: str) -> None: + print(f"[WARN] {msg}", file=sys.stderr) + + +def log_error(msg: str) -> None: + print(f"[ERROR] {msg}", file=sys.stderr) + + +def run(cmd: List[str], check: bool = True, cwd: Optional[Path] = None, **kwargs) -> subprocess.CompletedProcess: + """Run a shell command and return the result.""" + log_info("$ " + " ".join(cmd)) + return subprocess.run( + cmd, + cwd=str(cwd or WORKSPACE_ROOT), + check=check, + text=True, + capture_output=True, + **kwargs, + ) + + +def container_runtime_available() -> bool: + return shutil.which("podman") is not None + + +def container_exists(name: str) -> bool: + result = run(["podman", "ps", "-aq", "--filter", f"name={name}"], check=False) + return bool(result.stdout.strip()) + + +def stop_container(name: Optional[str]) -> None: + if not name: + return + if container_exists(name): + run(["podman", "rm", "-f", name], check=False) + + +def list_benchmark_containers() -> List[str]: + """Return names of all containers created by this orchestrator.""" + result = run( + ["podman", "ps", "-aq", "--format", "{{.Names}}"], + check=False, + ) + if result.returncode != 0 or not result.stdout.strip(): + return [] + names = [] + for line in result.stdout.strip().splitlines(): + name = line.strip() + if name.startswith(CONTAINER_PREFIX) or name == "rbe-cache": + names.append(name) + return names + + +def cleanup_all_benchmark_containers() -> None: + """Force-remove any benchmark containers left over from previous runs.""" + names = list_benchmark_containers() + if not names: + return + log_info(f"Removing {len(names)} leftover benchmark container(s): {', '.join(names)}") + for name in names: + run(["podman", "rm", "-f", name], check=False) + # Give the host/port mapper a moment to release bindings. + time.sleep(1) + + +def ensure_network() -> None: + """Create the bridge network used by benchmark containers.""" + result = run(["podman", "network", "inspect", NETWORK_NAME], check=False) + if result.returncode != 0: + log_info(f"Creating container network: {NETWORK_NAME}") + run(["podman", "network", "create", NETWORK_NAME], check=False) + + +def remove_network() -> None: + """Remove the bridge network if it exists.""" + result = run(["podman", "network", "inspect", NETWORK_NAME], check=False) + if result.returncode == 0: + log_info(f"Removing container network: {NETWORK_NAME}") + run(["podman", "network", "rm", NETWORK_NAME], check=False) + + +def wait_for_port(port: int, timeout: int = 60) -> bool: + """Wait until a TCP port accepts connections.""" + start = time.time() + while time.time() - start < timeout: + try: + import socket + with socket.create_connection(("localhost", port), timeout=1): + return True + except OSError: + time.sleep(0.5) + return False + + +def wait_for_port_free(port: int, timeout: int = 30) -> bool: + """Wait until a TCP port is no longer accepting connections.""" + start = time.time() + while time.time() - start < timeout: + try: + import socket + with socket.create_connection(("localhost", port), timeout=1): + time.sleep(0.5) + except OSError: + return True + return False + + +def get_container_memory_mb(name: str) -> float: + """Return the latest memory usage in MB for a container.""" + result = run( + ["podman", "stats", name, "--no-stream", "--format", "{{.MemUsage}}"], + check=False, + ) + if result.returncode != 0 or not result.stdout.strip(): + return 0.0 + # Format is usually "6.5MiB / 512MiB" or similar. + raw = result.stdout.strip().split()[0] + value = float("".join(c for c in raw if c.isdigit() or c == ".")) + if "GiB" in raw: + value *= 1024 + elif "kB" in raw: + value /= 1024 + elif "MB" in raw: + pass # Already in megabytes. + elif "MiB" in raw: + pass # Treat as megabytes for reporting. + return value + + +def get_image_info(image: str) -> Dict[str, str]: + result = run(["podman", "images", "--format", "{{.ID}}|{{.Size}}|{{.CreatedAt}}", image], check=False) + if result.returncode != 0 or not result.stdout.strip(): + return {"id": "N/A", "size": "N/A", "created": "N/A"} + parts = result.stdout.strip().split("|") + return { + "id": parts[0] if len(parts) > 0 else "N/A", + "size": parts[1] if len(parts) > 1 else "N/A", + "created": parts[2] if len(parts) > 2 else "N/A", + } + + +def bazel_run_target(target: str, args: List[str]) -> subprocess.CompletedProcess: + """Run a Bazel target and stream output.""" + cmd = ["bazel", "run", target, "--"] + args + log_info("$ " + " ".join(cmd)) + return subprocess.run(cmd, cwd=str(WORKSPACE_ROOT), text=True, check=False) + + +# --------------------------------------------------------------------------- +# Container lifecycle +# --------------------------------------------------------------------------- + +class BenchmarkEnvironment: + """Manages the rbe-cache, server and worker containers for a benchmark run.""" + + def __init__(self, timestamp: str, server_image: str, worker_image: str, cache_image: str): + self.timestamp = timestamp + self.server_image = server_image + self.worker_image = worker_image + self.cache_image = cache_image + self.server_name = f"{CONTAINER_PREFIX}-server-{timestamp}" + self.worker_name = f"{CONTAINER_PREFIX}-worker-{timestamp}" + self.cache_name = "rbe-cache" + self.cache_started_here = False + self.results: Dict[str, any] = {} + + def ensure_cache(self) -> None: + """Start or reuse an rbe-cache container.""" + if os.environ.get("BENCHMARK_SERVICES"): + log_info("Using existing GitHub Actions service for rbe-cache") + if not wait_for_port(9094, timeout=90): + log_warn("rbe-cache service did not become ready") + return + + if container_exists(self.cache_name): + log_info("Reusing existing rbe-cache container") + if wait_for_port(9094, timeout=30): + return + log_warn("Existing rbe-cache not responding, removing it") + stop_container(self.cache_name) + + log_info(f"Starting rbe-cache container from {self.cache_image}") + RESULTS_DIR.mkdir(parents=True, exist_ok=True) + cache_data = RESULTS_DIR / "rbe-cache-data" + cache_data.mkdir(parents=True, exist_ok=True) + + ensure_network() + run([ + "podman", "run", "-d", + "--name", self.cache_name, + "--network", NETWORK_NAME, + "-p", "9094:9094", + "-p", "8080:8080", + "-v", f"{cache_data}:/data", + "-e", "RUST_LOG=info", + "-e", "RBE_CACHE_PORT=9094", + "-e", "RBE_CACHE_HTTP_PORT=8080", + "-e", "RBE_CACHE_DIR=/data", + "-e", "RBE_CACHE_MAX_SIZE_GB=1", + "-e", "RBE_CACHE_AC_MAX_SIZE_GB=1", + self.cache_image, + ]) + self.cache_started_here = True + + if not wait_for_port(9094, timeout=60): + log_error("rbe-cache did not become ready") + raise RuntimeError("rbe-cache failed to start") + log_success("rbe-cache ready") + + def start_server(self) -> None: + log_info(f"Starting server container from {self.server_image}") + ensure_network() + run([ + "podman", "run", "-d", + "--name", self.server_name, + "--network", NETWORK_NAME, + "-p", "9092:9092", + "-e", "RBE_PORT=9092", + "-e", "RBE_BIND_ADDRESS=0.0.0.0", + "-e", "RUST_LOG=info", + "-e", f"CAS_ENDPOINT={self.cache_name}:9094", + "-e", "RBE_L1_CACHE_CAPACITY=100000", + "-e", "RBE_L1_CACHE_TTL_SECS=3600", + "--memory", SERVER_MEMORY_LIMIT, + self.server_image, + ]) + + if not wait_for_port(9092, timeout=60): + logs = run(["podman", "logs", self.server_name], check=False).stdout + log_error("Server failed to start") + print(logs[-2000:] if logs else "(no logs)", file=sys.stderr) + raise RuntimeError("Server failed to start") + log_success("Server ready") + + def start_worker(self) -> None: + log_info(f"Starting worker container from {self.worker_image}") + worker_id = f"benchmark-worker-{self.timestamp}" + ensure_network() + run([ + "podman", "run", "-d", + "--name", self.worker_name, + "--network", NETWORK_NAME, + "-e", f"WORKER_ID={worker_id}", + "-e", f"SERVER_ENDPOINT=http://{self.server_name}:9092", + "-e", f"CAS_ENDPOINT={self.cache_name}:9094", + "-e", "RUST_LOG=info", + "-e", "MAX_CONCURRENT=4", + "--memory", WORKER_MEMORY_LIMIT, + self.worker_image, + ]) + log_success("Worker container started") + + def cleanup(self) -> None: + log_info("Cleaning up benchmark containers...") + # Remove the containers we created plus any leftovers from prior runs. + cleanup_all_benchmark_containers() + remove_network() + log_success("Cleanup complete") + + +# --------------------------------------------------------------------------- +# Benchmark execution +# --------------------------------------------------------------------------- + +def run_benchmark_script(name: str, args: List[str]) -> bool: + target = f"//benchmark/scripts:{name}" + result = bazel_run_target(target, args) + return result.returncode == 0 + + +def measure_memory(env: BenchmarkEnvironment) -> float: + log_info("Measuring idle server memory...") + samples = [] + for _ in range(5): + mb = get_container_memory_mb(env.server_name) + if mb > 0: + samples.append(mb) + time.sleep(1) + if not samples: + return 0.0 + return min(samples) + + +def run_light_benchmarks(env: BenchmarkEnvironment) -> Dict[str, any]: + RESULTS_DIR.mkdir(parents=True, exist_ok=True) + results: Dict[str, any] = {} + + if LIGHT_TESTS.get("memory"): + results["memory_mb"] = measure_memory(env) + (RESULTS_DIR / "memory_baseline.txt").write_text(str(results["memory_mb"])) + + env.start_worker() + time.sleep(2) + + exec_cfg = LIGHT_TESTS["execution"] + if run_benchmark_script("execution_load_test", [ + "--server", "localhost:9092", + "--actions", str(exec_cfg["actions"]), + "--concurrent", str(exec_cfg["concurrent"]), + "--output", str(RESULTS_DIR / f"execution_{env.timestamp}.json"), + ]): + results["execution"] = "completed" + + ac_cfg = LIGHT_TESTS["action_cache"] + if run_benchmark_script("action_cache_test", [ + "--server", "localhost:9092", + "--operations", str(ac_cfg["operations"]), + "--concurrent", str(ac_cfg["concurrent"]), + "--operation", "read", + "--output", str(RESULTS_DIR / f"cache_{env.timestamp}.json"), + ]): + results["action_cache"] = "completed" + + churn_cfg = LIGHT_TESTS["connection_churn"] + if run_benchmark_script("connection_churn_test", [ + "--server", "localhost:9092", + "--connections", str(churn_cfg["connections"]), + "--disconnect-rate", str(churn_cfg["disconnect_rate"]), + "--output", str(RESULTS_DIR / f"churn_{env.timestamp}.json"), + ]): + results["connection_churn"] = "completed" + + if LIGHT_TESTS.get("cold_start"): + results["cold_start_ms"] = measure_cold_start(env.server_image, env.server_name, env.cache_name) + + return results + + +def run_full_benchmarks(env: BenchmarkEnvironment) -> Dict[str, any]: + RESULTS_DIR.mkdir(parents=True, exist_ok=True) + results: Dict[str, any] = {} + + if FULL_TESTS.get("memory"): + results["memory_mb"] = measure_memory(env) + (RESULTS_DIR / "memory_baseline.txt").write_text(str(results["memory_mb"])) + + env.start_worker() + time.sleep(2) + + exec_cfg = FULL_TESTS["execution"] + if run_benchmark_script("execution_load_test", [ + "--server", "localhost:9092", + "--actions", str(exec_cfg["actions"]), + "--concurrent", str(exec_cfg["concurrent"]), + "--output", str(RESULTS_DIR / f"execution_{env.timestamp}.json"), + ]): + results["execution"] = "completed" + + ac_cfg = FULL_TESTS["action_cache"] + if run_benchmark_script("action_cache_test", [ + "--server", "localhost:9092", + "--operations", str(ac_cfg["operations"]), + "--concurrent", str(ac_cfg["concurrent"]), + "--operation", "read", + "--output", str(RESULTS_DIR / f"cache_{env.timestamp}.json"), + ]): + results["action_cache"] = "completed" + + nn_cfg = FULL_TESTS.get("noisy_neighbor") + if nn_cfg and run_benchmark_script("noisy_neighbor_test", [ + "--server", "localhost:9092", + "--slow", str(nn_cfg["slow"]), + "--fast", str(nn_cfg["fast"]), + "--output", str(RESULTS_DIR / f"scheduler_{env.timestamp}.json"), + ]): + results["noisy_neighbor"] = "completed" + + stream_cfg = FULL_TESTS.get("o1_streaming") + if stream_cfg and run_benchmark_script("o1_streaming_test", [ + "--server", "localhost:9092", + "--large-sizes", " ".join(str(s) for s in stream_cfg["large_sizes"]), + "--small-count", str(stream_cfg["small_count"]), + "--container", env.server_name, + "--output", str(RESULTS_DIR / f"streaming_{env.timestamp}.json"), + ]): + results["o1_streaming"] = "completed" + + churn_cfg = FULL_TESTS["connection_churn"] + if run_benchmark_script("connection_churn_test", [ + "--server", "localhost:9092", + "--connections", str(churn_cfg["connections"]), + "--disconnect-rate", str(churn_cfg["disconnect_rate"]), + "--output", str(RESULTS_DIR / f"churn_{env.timestamp}.json"), + ]): + results["connection_churn"] = "completed" + + stampede_cfg = FULL_TESTS.get("cache_stampede") + if stampede_cfg and run_benchmark_script("cache_stampede_test", [ + "--server", "localhost:9092", + "--requests", str(stampede_cfg["requests"]), + "--concurrent", str(stampede_cfg["concurrent"]), + "--output", str(RESULTS_DIR / f"stampede_{env.timestamp}.json"), + ]): + results["cache_stampede"] = "completed" + + cas_cfg = FULL_TESTS.get("cas_load") + if cas_cfg and run_benchmark_script("cas_load_test", [ + "--server", "localhost:9092", + "--blobs", str(cas_cfg["blobs"]), + "--size", str(cas_cfg["size"]), + "--concurrent", str(cas_cfg["concurrent"]), + "--output", str(RESULTS_DIR / f"cas_{env.timestamp}.json"), + ]): + results["cas_load"] = "completed" + + if FULL_TESTS.get("cold_start"): + results["cold_start_ms"] = measure_cold_start(env.server_image, env.server_name, env.cache_name) + + return results + + +def measure_cold_start(image: str, current_server_name: str, cache_name: str = "rbe-cache") -> Optional[int]: + """Stop the current server, start a fresh one and measure ready time.""" + log_info("Measuring container cold start...") + stop_container(current_server_name) + if not wait_for_port_free(9092, timeout=30): + log_warn("Port 9092 did not become free; forcing cleanup of all benchmark containers") + cleanup_all_benchmark_containers() + + ensure_network() + cold_name = f"{CONTAINER_PREFIX}-coldstart-{int(time.time())}" + start = time.time_ns() + run([ + "podman", "run", "-d", + "--name", cold_name, + "--network", NETWORK_NAME, + "-p", "9092:9092", + "-e", "RBE_PORT=9092", + "-e", "RBE_BIND_ADDRESS=0.0.0.0", + "-e", "RUST_LOG=info", + "-e", f"CAS_ENDPOINT={cache_name}:9094", + image, + ]) + + ready = wait_for_port(9092, timeout=60) + elapsed_ms = (time.time_ns() - start) // 1_000_000 + if ready: + log_success(f"Cold start: {elapsed_ms}ms") + else: + log_error("Cold start measurement failed: server did not become ready") + elapsed_ms = 0 + + stop_container(cold_name) + (RESULTS_DIR / f"coldstart_{int(time.time())}.txt").write_text(str(elapsed_ms)) + return elapsed_ms + + +# --------------------------------------------------------------------------- +# Reports +# --------------------------------------------------------------------------- + +def generate_summary(mode: str, timestamp: str, results: Dict[str, any], image: str) -> None: + RESULTS_DIR.mkdir(parents=True, exist_ok=True) + image_info = get_image_info(image) + + summary_path = RESULTS_DIR / "benchmark_summary.md" + summary_lines = [ + f"### Benchmark Results Summary (Container Mode)", + "", + f"**Mode:** {mode} ", + f"**Timestamp:** {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')} ", + f"**Commit:** {os.environ.get('GITHUB_SHA', 'N/A')} ", + f"**Image:** {image} ", + f"**Image ID:** {image_info.get('id', 'N/A')} ", + f"**Image Size:** {image_info.get('size', 'N/A')}", + "", + ] + + memory_mb = results.get("memory_mb") + if memory_mb is not None: + summary_lines.extend([ + "#### Memory Footprint (Container)", + f"- **Idle Memory:** {memory_mb:.1f} MB", + "", + ]) + if memory_mb > 20: + summary_lines.append(f"⚠️ **WARNING:** Memory usage ({memory_mb:.1f}MB) exceeds threshold (20MB)\n") + else: + summary_lines.append("✅ Memory usage within expected range\n") + + cold_start_ms = results.get("cold_start_ms") + if cold_start_ms is not None: + summary_lines.extend([ + "#### Cold Start Time (Container)", + f"- **Startup Time:** {cold_start_ms}ms", + "", + ]) + if cold_start_ms > 500: + summary_lines.append(f"⚠️ **WARNING:** Cold start ({cold_start_ms}ms) exceeds threshold (500ms)\n") + else: + summary_lines.append("✅ Cold start within expected range\n") + + summary_lines.extend([ + "#### Detailed Results", + "", + ]) + for key, value in results.items(): + if key in ("memory_mb", "cold_start_ms"): + continue + summary_lines.append(f"- {key}: ✅ Completed") + + summary_lines.extend([ + "", + "---", + "*Generated by FerrisRBE Benchmark Suite (Bazel)*", + "", + ]) + + summary_path.write_text("\n".join(summary_lines)) + log_success(f"Summary written to {summary_path}") + + data = { + "timestamp": timestamp, + "mode": mode, + "commit": os.environ.get("GITHUB_SHA", "N/A"), + "container": { + "image": image, + "image_id": image_info.get("id"), + "image_size": image_info.get("size"), + }, + "results": { + "memory_mb": results.get("memory_mb"), + "cold_start_ms": results.get("cold_start_ms"), + }, + } + (RESULTS_DIR / "benchmark_data.json").write_text(json.dumps(data, indent=2) + "\n") + + +def generate_report() -> None: + """Generate a markdown report from existing JSON results.""" + RESULTS_DIR.mkdir(parents=True, exist_ok=True) + json_files = sorted(RESULTS_DIR.glob("*.json")) + if not json_files: + log_warn("No JSON result files found") + return + + lines = [ + "# FerrisRBE Benchmark Report", + "", + f"Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}", + "", + "## Results", + "", + ] + for path in json_files: + try: + data = json.loads(path.read_text()) + except json.JSONDecodeError: + continue + lines.append(f"### {path.stem}") + lines.append("") + lines.append("```json") + lines.append(json.dumps(data, indent=2)) + lines.append("```") + lines.append("") + + report_path = RESULTS_DIR / f"BENCHMARK_REPORT_{datetime.now(timezone.utc):%Y%m%d_%H%M%S}.md" + report_path.write_text("\n".join(lines)) + log_success(f"Report written to {report_path}") + + +def compare_images(pr_image: str) -> None: + """Compare the PR image against the official release image.""" + if not container_runtime_available(): + log_error("Podman is required for comparison") + sys.exit(1) + + timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S") + official_container = f"ferrisrbe-official-{timestamp}" + pr_container = f"ferrisrbe-pr-{timestamp}" + + log_info(f"Comparing PR image {pr_image} against {OFFICIAL_SERVER_IMAGE}") + + def benchmark_image(image: str, name: str, container_name: str) -> Tuple[Optional[float], Optional[Dict]]: + log_info(f"Benchmarking {name}: {image}") + run([ + "podman", "run", "-d", + "--name", container_name, + "--network", "host", + "-p", "9092:9092", + "-e", "RBE_PORT=9092", + "-e", "RBE_BIND_ADDRESS=0.0.0.0", + "-e", "RUST_LOG=info", + "-e", f"CAS_ENDPOINT={CAS_ENDPOINT}", + "--memory", SERVER_MEMORY_LIMIT, + image, + ]) + if not wait_for_port(9092, timeout=60): + log_error(f"Server {name} failed to start") + return None, None + + time.sleep(2) + mem = get_container_memory_mb(container_name) + info = get_image_info(image) + + # Quick throughput sample. + run_benchmark_script("execution_load_test", [ + "--server", "localhost:9092", + "--actions", "50", + "--concurrent", "10", + "--output", str(RESULTS_DIR / f"compare_{name}_{timestamp}.json"), + ]) + + stop_container(container_name) + return mem, info + + try: + run(["podman", "pull", OFFICIAL_SERVER_IMAGE], check=False) + official_mem, official_info = benchmark_image(OFFICIAL_SERVER_IMAGE, "official", official_container) + pr_mem, pr_info = benchmark_image(pr_image, "pr", pr_container) + + lines = [ + "## 📊 Performance Comparison: PR vs Official Release", + "", + "| Attribute | Official (latest) | PR |", + "|-----------|-------------------|-----|", + f"| **Image** | `{OFFICIAL_SERVER_IMAGE}` | `{pr_image}` |", + f"| **Image ID** | `{official_info.get('id', 'N/A')[:12]}` | `{pr_info.get('id', 'N/A')[:12]}` |", + f"| **Size** | {official_info.get('size', 'N/A')} | {pr_info.get('size', 'N/A')} |", + f"| **Memory (MB)** | {official_mem if official_mem is not None else 'N/A'} | {pr_mem if pr_mem is not None else 'N/A'} |", + "", + ] + + if official_mem and pr_mem and official_mem > 0: + change = ((pr_mem - official_mem) / official_mem) * 100 + arrow = "↓" if change < 0 else "↑" if change > 0 else "=" + status = "✅" if abs(change) <= 5 else "⚠️" if abs(change) <= 15 else "🚨" + lines.append(f"**Memory change:** {arrow} {change:.1f}% {status}") + lines.append("") + + lines.extend([ + "#### Legend", + "- ✅ Within 5% - Acceptable", + "- ⚠️ 5-15% change - Review recommended", + "- 🚨 >15% regression - Optimization required", + "", + ]) + + comparison_path = RESULTS_DIR / "comparison.md" + comparison_path.write_text("\n".join(lines)) + log_success(f"Comparison written to {comparison_path}") + finally: + stop_container(official_container) + stop_container(pr_container) + + +# --------------------------------------------------------------------------- +# Main entry points +# --------------------------------------------------------------------------- + +def ensure_images(server_image: str, worker_image: str, cache_image: str) -> None: + """Build or pull images if they are not present locally.""" + if not container_runtime_available(): + log_error("Podman is required for container-native benchmarks") + sys.exit(1) + + for image, label in [(server_image, "server"), (worker_image, "worker"), (cache_image, "cache")]: + result = run(["podman", "image", "inspect", image], check=False) + if result.returncode == 0: + log_info(f"Image present: {image}") + continue + + if image.startswith("xangcastle/"): + log_info(f"Pulling {label} image: {image}") + run(["podman", "pull", image], check=False) + else: + log_info(f"Building {label} image via Bazel: {image}") + arch = os.uname().machine + arch_label = "arm64" if arch in ("arm64", "aarch64") else "amd64" + target = f"//oci:{label}.{arch_label}.image.load" + run(["bazel", "run", target], check=False) + + +def cmd_light(args: argparse.Namespace) -> int: + timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S") + ensure_images(args.server_image, args.worker_image, args.cache_image) + + env = BenchmarkEnvironment( + timestamp=timestamp, + server_image=args.server_image, + worker_image=args.worker_image, + cache_image=args.cache_image, + ) + cleanup_all_benchmark_containers() + try: + env.ensure_cache() + env.start_server() + results = run_light_benchmarks(env) + generate_summary("light", timestamp, results, args.server_image) + return 0 + finally: + env.cleanup() + + +def cmd_full(args: argparse.Namespace) -> int: + timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S") + ensure_images(args.server_image, args.worker_image, args.cache_image) + + env = BenchmarkEnvironment( + timestamp=timestamp, + server_image=args.server_image, + worker_image=args.worker_image, + cache_image=args.cache_image, + ) + + cleanup_all_benchmark_containers() + try: + env.ensure_cache() + env.start_server() + results = run_full_benchmarks(env) + generate_summary("full", timestamp, results, args.server_image) + return 0 + finally: + env.cleanup() + + +def cmd_compare(args: argparse.Namespace) -> int: + compare_images(args.image) + return 0 + + +def cmd_report(args: argparse.Namespace) -> int: + generate_report() + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Bazel-native benchmark orchestrator for FerrisRBE", + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + for name, func in [("light", cmd_light), ("full", cmd_full), ("compare", cmd_compare), ("report", cmd_report)]: + sub = subparsers.add_parser(name, help=f"Run {name} benchmark flow") + sub.set_defaults(func=func) + if name in ("light", "full"): + sub.add_argument("--server-image", default=DEFAULT_SERVER_IMAGE) + sub.add_argument("--worker-image", default=DEFAULT_WORKER_IMAGE) + sub.add_argument("--cache-image", default=DEFAULT_CACHE_IMAGE) + if name == "compare": + sub.add_argument("--image", default=DEFAULT_SERVER_IMAGE) + + args = parser.parse_args() + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/benchmark/proto/BUILD.bazel b/benchmark/proto/BUILD.bazel new file mode 100644 index 0000000..6e9daa6 --- /dev/null +++ b/benchmark/proto/BUILD.bazel @@ -0,0 +1,68 @@ +load("@rules_python//python:defs.bzl", "py_binary", "py_library") +load("@benchmark_pip//:requirements.bzl", "requirement") + +package(default_visibility = ["//benchmark:__subpackages__"]) + +# Stubs generated by grpcio-tools. The list must stay in sync with the protos +# consumed by the benchmark scripts. +_REAPI_PYTHON_STUBS = [ + "build/bazel/remote/execution/v2/remote_execution_pb2.py", + "build/bazel/remote/execution/v2/remote_execution_pb2_grpc.py", + "build/bazel/semver/semver_pb2.py", + "build/bazel/semver/semver_pb2_grpc.py", + "google/api/annotations_pb2.py", + "google/api/annotations_pb2_grpc.py", + "google/api/client_pb2.py", + "google/api/client_pb2_grpc.py", + "google/api/field_behavior_pb2.py", + "google/api/field_behavior_pb2_grpc.py", + "google/api/http_pb2.py", + "google/api/http_pb2_grpc.py", + "google/api/launch_stage_pb2.py", + "google/api/launch_stage_pb2_grpc.py", + "google/bytestream/bytestream_pb2.py", + "google/bytestream/bytestream_pb2_grpc.py", + "google/longrunning/operations_pb2.py", + "google/longrunning/operations_pb2_grpc.py", + "google/rpc/status_pb2.py", + "google/rpc/status_pb2_grpc.py", +] + +py_binary( + name = "generator", + srcs = ["generate_protos.py"], + data = ["//proto:all_protos"], + main = "generate_protos.py", + deps = [requirement("grpcio-tools")], +) + +_REAPI_PROTOS = [ + "//proto:build/bazel/remote/execution/v2/remote_execution.proto", + "//proto:build/bazel/semver/semver.proto", + "//proto:google/api/annotations.proto", + "//proto:google/api/client.proto", + "//proto:google/api/field_behavior.proto", + "//proto:google/api/http.proto", + "//proto:google/api/launch_stage.proto", + "//proto:google/bytestream/bytestream.proto", + "//proto:google/longrunning/operations.proto", + "//proto:google/rpc/status.proto", +] + +genrule( + name = "proto_stubs", + srcs = _REAPI_PROTOS, + outs = _REAPI_PYTHON_STUBS, + cmd = "$(location :generator) $(@D) $(SRCS)", + tools = [":generator"], +) + +py_library( + name = "reapi_py_proto", + srcs = [":proto_stubs"], + imports = ["."], + deps = [ + requirement("grpcio"), + requirement("protobuf"), + ], +) diff --git a/benchmark/proto/generate_protos.py b/benchmark/proto/generate_protos.py new file mode 100644 index 0000000..ee5b3ad --- /dev/null +++ b/benchmark/proto/generate_protos.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Generate Python protobuf and gRPC stubs using grpcio-tools. + +This script is invoked from benchmark/proto/BUILD.bazel and writes generated +_pb2.py and _pb2_grpc.py files into a single output tree that mirrors the +original proto package structure. +""" + +import os +import sys + +import grpc_tools +from grpc_tools import protoc + + +def find_proto_include_dir(proto_files): + """Return the directory that acts as the proto import root. + + The paths passed by Bazel are sandbox paths such as: + proto/build/bazel/.../remote_execution.proto + We walk up each path until we find the 'proto' component and return the + path up to and including 'proto'. That directory is the include root: + proto imports like "build/bazel/semver/semver.proto" are resolved inside + it. + """ + for pf in proto_files: + parts = os.path.normpath(pf).split(os.sep) + if "proto" in parts: + idx = parts.index("proto") + return os.sep.join(parts[: idx + 1]) + raise RuntimeError( + f"Could not locate the 'proto' include root in any of: {proto_files}" + ) + + +def well_known_types_include(): + """Return the path grpcio-tools uses for protobuf well-known types.""" + return os.path.join(grpc_tools.__path__[0], "_proto") + + +def main(): + if len(sys.argv) < 3: + print( + "Usage: generate_protos.py ...", + file=sys.stderr, + ) + sys.exit(1) + + out_dir = sys.argv[1] + proto_files = sys.argv[2:] + + os.makedirs(out_dir, exist_ok=True) + include_dir = find_proto_include_dir(proto_files) + + # protoc expects paths relative to the include directory. + rel_proto_files = [os.path.relpath(pf, include_dir) for pf in proto_files] + + args = [ + "protoc", + f"-I{include_dir}", + f"-I{well_known_types_include()}", + f"--python_out={out_dir}", + f"--grpc_python_out={out_dir}", + ] + rel_proto_files + + return_code = protoc.main(args) + if return_code != 0: + sys.exit(return_code) + + +if __name__ == "__main__": + main() diff --git a/benchmark/proto_gen/build/bazel/remote/execution/v2/remote_execution_pb2.py b/benchmark/proto_gen/build/bazel/remote/execution/v2/remote_execution_pb2.py deleted file mode 100644 index 692de1e..0000000 --- a/benchmark/proto_gen/build/bazel/remote/execution/v2/remote_execution_pb2.py +++ /dev/null @@ -1,215 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: build/bazel/remote/execution/v2/remote_execution.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'build/bazel/remote/execution/v2/remote_execution.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from build.bazel.semver import semver_pb2 as build_dot_bazel_dot_semver_dot_semver__pb2 -from google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2 -from google.longrunning import operations_pb2 as google_dot_longrunning_dot_operations__pb2 -from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 -from google.rpc import status_pb2 as google_dot_rpc_dot_status__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n6build/bazel/remote/execution/v2/remote_execution.proto\x12\x1f\x62uild.bazel.remote.execution.v2\x1a\x1f\x62uild/bazel/semver/semver.proto\x1a\x1cgoogle/api/annotations.proto\x1a#google/longrunning/operations.proto\x1a\x19google/protobuf/any.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\x1a\x17google/rpc/status.proto\"\xa6\x02\n\x06\x41\x63tion\x12?\n\x0e\x63ommand_digest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x42\n\x11input_root_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12*\n\x07timeout\x18\x06 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x14\n\x0c\x64o_not_cache\x18\x07 \x01(\x08\x12\x0c\n\x04salt\x18\t \x01(\x0c\x12;\n\x08platform\x18\n \x01(\x0b\x32).build.bazel.remote.execution.v2.PlatformJ\x04\x08\x03\x10\x06J\x04\x08\x08\x10\t\"\xae\x04\n\x07\x43ommand\x12\x11\n\targuments\x18\x01 \x03(\t\x12[\n\x15\x65nvironment_variables\x18\x02 \x03(\x0b\x32<.build.bazel.remote.execution.v2.Command.EnvironmentVariable\x12\x18\n\x0coutput_files\x18\x03 \x03(\tB\x02\x18\x01\x12\x1e\n\x12output_directories\x18\x04 \x03(\tB\x02\x18\x01\x12\x14\n\x0coutput_paths\x18\x07 \x03(\t\x12?\n\x08platform\x18\x05 \x01(\x0b\x32).build.bazel.remote.execution.v2.PlatformB\x02\x18\x01\x12\x19\n\x11working_directory\x18\x06 \x01(\t\x12\x1e\n\x16output_node_properties\x18\x08 \x03(\t\x12_\n\x17output_directory_format\x18\t \x01(\x0e\x32>.build.bazel.remote.execution.v2.Command.OutputDirectoryFormat\x1a\x32\n\x13\x45nvironmentVariable\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"R\n\x15OutputDirectoryFormat\x12\r\n\tTREE_ONLY\x10\x00\x12\x12\n\x0e\x44IRECTORY_ONLY\x10\x01\x12\x16\n\x12TREE_AND_DIRECTORY\x10\x02\"{\n\x08Platform\x12\x46\n\nproperties\x18\x01 \x03(\x0b\x32\x32.build.bazel.remote.execution.v2.Platform.Property\x1a\'\n\x08Property\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"\x9a\x02\n\tDirectory\x12\x38\n\x05\x66iles\x18\x01 \x03(\x0b\x32).build.bazel.remote.execution.v2.FileNode\x12\x43\n\x0b\x64irectories\x18\x02 \x03(\x0b\x32..build.bazel.remote.execution.v2.DirectoryNode\x12>\n\x08symlinks\x18\x03 \x03(\x0b\x32,.build.bazel.remote.execution.v2.SymlinkNode\x12H\n\x0fnode_properties\x18\x05 \x01(\x0b\x32/.build.bazel.remote.execution.v2.NodePropertiesJ\x04\x08\x04\x10\x05\"+\n\x0cNodeProperty\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"\xaf\x01\n\x0eNodeProperties\x12\x41\n\nproperties\x18\x01 \x03(\x0b\x32-.build.bazel.remote.execution.v2.NodeProperty\x12)\n\x05mtime\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\tunix_mode\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\"\xbe\x01\n\x08\x46ileNode\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x37\n\x06\x64igest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x15\n\ris_executable\x18\x04 \x01(\x08\x12H\n\x0fnode_properties\x18\x06 \x01(\x0b\x32/.build.bazel.remote.execution.v2.NodePropertiesJ\x04\x08\x03\x10\x04J\x04\x08\x05\x10\x06\"V\n\rDirectoryNode\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x37\n\x06\x64igest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\"{\n\x0bSymlinkNode\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06target\x18\x02 \x01(\t\x12H\n\x0fnode_properties\x18\x04 \x01(\x0b\x32/.build.bazel.remote.execution.v2.NodePropertiesJ\x04\x08\x03\x10\x04\"*\n\x06\x44igest\x12\x0c\n\x04hash\x18\x01 \x01(\t\x12\x12\n\nsize_bytes\x18\x02 \x01(\x03\"\xdd\x05\n\x16\x45xecutedActionMetadata\x12\x0e\n\x06worker\x18\x01 \x01(\t\x12\x34\n\x10queued_timestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12:\n\x16worker_start_timestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12>\n\x1aworker_completed_timestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12?\n\x1binput_fetch_start_timestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x43\n\x1finput_fetch_completed_timestamp\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12=\n\x19\x65xecution_start_timestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x41\n\x1d\x65xecution_completed_timestamp\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12=\n\x1avirtual_execution_duration\x18\x0c \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x41\n\x1doutput_upload_start_timestamp\x18\t \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x45\n!output_upload_completed_timestamp\x18\n \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x30\n\x12\x61uxiliary_metadata\x18\x0b \x03(\x0b\x32\x14.google.protobuf.Any\"\xa7\x05\n\x0c\x41\x63tionResult\x12\x41\n\x0coutput_files\x18\x02 \x03(\x0b\x32+.build.bazel.remote.execution.v2.OutputFile\x12P\n\x14output_file_symlinks\x18\n \x03(\x0b\x32..build.bazel.remote.execution.v2.OutputSymlinkB\x02\x18\x01\x12G\n\x0foutput_symlinks\x18\x0c \x03(\x0b\x32..build.bazel.remote.execution.v2.OutputSymlink\x12L\n\x12output_directories\x18\x03 \x03(\x0b\x32\x30.build.bazel.remote.execution.v2.OutputDirectory\x12U\n\x19output_directory_symlinks\x18\x0b \x03(\x0b\x32..build.bazel.remote.execution.v2.OutputSymlinkB\x02\x18\x01\x12\x11\n\texit_code\x18\x04 \x01(\x05\x12\x12\n\nstdout_raw\x18\x05 \x01(\x0c\x12>\n\rstdout_digest\x18\x06 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x12\n\nstderr_raw\x18\x07 \x01(\x0c\x12>\n\rstderr_digest\x18\x08 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12S\n\x12\x65xecution_metadata\x18\t \x01(\x0b\x32\x37.build.bazel.remote.execution.v2.ExecutedActionMetadataJ\x04\x08\x01\x10\x02\"\xd2\x01\n\nOutputFile\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x37\n\x06\x64igest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x15\n\ris_executable\x18\x04 \x01(\x08\x12\x10\n\x08\x63ontents\x18\x05 \x01(\x0c\x12H\n\x0fnode_properties\x18\x07 \x01(\x0b\x32/.build.bazel.remote.execution.v2.NodePropertiesJ\x04\x08\x03\x10\x04J\x04\x08\x06\x10\x07\"~\n\x04Tree\x12\x38\n\x04root\x18\x01 \x01(\x0b\x32*.build.bazel.remote.execution.v2.Directory\x12<\n\x08\x63hildren\x18\x02 \x03(\x0b\x32*.build.bazel.remote.execution.v2.Directory\"\xcc\x01\n\x0fOutputDirectory\x12\x0c\n\x04path\x18\x01 \x01(\t\x12<\n\x0btree_digest\x18\x03 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x1f\n\x17is_topologically_sorted\x18\x04 \x01(\x08\x12\x46\n\x15root_directory_digest\x18\x05 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.DigestJ\x04\x08\x02\x10\x03\"}\n\rOutputSymlink\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0e\n\x06target\x18\x02 \x01(\t\x12H\n\x0fnode_properties\x18\x04 \x01(\x0b\x32/.build.bazel.remote.execution.v2.NodePropertiesJ\x04\x08\x03\x10\x04\"#\n\x0f\x45xecutionPolicy\x12\x10\n\x08priority\x18\x01 \x01(\x05\"&\n\x12ResultsCachePolicy\x12\x10\n\x08priority\x18\x01 \x01(\x05\"\xce\x03\n\x0e\x45xecuteRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12\x19\n\x11skip_cache_lookup\x18\x03 \x01(\x08\x12>\n\raction_digest\x18\x06 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12J\n\x10\x65xecution_policy\x18\x07 \x01(\x0b\x32\x30.build.bazel.remote.execution.v2.ExecutionPolicy\x12Q\n\x14results_cache_policy\x18\x08 \x01(\x0b\x32\x33.build.bazel.remote.execution.v2.ResultsCachePolicy\x12N\n\x0f\x64igest_function\x18\t \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\x12\x15\n\rinline_stdout\x18\n \x01(\x08\x12\x15\n\rinline_stderr\x18\x0b \x01(\x08\x12\x1b\n\x13inline_output_files\x18\x0c \x03(\tJ\x04\x08\x02\x10\x03J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06\"Z\n\x07LogFile\x12\x37\n\x06\x64igest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x16\n\x0ehuman_readable\x18\x02 \x01(\x08\"\xd0\x02\n\x0f\x45xecuteResponse\x12=\n\x06result\x18\x01 \x01(\x0b\x32-.build.bazel.remote.execution.v2.ActionResult\x12\x15\n\rcached_result\x18\x02 \x01(\x08\x12\"\n\x06status\x18\x03 \x01(\x0b\x32\x12.google.rpc.Status\x12U\n\x0bserver_logs\x18\x04 \x03(\x0b\x32@.build.bazel.remote.execution.v2.ExecuteResponse.ServerLogsEntry\x12\x0f\n\x07message\x18\x05 \x01(\t\x1a[\n\x0fServerLogsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x37\n\x05value\x18\x02 \x01(\x0b\x32(.build.bazel.remote.execution.v2.LogFile:\x02\x38\x01\"a\n\x0e\x45xecutionStage\"O\n\x05Value\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0f\n\x0b\x43\x41\x43HE_CHECK\x10\x01\x12\n\n\x06QUEUED\x10\x02\x12\r\n\tEXECUTING\x10\x03\x12\r\n\tCOMPLETED\x10\x04\"\x85\x03\n\x18\x45xecuteOperationMetadata\x12\x44\n\x05stage\x18\x01 \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.ExecutionStage.Value\x12>\n\raction_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x1a\n\x12stdout_stream_name\x18\x03 \x01(\t\x12\x1a\n\x12stderr_stream_name\x18\x04 \x01(\t\x12[\n\x1apartial_execution_metadata\x18\x05 \x01(\x0b\x32\x37.build.bazel.remote.execution.v2.ExecutedActionMetadata\x12N\n\x0f\x64igest_function\x18\x06 \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\"$\n\x14WaitExecutionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x8a\x02\n\x16GetActionResultRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12>\n\raction_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x15\n\rinline_stdout\x18\x03 \x01(\x08\x12\x15\n\rinline_stderr\x18\x04 \x01(\x08\x12\x1b\n\x13inline_output_files\x18\x05 \x03(\t\x12N\n\x0f\x64igest_function\x18\x06 \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\"\xdb\x02\n\x19UpdateActionResultRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12>\n\raction_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x44\n\raction_result\x18\x03 \x01(\x0b\x32-.build.bazel.remote.execution.v2.ActionResult\x12Q\n\x14results_cache_policy\x18\x04 \x01(\x0b\x32\x33.build.bazel.remote.execution.v2.ResultsCachePolicy\x12N\n\x0f\x64igest_function\x18\x05 \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\"\xbf\x01\n\x17\x46indMissingBlobsRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12=\n\x0c\x62lob_digests\x18\x02 \x03(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12N\n\x0f\x64igest_function\x18\x03 \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\"a\n\x18\x46indMissingBlobsResponse\x12\x45\n\x14missing_blob_digests\x18\x02 \x03(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\"\xee\x02\n\x17\x42\x61tchUpdateBlobsRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12R\n\x08requests\x18\x02 \x03(\x0b\x32@.build.bazel.remote.execution.v2.BatchUpdateBlobsRequest.Request\x12N\n\x0f\x64igest_function\x18\x05 \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\x1a\x97\x01\n\x07Request\x12\x37\n\x06\x64igest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x45\n\ncompressor\x18\x03 \x01(\x0e\x32\x31.build.bazel.remote.execution.v2.Compressor.Value\"\xda\x01\n\x18\x42\x61tchUpdateBlobsResponse\x12U\n\tresponses\x18\x01 \x03(\x0b\x32\x42.build.bazel.remote.execution.v2.BatchUpdateBlobsResponse.Response\x1ag\n\x08Response\x12\x37\n\x06\x64igest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\"\n\x06status\x18\x02 \x01(\x0b\x32\x12.google.rpc.Status\"\x8b\x02\n\x15\x42\x61tchReadBlobsRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12\x38\n\x07\x64igests\x18\x02 \x03(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12Q\n\x16\x61\x63\x63\x65ptable_compressors\x18\x03 \x03(\x0e\x32\x31.build.bazel.remote.execution.v2.Compressor.Value\x12N\n\x0f\x64igest_function\x18\x04 \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\"\xac\x02\n\x16\x42\x61tchReadBlobsResponse\x12S\n\tresponses\x18\x01 \x03(\x0b\x32@.build.bazel.remote.execution.v2.BatchReadBlobsResponse.Response\x1a\xbc\x01\n\x08Response\x12\x37\n\x06\x64igest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x45\n\ncompressor\x18\x04 \x01(\x0e\x32\x31.build.bazel.remote.execution.v2.Compressor.Value\x12\"\n\x06status\x18\x03 \x01(\x0b\x32\x12.google.rpc.Status\"\xdc\x01\n\x0eGetTreeRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12<\n\x0broot_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x11\n\tpage_size\x18\x03 \x01(\x05\x12\x12\n\npage_token\x18\x04 \x01(\t\x12N\n\x0f\x64igest_function\x18\x05 \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\"k\n\x0fGetTreeResponse\x12?\n\x0b\x64irectories\x18\x01 \x03(\x0b\x32*.build.bazel.remote.execution.v2.Directory\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"\x8b\x02\n\x10SplitBlobRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12<\n\x0b\x62lob_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12N\n\x0f\x64igest_function\x18\x03 \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\x12R\n\x11\x63hunking_function\x18\x04 \x01(\x0e\x32\x37.build.bazel.remote.execution.v2.ChunkingFunction.Value\"\xa7\x01\n\x11SplitBlobResponse\x12>\n\rchunk_digests\x18\x01 \x03(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12R\n\x11\x63hunking_function\x18\x02 \x01(\x0e\x32\x37.build.bazel.remote.execution.v2.ChunkingFunction.Value\"\xcc\x02\n\x11SpliceBlobRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12<\n\x0b\x62lob_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12>\n\rchunk_digests\x18\x03 \x03(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12N\n\x0f\x64igest_function\x18\x04 \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\x12R\n\x11\x63hunking_function\x18\x05 \x01(\x0e\x32\x37.build.bazel.remote.execution.v2.ChunkingFunction.Value\"R\n\x12SpliceBlobResponse\x12<\n\x0b\x62lob_digest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\"/\n\x16GetCapabilitiesRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\"\xe3\x02\n\x12ServerCapabilities\x12N\n\x12\x63\x61\x63he_capabilities\x18\x01 \x01(\x0b\x32\x32.build.bazel.remote.execution.v2.CacheCapabilities\x12V\n\x16\x65xecution_capabilities\x18\x02 \x01(\x0b\x32\x36.build.bazel.remote.execution.v2.ExecutionCapabilities\x12:\n\x16\x64\x65precated_api_version\x18\x03 \x01(\x0b\x32\x1a.build.bazel.semver.SemVer\x12\x33\n\x0flow_api_version\x18\x04 \x01(\x0b\x32\x1a.build.bazel.semver.SemVer\x12\x34\n\x10high_api_version\x18\x05 \x01(\x0b\x32\x1a.build.bazel.semver.SemVer\"\x9d\x01\n\x0e\x44igestFunction\"\x8a\x01\n\x05Value\x12\x0b\n\x07UNKNOWN\x10\x00\x12\n\n\x06SHA256\x10\x01\x12\x08\n\x04SHA1\x10\x02\x12\x07\n\x03MD5\x10\x03\x12\x07\n\x03VSO\x10\x04\x12\n\n\x06SHA384\x10\x05\x12\n\n\x06SHA512\x10\x06\x12\x0b\n\x07MURMUR3\x10\x07\x12\x0e\n\nSHA256TREE\x10\x08\x12\n\n\x06\x42LAKE3\x10\t\x12\x0b\n\x07GITSHA1\x10\n\"L\n\x10\x43hunkingFunction\"8\n\x05Value\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x11\n\rFAST_CDC_2020\x10\x01\x12\x0f\n\x0bREP_MAX_CDC\x10\x02\"7\n\x1d\x41\x63tionCacheUpdateCapabilities\x12\x16\n\x0eupdate_enabled\x18\x01 \x01(\x08\"\xac\x01\n\x14PriorityCapabilities\x12W\n\npriorities\x18\x01 \x03(\x0b\x32\x43.build.bazel.remote.execution.v2.PriorityCapabilities.PriorityRange\x1a;\n\rPriorityRange\x12\x14\n\x0cmin_priority\x18\x01 \x01(\x05\x12\x14\n\x0cmax_priority\x18\x02 \x01(\x05\"P\n\x1bSymlinkAbsolutePathStrategy\"1\n\x05Value\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0e\n\nDISALLOWED\x10\x01\x12\x0b\n\x07\x41LLOWED\x10\x02\"F\n\nCompressor\"8\n\x05Value\x12\x0c\n\x08IDENTITY\x10\x00\x12\x08\n\x04ZSTD\x10\x01\x12\x0b\n\x07\x44\x45\x46LATE\x10\x02\x12\n\n\x06\x42ROTLI\x10\x03\"\xe5\x06\n\x11\x43\x61\x63heCapabilities\x12O\n\x10\x64igest_functions\x18\x01 \x03(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\x12h\n action_cache_update_capabilities\x18\x02 \x01(\x0b\x32>.build.bazel.remote.execution.v2.ActionCacheUpdateCapabilities\x12Z\n\x1b\x63\x61\x63he_priority_capabilities\x18\x03 \x01(\x0b\x32\x35.build.bazel.remote.execution.v2.PriorityCapabilities\x12\"\n\x1amax_batch_total_size_bytes\x18\x04 \x01(\x03\x12j\n\x1esymlink_absolute_path_strategy\x18\x05 \x01(\x0e\x32\x42.build.bazel.remote.execution.v2.SymlinkAbsolutePathStrategy.Value\x12P\n\x15supported_compressors\x18\x06 \x03(\x0e\x32\x31.build.bazel.remote.execution.v2.Compressor.Value\x12]\n\"supported_batch_update_compressors\x18\x07 \x03(\x0e\x32\x31.build.bazel.remote.execution.v2.Compressor.Value\x12\x1f\n\x17max_cas_blob_size_bytes\x18\x08 \x01(\x03\x12\x1a\n\x12split_blob_support\x18\t \x01(\x08\x12\x1b\n\x13splice_blob_support\x18\n \x01(\x08\x12P\n\x14\x66\x61st_cdc_2020_params\x18\x0b \x01(\x0b\x32\x32.build.bazel.remote.execution.v2.FastCdc2020Params\x12L\n\x12rep_max_cdc_params\x18\x0c \x01(\x0b\x32\x30.build.bazel.remote.execution.v2.RepMaxCdcParams\"?\n\x11\x46\x61stCdc2020Params\x12\x1c\n\x14\x61vg_chunk_size_bytes\x18\x01 \x01(\x04\x12\x0c\n\x04seed\x18\x02 \x01(\r\"K\n\x0fRepMaxCdcParams\x12\x1c\n\x14min_chunk_size_bytes\x18\x01 \x01(\x04\x12\x1a\n\x12horizon_size_bytes\x18\x02 \x01(\x04\"\xd1\x02\n\x15\x45xecutionCapabilities\x12N\n\x0f\x64igest_function\x18\x01 \x01(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\x12\x14\n\x0c\x65xec_enabled\x18\x02 \x01(\x08\x12^\n\x1f\x65xecution_priority_capabilities\x18\x03 \x01(\x0b\x32\x35.build.bazel.remote.execution.v2.PriorityCapabilities\x12!\n\x19supported_node_properties\x18\x04 \x03(\t\x12O\n\x10\x64igest_functions\x18\x05 \x03(\x0e\x32\x35.build.bazel.remote.execution.v2.DigestFunction.Value\"6\n\x0bToolDetails\x12\x11\n\ttool_name\x18\x01 \x01(\t\x12\x14\n\x0ctool_version\x18\x02 \x01(\t\"\xed\x01\n\x0fRequestMetadata\x12\x42\n\x0ctool_details\x18\x01 \x01(\x0b\x32,.build.bazel.remote.execution.v2.ToolDetails\x12\x11\n\taction_id\x18\x02 \x01(\t\x12\x1a\n\x12tool_invocation_id\x18\x03 \x01(\t\x12!\n\x19\x63orrelated_invocations_id\x18\x04 \x01(\t\x12\x17\n\x0f\x61\x63tion_mnemonic\x18\x05 \x01(\t\x12\x11\n\ttarget_id\x18\x06 \x01(\t\x12\x18\n\x10\x63onfiguration_id\x18\x07 \x01(\t2\xb9\x02\n\tExecution\x12\x8e\x01\n\x07\x45xecute\x12/.build.bazel.remote.execution.v2.ExecuteRequest\x1a\x1d.google.longrunning.Operation\"1\x82\xd3\xe4\x93\x02+\"&/v2/{instance_name=**}/actions:execute:\x01*0\x01\x12\x9a\x01\n\rWaitExecution\x12\x35.build.bazel.remote.execution.v2.WaitExecutionRequest\x1a\x1d.google.longrunning.Operation\"1\x82\xd3\xe4\x93\x02+\"&/v2/{name=operations/**}:waitExecution:\x01*0\x01\x32\xd6\x03\n\x0b\x41\x63tionCache\x12\xd7\x01\n\x0fGetActionResult\x12\x37.build.bazel.remote.execution.v2.GetActionResultRequest\x1a-.build.bazel.remote.execution.v2.ActionResult\"\\\x82\xd3\xe4\x93\x02V\x12T/v2/{instance_name=**}/actionResults/{action_digest.hash}/{action_digest.size_bytes}\x12\xec\x01\n\x12UpdateActionResult\x12:.build.bazel.remote.execution.v2.UpdateActionResultRequest\x1a-.build.bazel.remote.execution.v2.ActionResult\"k\x82\xd3\xe4\x93\x02\x65\x1aT/v2/{instance_name=**}/actionResults/{action_digest.hash}/{action_digest.size_bytes}:\raction_result2\x98\t\n\x19\x43ontentAddressableStorage\x12\xbc\x01\n\x10\x46indMissingBlobs\x12\x38.build.bazel.remote.execution.v2.FindMissingBlobsRequest\x1a\x39.build.bazel.remote.execution.v2.FindMissingBlobsResponse\"3\x82\xd3\xe4\x93\x02-\"(/v2/{instance_name=**}/blobs:findMissing:\x01*\x12\xbc\x01\n\x10\x42\x61tchUpdateBlobs\x12\x38.build.bazel.remote.execution.v2.BatchUpdateBlobsRequest\x1a\x39.build.bazel.remote.execution.v2.BatchUpdateBlobsResponse\"3\x82\xd3\xe4\x93\x02-\"(/v2/{instance_name=**}/blobs:batchUpdate:\x01*\x12\xb4\x01\n\x0e\x42\x61tchReadBlobs\x12\x36.build.bazel.remote.execution.v2.BatchReadBlobsRequest\x1a\x37.build.bazel.remote.execution.v2.BatchReadBlobsResponse\"1\x82\xd3\xe4\x93\x02+\"&/v2/{instance_name=**}/blobs:batchRead:\x01*\x12\xc8\x01\n\x07GetTree\x12/.build.bazel.remote.execution.v2.GetTreeRequest\x1a\x30.build.bazel.remote.execution.v2.GetTreeResponse\"X\x82\xd3\xe4\x93\x02R\x12P/v2/{instance_name=**}/blobs/{root_digest.hash}/{root_digest.size_bytes}:getTree0\x01\x12\xce\x01\n\tSplitBlob\x12\x31.build.bazel.remote.execution.v2.SplitBlobRequest\x1a\x32.build.bazel.remote.execution.v2.SplitBlobResponse\"Z\x82\xd3\xe4\x93\x02T\x12R/v2/{instance_name=**}/blobs/{blob_digest.hash}/{blob_digest.size_bytes}:splitBlob\x12\xa9\x01\n\nSpliceBlob\x12\x32.build.bazel.remote.execution.v2.SpliceBlobRequest\x1a\x33.build.bazel.remote.execution.v2.SpliceBlobResponse\"2\x82\xd3\xe4\x93\x02,\"\'/v2/{instance_name=**}/blobs:spliceBlob:\x01*2\xbd\x01\n\x0c\x43\x61pabilities\x12\xac\x01\n\x0fGetCapabilities\x12\x37.build.bazel.remote.execution.v2.GetCapabilitiesRequest\x1a\x33.build.bazel.remote.execution.v2.ServerCapabilities\"+\x82\xd3\xe4\x93\x02%\x12#/v2/{instance_name=**}/capabilitiesB\xb4\x01\n\x1f\x62uild.bazel.remote.execution.v2B\x14RemoteExecutionProtoP\x01ZQgithub.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2;remoteexecution\xa2\x02\x03REX\xaa\x02\x1f\x42uild.Bazel.Remote.Execution.V2b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'build.bazel.remote.execution.v2.remote_execution_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\037build.bazel.remote.execution.v2B\024RemoteExecutionProtoP\001ZQgithub.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2;remoteexecution\242\002\003REX\252\002\037Build.Bazel.Remote.Execution.V2' - _globals['_COMMAND'].fields_by_name['output_files']._loaded_options = None - _globals['_COMMAND'].fields_by_name['output_files']._serialized_options = b'\030\001' - _globals['_COMMAND'].fields_by_name['output_directories']._loaded_options = None - _globals['_COMMAND'].fields_by_name['output_directories']._serialized_options = b'\030\001' - _globals['_COMMAND'].fields_by_name['platform']._loaded_options = None - _globals['_COMMAND'].fields_by_name['platform']._serialized_options = b'\030\001' - _globals['_ACTIONRESULT'].fields_by_name['output_file_symlinks']._loaded_options = None - _globals['_ACTIONRESULT'].fields_by_name['output_file_symlinks']._serialized_options = b'\030\001' - _globals['_ACTIONRESULT'].fields_by_name['output_directory_symlinks']._loaded_options = None - _globals['_ACTIONRESULT'].fields_by_name['output_directory_symlinks']._serialized_options = b'\030\001' - _globals['_EXECUTERESPONSE_SERVERLOGSENTRY']._loaded_options = None - _globals['_EXECUTERESPONSE_SERVERLOGSENTRY']._serialized_options = b'8\001' - _globals['_EXECUTION'].methods_by_name['Execute']._loaded_options = None - _globals['_EXECUTION'].methods_by_name['Execute']._serialized_options = b'\202\323\344\223\002+\"&/v2/{instance_name=**}/actions:execute:\001*' - _globals['_EXECUTION'].methods_by_name['WaitExecution']._loaded_options = None - _globals['_EXECUTION'].methods_by_name['WaitExecution']._serialized_options = b'\202\323\344\223\002+\"&/v2/{name=operations/**}:waitExecution:\001*' - _globals['_ACTIONCACHE'].methods_by_name['GetActionResult']._loaded_options = None - _globals['_ACTIONCACHE'].methods_by_name['GetActionResult']._serialized_options = b'\202\323\344\223\002V\022T/v2/{instance_name=**}/actionResults/{action_digest.hash}/{action_digest.size_bytes}' - _globals['_ACTIONCACHE'].methods_by_name['UpdateActionResult']._loaded_options = None - _globals['_ACTIONCACHE'].methods_by_name['UpdateActionResult']._serialized_options = b'\202\323\344\223\002e\032T/v2/{instance_name=**}/actionResults/{action_digest.hash}/{action_digest.size_bytes}:\raction_result' - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['FindMissingBlobs']._loaded_options = None - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['FindMissingBlobs']._serialized_options = b'\202\323\344\223\002-\"(/v2/{instance_name=**}/blobs:findMissing:\001*' - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['BatchUpdateBlobs']._loaded_options = None - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['BatchUpdateBlobs']._serialized_options = b'\202\323\344\223\002-\"(/v2/{instance_name=**}/blobs:batchUpdate:\001*' - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['BatchReadBlobs']._loaded_options = None - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['BatchReadBlobs']._serialized_options = b'\202\323\344\223\002+\"&/v2/{instance_name=**}/blobs:batchRead:\001*' - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['GetTree']._loaded_options = None - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['GetTree']._serialized_options = b'\202\323\344\223\002R\022P/v2/{instance_name=**}/blobs/{root_digest.hash}/{root_digest.size_bytes}:getTree' - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['SplitBlob']._loaded_options = None - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['SplitBlob']._serialized_options = b'\202\323\344\223\002T\022R/v2/{instance_name=**}/blobs/{blob_digest.hash}/{blob_digest.size_bytes}:splitBlob' - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['SpliceBlob']._loaded_options = None - _globals['_CONTENTADDRESSABLESTORAGE'].methods_by_name['SpliceBlob']._serialized_options = b'\202\323\344\223\002,\"\'/v2/{instance_name=**}/blobs:spliceBlob:\001*' - _globals['_CAPABILITIES'].methods_by_name['GetCapabilities']._loaded_options = None - _globals['_CAPABILITIES'].methods_by_name['GetCapabilities']._serialized_options = b'\202\323\344\223\002%\022#/v2/{instance_name=**}/capabilities' - _globals['_ACTION']._serialized_start=341 - _globals['_ACTION']._serialized_end=635 - _globals['_COMMAND']._serialized_start=638 - _globals['_COMMAND']._serialized_end=1196 - _globals['_COMMAND_ENVIRONMENTVARIABLE']._serialized_start=1062 - _globals['_COMMAND_ENVIRONMENTVARIABLE']._serialized_end=1112 - _globals['_COMMAND_OUTPUTDIRECTORYFORMAT']._serialized_start=1114 - _globals['_COMMAND_OUTPUTDIRECTORYFORMAT']._serialized_end=1196 - _globals['_PLATFORM']._serialized_start=1198 - _globals['_PLATFORM']._serialized_end=1321 - _globals['_PLATFORM_PROPERTY']._serialized_start=1282 - _globals['_PLATFORM_PROPERTY']._serialized_end=1321 - _globals['_DIRECTORY']._serialized_start=1324 - _globals['_DIRECTORY']._serialized_end=1606 - _globals['_NODEPROPERTY']._serialized_start=1608 - _globals['_NODEPROPERTY']._serialized_end=1651 - _globals['_NODEPROPERTIES']._serialized_start=1654 - _globals['_NODEPROPERTIES']._serialized_end=1829 - _globals['_FILENODE']._serialized_start=1832 - _globals['_FILENODE']._serialized_end=2022 - _globals['_DIRECTORYNODE']._serialized_start=2024 - _globals['_DIRECTORYNODE']._serialized_end=2110 - _globals['_SYMLINKNODE']._serialized_start=2112 - _globals['_SYMLINKNODE']._serialized_end=2235 - _globals['_DIGEST']._serialized_start=2237 - _globals['_DIGEST']._serialized_end=2279 - _globals['_EXECUTEDACTIONMETADATA']._serialized_start=2282 - _globals['_EXECUTEDACTIONMETADATA']._serialized_end=3015 - _globals['_ACTIONRESULT']._serialized_start=3018 - _globals['_ACTIONRESULT']._serialized_end=3697 - _globals['_OUTPUTFILE']._serialized_start=3700 - _globals['_OUTPUTFILE']._serialized_end=3910 - _globals['_TREE']._serialized_start=3912 - _globals['_TREE']._serialized_end=4038 - _globals['_OUTPUTDIRECTORY']._serialized_start=4041 - _globals['_OUTPUTDIRECTORY']._serialized_end=4245 - _globals['_OUTPUTSYMLINK']._serialized_start=4247 - _globals['_OUTPUTSYMLINK']._serialized_end=4372 - _globals['_EXECUTIONPOLICY']._serialized_start=4374 - _globals['_EXECUTIONPOLICY']._serialized_end=4409 - _globals['_RESULTSCACHEPOLICY']._serialized_start=4411 - _globals['_RESULTSCACHEPOLICY']._serialized_end=4449 - _globals['_EXECUTEREQUEST']._serialized_start=4452 - _globals['_EXECUTEREQUEST']._serialized_end=4914 - _globals['_LOGFILE']._serialized_start=4916 - _globals['_LOGFILE']._serialized_end=5006 - _globals['_EXECUTERESPONSE']._serialized_start=5009 - _globals['_EXECUTERESPONSE']._serialized_end=5345 - _globals['_EXECUTERESPONSE_SERVERLOGSENTRY']._serialized_start=5254 - _globals['_EXECUTERESPONSE_SERVERLOGSENTRY']._serialized_end=5345 - _globals['_EXECUTIONSTAGE']._serialized_start=5347 - _globals['_EXECUTIONSTAGE']._serialized_end=5444 - _globals['_EXECUTIONSTAGE_VALUE']._serialized_start=5365 - _globals['_EXECUTIONSTAGE_VALUE']._serialized_end=5444 - _globals['_EXECUTEOPERATIONMETADATA']._serialized_start=5447 - _globals['_EXECUTEOPERATIONMETADATA']._serialized_end=5836 - _globals['_WAITEXECUTIONREQUEST']._serialized_start=5838 - _globals['_WAITEXECUTIONREQUEST']._serialized_end=5874 - _globals['_GETACTIONRESULTREQUEST']._serialized_start=5877 - _globals['_GETACTIONRESULTREQUEST']._serialized_end=6143 - _globals['_UPDATEACTIONRESULTREQUEST']._serialized_start=6146 - _globals['_UPDATEACTIONRESULTREQUEST']._serialized_end=6493 - _globals['_FINDMISSINGBLOBSREQUEST']._serialized_start=6496 - _globals['_FINDMISSINGBLOBSREQUEST']._serialized_end=6687 - _globals['_FINDMISSINGBLOBSRESPONSE']._serialized_start=6689 - _globals['_FINDMISSINGBLOBSRESPONSE']._serialized_end=6786 - _globals['_BATCHUPDATEBLOBSREQUEST']._serialized_start=6789 - _globals['_BATCHUPDATEBLOBSREQUEST']._serialized_end=7155 - _globals['_BATCHUPDATEBLOBSREQUEST_REQUEST']._serialized_start=7004 - _globals['_BATCHUPDATEBLOBSREQUEST_REQUEST']._serialized_end=7155 - _globals['_BATCHUPDATEBLOBSRESPONSE']._serialized_start=7158 - _globals['_BATCHUPDATEBLOBSRESPONSE']._serialized_end=7376 - _globals['_BATCHUPDATEBLOBSRESPONSE_RESPONSE']._serialized_start=7273 - _globals['_BATCHUPDATEBLOBSRESPONSE_RESPONSE']._serialized_end=7376 - _globals['_BATCHREADBLOBSREQUEST']._serialized_start=7379 - _globals['_BATCHREADBLOBSREQUEST']._serialized_end=7646 - _globals['_BATCHREADBLOBSRESPONSE']._serialized_start=7649 - _globals['_BATCHREADBLOBSRESPONSE']._serialized_end=7949 - _globals['_BATCHREADBLOBSRESPONSE_RESPONSE']._serialized_start=7761 - _globals['_BATCHREADBLOBSRESPONSE_RESPONSE']._serialized_end=7949 - _globals['_GETTREEREQUEST']._serialized_start=7952 - _globals['_GETTREEREQUEST']._serialized_end=8172 - _globals['_GETTREERESPONSE']._serialized_start=8174 - _globals['_GETTREERESPONSE']._serialized_end=8281 - _globals['_SPLITBLOBREQUEST']._serialized_start=8284 - _globals['_SPLITBLOBREQUEST']._serialized_end=8551 - _globals['_SPLITBLOBRESPONSE']._serialized_start=8554 - _globals['_SPLITBLOBRESPONSE']._serialized_end=8721 - _globals['_SPLICEBLOBREQUEST']._serialized_start=8724 - _globals['_SPLICEBLOBREQUEST']._serialized_end=9056 - _globals['_SPLICEBLOBRESPONSE']._serialized_start=9058 - _globals['_SPLICEBLOBRESPONSE']._serialized_end=9140 - _globals['_GETCAPABILITIESREQUEST']._serialized_start=9142 - _globals['_GETCAPABILITIESREQUEST']._serialized_end=9189 - _globals['_SERVERCAPABILITIES']._serialized_start=9192 - _globals['_SERVERCAPABILITIES']._serialized_end=9547 - _globals['_DIGESTFUNCTION']._serialized_start=9550 - _globals['_DIGESTFUNCTION']._serialized_end=9707 - _globals['_DIGESTFUNCTION_VALUE']._serialized_start=9569 - _globals['_DIGESTFUNCTION_VALUE']._serialized_end=9707 - _globals['_CHUNKINGFUNCTION']._serialized_start=9709 - _globals['_CHUNKINGFUNCTION']._serialized_end=9785 - _globals['_CHUNKINGFUNCTION_VALUE']._serialized_start=9729 - _globals['_CHUNKINGFUNCTION_VALUE']._serialized_end=9785 - _globals['_ACTIONCACHEUPDATECAPABILITIES']._serialized_start=9787 - _globals['_ACTIONCACHEUPDATECAPABILITIES']._serialized_end=9842 - _globals['_PRIORITYCAPABILITIES']._serialized_start=9845 - _globals['_PRIORITYCAPABILITIES']._serialized_end=10017 - _globals['_PRIORITYCAPABILITIES_PRIORITYRANGE']._serialized_start=9958 - _globals['_PRIORITYCAPABILITIES_PRIORITYRANGE']._serialized_end=10017 - _globals['_SYMLINKABSOLUTEPATHSTRATEGY']._serialized_start=10019 - _globals['_SYMLINKABSOLUTEPATHSTRATEGY']._serialized_end=10099 - _globals['_SYMLINKABSOLUTEPATHSTRATEGY_VALUE']._serialized_start=10050 - _globals['_SYMLINKABSOLUTEPATHSTRATEGY_VALUE']._serialized_end=10099 - _globals['_COMPRESSOR']._serialized_start=10101 - _globals['_COMPRESSOR']._serialized_end=10171 - _globals['_COMPRESSOR_VALUE']._serialized_start=10115 - _globals['_COMPRESSOR_VALUE']._serialized_end=10171 - _globals['_CACHECAPABILITIES']._serialized_start=10174 - _globals['_CACHECAPABILITIES']._serialized_end=11043 - _globals['_FASTCDC2020PARAMS']._serialized_start=11045 - _globals['_FASTCDC2020PARAMS']._serialized_end=11108 - _globals['_REPMAXCDCPARAMS']._serialized_start=11110 - _globals['_REPMAXCDCPARAMS']._serialized_end=11185 - _globals['_EXECUTIONCAPABILITIES']._serialized_start=11188 - _globals['_EXECUTIONCAPABILITIES']._serialized_end=11525 - _globals['_TOOLDETAILS']._serialized_start=11527 - _globals['_TOOLDETAILS']._serialized_end=11581 - _globals['_REQUESTMETADATA']._serialized_start=11584 - _globals['_REQUESTMETADATA']._serialized_end=11821 - _globals['_EXECUTION']._serialized_start=11824 - _globals['_EXECUTION']._serialized_end=12137 - _globals['_ACTIONCACHE']._serialized_start=12140 - _globals['_ACTIONCACHE']._serialized_end=12610 - _globals['_CONTENTADDRESSABLESTORAGE']._serialized_start=12613 - _globals['_CONTENTADDRESSABLESTORAGE']._serialized_end=13789 - _globals['_CAPABILITIES']._serialized_start=13792 - _globals['_CAPABILITIES']._serialized_end=13981 -# @@protoc_insertion_point(module_scope) diff --git a/benchmark/proto_gen/build/bazel/remote/execution/v2/remote_execution_pb2_grpc.py b/benchmark/proto_gen/build/bazel/remote/execution/v2/remote_execution_pb2_grpc.py deleted file mode 100644 index a9ca0fd..0000000 --- a/benchmark/proto_gen/build/bazel/remote/execution/v2/remote_execution_pb2_grpc.py +++ /dev/null @@ -1,1476 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - -from build.bazel.remote.execution.v2 import remote_execution_pb2 as build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2 -from google.longrunning import operations_pb2 as google_dot_longrunning_dot_operations__pb2 - -GRPC_GENERATED_VERSION = '1.78.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in build/bazel/remote/execution/v2/remote_execution_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) - - -class ExecutionStub(object): - """The Remote Execution API is used to execute an - [Action][build.bazel.remote.execution.v2.Action] on the remote - workers. - - As with other services in the Remote Execution API, any call may return an - error with a [RetryInfo][google.rpc.RetryInfo] error detail providing - information about when the client should retry the request; clients SHOULD - respect the information provided. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.Execute = channel.unary_stream( - '/build.bazel.remote.execution.v2.Execution/Execute', - request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ExecuteRequest.SerializeToString, - response_deserializer=google_dot_longrunning_dot_operations__pb2.Operation.FromString, - _registered_method=True) - self.WaitExecution = channel.unary_stream( - '/build.bazel.remote.execution.v2.Execution/WaitExecution', - request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.WaitExecutionRequest.SerializeToString, - response_deserializer=google_dot_longrunning_dot_operations__pb2.Operation.FromString, - _registered_method=True) - - -class ExecutionServicer(object): - """The Remote Execution API is used to execute an - [Action][build.bazel.remote.execution.v2.Action] on the remote - workers. - - As with other services in the Remote Execution API, any call may return an - error with a [RetryInfo][google.rpc.RetryInfo] error detail providing - information about when the client should retry the request; clients SHOULD - respect the information provided. - """ - - def Execute(self, request, context): - """Execute an action remotely. - - In order to execute an action, the client must first upload all of the - inputs, the - [Command][build.bazel.remote.execution.v2.Command] to run, and the - [Action][build.bazel.remote.execution.v2.Action] into the - [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage]. - It then calls `Execute` with an `action_digest` referring to them. The - server will run the action and eventually return the result. - - The input `Action`'s fields MUST meet the various canonicalization - requirements specified in the documentation for their types so that it has - the same digest as other logically equivalent `Action`s. The server MAY - enforce the requirements and return errors if a non-canonical input is - received. It MAY also proceed without verifying some or all of the - requirements, such as for performance reasons. If the server does not - verify the requirement, then it will treat the `Action` as distinct from - another logically equivalent action if they hash differently. - - Returns a stream of - [google.longrunning.Operation][google.longrunning.Operation] messages - describing the resulting execution, with eventual `response` - [ExecuteResponse][build.bazel.remote.execution.v2.ExecuteResponse]. The - `metadata` on the operation is of type - [ExecuteOperationMetadata][build.bazel.remote.execution.v2.ExecuteOperationMetadata]. - - If the client remains connected after the first response is returned after - the server, then updates are streamed as if the client had called - [WaitExecution][build.bazel.remote.execution.v2.Execution.WaitExecution] - until the execution completes or the request reaches an error. The - operation can also be queried using [Operations - API][google.longrunning.Operations.GetOperation]. - - The server NEED NOT implement other methods or functionality of the - Operations API. - - Errors discovered during creation of the `Operation` will be reported - as gRPC Status errors, while errors that occurred while running the - action will be reported in the `status` field of the `ExecuteResponse`. The - server MUST NOT set the `error` field of the `Operation` proto. - The possible errors include: - - * `INVALID_ARGUMENT`: One or more arguments are invalid. - * `FAILED_PRECONDITION`: One or more errors occurred in setting up the - action requested, such as a missing input or command or no worker being - available. The client may be able to fix the errors and retry. - * `RESOURCE_EXHAUSTED`: There is insufficient quota of some resource to run - the action. - * `UNAVAILABLE`: Due to a transient condition, such as all workers being - occupied (and the server does not support a queue), the action could not - be started. The client should retry. - * `INTERNAL`: An internal error occurred in the execution engine or the - worker. - * `DEADLINE_EXCEEDED`: The execution timed out. - * `CANCELLED`: The operation was cancelled by the client. This status is - only possible if the server implements the Operations API CancelOperation - method, and it was called for the current execution. - - In the case of a missing input or command, the server SHOULD additionally - send a [PreconditionFailure][google.rpc.PreconditionFailure] error detail - where, for each requested blob not present in the CAS, there is a - `Violation` with a `type` of `MISSING` and a `subject` of - `"blobs/{digest_function/}{hash}/{size}"` indicating the digest of the - missing blob. The `subject` is formatted the same way as the - `resource_name` provided to - [ByteStream.Read][google.bytestream.ByteStream.Read], with the leading - instance name omitted. `digest_function` MUST thus be omitted if its value - is one of MD5, MURMUR3, SHA1, SHA256, SHA384, SHA512, or VSO. - - The server does not need to guarantee that a call to this method leads to - at most one execution of the action. The server MAY execute the action - multiple times, potentially in parallel. These redundant executions MAY - continue to run, even if the operation is completed. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def WaitExecution(self, request, context): - """Wait for an execution operation to complete. When the client initially - makes the request, the server immediately responds with the current status - of the execution. The server will leave the request stream open until the - operation completes, and then respond with the completed operation. The - server MAY choose to stream additional updates as execution progresses, - such as to provide an update as to the state of the execution. - - In addition to the cases described for Execute, the WaitExecution method - may fail as follows: - - * `NOT_FOUND`: The operation no longer exists due to any of a transient - condition, an unknown operation name, or if the server implements the - Operations API DeleteOperation method and it was called for the current - execution. The client should call `Execute` to retry. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_ExecutionServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Execute': grpc.unary_stream_rpc_method_handler( - servicer.Execute, - request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ExecuteRequest.FromString, - response_serializer=google_dot_longrunning_dot_operations__pb2.Operation.SerializeToString, - ), - 'WaitExecution': grpc.unary_stream_rpc_method_handler( - servicer.WaitExecution, - request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.WaitExecutionRequest.FromString, - response_serializer=google_dot_longrunning_dot_operations__pb2.Operation.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'build.bazel.remote.execution.v2.Execution', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('build.bazel.remote.execution.v2.Execution', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class Execution(object): - """The Remote Execution API is used to execute an - [Action][build.bazel.remote.execution.v2.Action] on the remote - workers. - - As with other services in the Remote Execution API, any call may return an - error with a [RetryInfo][google.rpc.RetryInfo] error detail providing - information about when the client should retry the request; clients SHOULD - respect the information provided. - """ - - @staticmethod - def Execute(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/build.bazel.remote.execution.v2.Execution/Execute', - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ExecuteRequest.SerializeToString, - google_dot_longrunning_dot_operations__pb2.Operation.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def WaitExecution(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/build.bazel.remote.execution.v2.Execution/WaitExecution', - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.WaitExecutionRequest.SerializeToString, - google_dot_longrunning_dot_operations__pb2.Operation.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - -class ActionCacheStub(object): - """The action cache API is used to query whether a given action has already been - performed and, if so, retrieve its result. Unlike the - [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage], - which addresses blobs by their own content, the action cache addresses the - [ActionResult][build.bazel.remote.execution.v2.ActionResult] by a - digest of the encoded [Action][build.bazel.remote.execution.v2.Action] - which produced them. - - The lifetime of entries in the action cache is implementation-specific, but - the server SHOULD assume that more recently used entries are more likely to - be used again. - - As with other services in the Remote Execution API, any call may return an - error with a [RetryInfo][google.rpc.RetryInfo] error detail providing - information about when the client should retry the request; clients SHOULD - respect the information provided. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetActionResult = channel.unary_unary( - '/build.bazel.remote.execution.v2.ActionCache/GetActionResult', - request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetActionResultRequest.SerializeToString, - response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ActionResult.FromString, - _registered_method=True) - self.UpdateActionResult = channel.unary_unary( - '/build.bazel.remote.execution.v2.ActionCache/UpdateActionResult', - request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.UpdateActionResultRequest.SerializeToString, - response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ActionResult.FromString, - _registered_method=True) - - -class ActionCacheServicer(object): - """The action cache API is used to query whether a given action has already been - performed and, if so, retrieve its result. Unlike the - [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage], - which addresses blobs by their own content, the action cache addresses the - [ActionResult][build.bazel.remote.execution.v2.ActionResult] by a - digest of the encoded [Action][build.bazel.remote.execution.v2.Action] - which produced them. - - The lifetime of entries in the action cache is implementation-specific, but - the server SHOULD assume that more recently used entries are more likely to - be used again. - - As with other services in the Remote Execution API, any call may return an - error with a [RetryInfo][google.rpc.RetryInfo] error detail providing - information about when the client should retry the request; clients SHOULD - respect the information provided. - """ - - def GetActionResult(self, request, context): - """Retrieve a cached execution result. - - Implementations SHOULD ensure that any blobs referenced from the - [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage] - are available at the time of returning the - [ActionResult][build.bazel.remote.execution.v2.ActionResult] and will be - for some period of time afterwards. The lifetimes of the referenced blobs SHOULD be increased - if necessary and applicable. - - Errors: - - * `NOT_FOUND`: The requested `ActionResult` is not in the cache. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpdateActionResult(self, request, context): - """Upload a new execution result. - - In order to allow the server to perform access control based on the type of - action, and to assist with client debugging, the client MUST first upload - the [Action][build.bazel.remote.execution.v2.Action] that produced the - result, along with its - [Command][build.bazel.remote.execution.v2.Command], into the - `ContentAddressableStorage`. - - Server implementations MAY modify the - `UpdateActionResultRequest.action_result` and return an equivalent value. - - Errors: - - * `INVALID_ARGUMENT`: One or more arguments are invalid. - * `FAILED_PRECONDITION`: One or more errors occurred in updating the - action result, such as a missing command or action. - * `RESOURCE_EXHAUSTED`: There is insufficient storage space to add the - entry to the cache. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_ActionCacheServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetActionResult': grpc.unary_unary_rpc_method_handler( - servicer.GetActionResult, - request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetActionResultRequest.FromString, - response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ActionResult.SerializeToString, - ), - 'UpdateActionResult': grpc.unary_unary_rpc_method_handler( - servicer.UpdateActionResult, - request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.UpdateActionResultRequest.FromString, - response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ActionResult.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'build.bazel.remote.execution.v2.ActionCache', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('build.bazel.remote.execution.v2.ActionCache', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class ActionCache(object): - """The action cache API is used to query whether a given action has already been - performed and, if so, retrieve its result. Unlike the - [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage], - which addresses blobs by their own content, the action cache addresses the - [ActionResult][build.bazel.remote.execution.v2.ActionResult] by a - digest of the encoded [Action][build.bazel.remote.execution.v2.Action] - which produced them. - - The lifetime of entries in the action cache is implementation-specific, but - the server SHOULD assume that more recently used entries are more likely to - be used again. - - As with other services in the Remote Execution API, any call may return an - error with a [RetryInfo][google.rpc.RetryInfo] error detail providing - information about when the client should retry the request; clients SHOULD - respect the information provided. - """ - - @staticmethod - def GetActionResult(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/build.bazel.remote.execution.v2.ActionCache/GetActionResult', - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetActionResultRequest.SerializeToString, - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ActionResult.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UpdateActionResult(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/build.bazel.remote.execution.v2.ActionCache/UpdateActionResult', - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.UpdateActionResultRequest.SerializeToString, - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ActionResult.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - -class ContentAddressableStorageStub(object): - """The CAS (content-addressable storage) is used to store the inputs to and - outputs from the execution service. Each piece of content is addressed by the - digest of its binary data. - - Most of the binary data stored in the CAS is opaque to the execution engine, - and is only used as a communication medium. In order to build an - [Action][build.bazel.remote.execution.v2.Action], - however, the client will need to also upload the - [Command][build.bazel.remote.execution.v2.Command] and input root - [Directory][build.bazel.remote.execution.v2.Directory] for the Action. - The Command and Directory messages must be marshalled to wire format and then - uploaded under the hash as with any other piece of content. In practice, the - input root directory is likely to refer to other Directories in its - hierarchy, which must also each be uploaded on their own. - - For small file uploads the client should group them together and call - [BatchUpdateBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchUpdateBlobs]. - - For large uploads, the client must use the - [Write method][google.bytestream.ByteStream.Write] of the ByteStream API. - - For uncompressed data, the `WriteRequest.resource_name` is of the following form: - `{instance_name}/uploads/{uuid}/blobs/{digest_function/}{hash}/{size}{/optional_metadata}` - - Where: - * `instance_name` is an identifier used to distinguish between the various - instances on the server. Syntax and semantics of this field are defined - by the server; Clients must not make any assumptions about it (e.g., - whether it spans multiple path segments or not). If it is the empty path, - the leading slash is omitted, so that the `resource_name` becomes - `uploads/{uuid}/blobs/{digest_function/}{hash}/{size}{/optional_metadata}`. - To simplify parsing, a path segment cannot equal any of the following - keywords: `blobs`, `uploads`, `actions`, `actionResults`, `operations`, - `capabilities` or `compressed-blobs`. - * `uuid` is a version 4 UUID generated by the client, used to avoid - collisions between concurrent uploads of the same data. Clients MAY - reuse the same `uuid` for uploading different blobs. - * `digest_function` is a lowercase string form of a `DigestFunction.Value` - enum, indicating which digest function was used to compute `hash`. If the - digest function used is one of MD5, MURMUR3, SHA1, SHA256, SHA384, SHA512, - or VSO, this component MUST be omitted. In that case the server SHOULD - infer the digest function using the length of the `hash` and the digest - functions announced in the server's capabilities. - * `hash` and `size` refer to the [Digest][build.bazel.remote.execution.v2.Digest] - of the data being uploaded. - * `optional_metadata` is implementation specific data, which clients MAY omit. - Servers MAY ignore this metadata. - - Data can alternatively be uploaded in compressed form, with the following - `WriteRequest.resource_name` form: - `{instance_name}/uploads/{uuid}/compressed-blobs/{compressor}/{digest_function/}{uncompressed_hash}/{uncompressed_size}{/optional_metadata}` - - Where: - * `instance_name`, `uuid`, `digest_function` and `optional_metadata` are - defined as above. - * `compressor` is a lowercase string form of a `Compressor.Value` enum - other than `identity`, which is supported by the server and advertised in - [CacheCapabilities.supported_compressors][build.bazel.remote.execution.v2.CacheCapabilities.supported_compressors]. - * `uncompressed_hash` and `uncompressed_size` refer to the - [Digest][build.bazel.remote.execution.v2.Digest] of the data being - uploaded, once uncompressed. Servers MUST verify that these match - the uploaded data once uncompressed, and MUST return an - `INVALID_ARGUMENT` error in the case of mismatch. - - Note that when writing compressed blobs, the `WriteRequest.write_offset` in - the initial request in a stream refers to the offset in the uncompressed form - of the blob. In subsequent requests, `WriteRequest.write_offset` MUST be the - sum of the first request's 'WriteRequest.write_offset' and the total size of - all the compressed data bundles in the previous requests. - Note that this mixes an uncompressed offset with a compressed byte length, - which is nonsensical, but it is done to fit the semantics of the existing - ByteStream protocol. - - Uploads of the same data MAY occur concurrently in any form, compressed or - uncompressed. - - Clients SHOULD NOT use gRPC-level compression for ByteStream API `Write` - calls of compressed blobs, since this would compress already-compressed data. - - When attempting an upload, if another client has already completed the upload - (which may occur in the middle of a single upload if another client uploads - the same blob concurrently), the request will terminate immediately without - error, and with a response whose `committed_size` is the value `-1` if this - is a compressed upload, or with the full size of the uploaded file if this is - an uncompressed upload (regardless of how much data was transmitted by the - client). If the client completes the upload but the - [Digest][build.bazel.remote.execution.v2.Digest] does not match, an - `INVALID_ARGUMENT` error will be returned. In either case, the client should - not attempt to retry the upload. - - Small downloads can be grouped and requested in a batch via - [BatchReadBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchReadBlobs]. - - For large downloads, the client must use the - [Read method][google.bytestream.ByteStream.Read] of the ByteStream API. - - For uncompressed data, the `ReadRequest.resource_name` is of the following form: - `{instance_name}/blobs/{digest_function/}{hash}/{size}` - Where `instance_name`, `digest_function`, `hash` and `size` are defined as - for uploads. - - Data can alternatively be downloaded in compressed form, with the following - `ReadRequest.resource_name` form: - `{instance_name}/compressed-blobs/{compressor}/{digest_function/}{uncompressed_hash}/{uncompressed_size}` - - Where: - * `instance_name`, `compressor` and `digest_function` are defined as for - uploads. - * `uncompressed_hash` and `uncompressed_size` refer to the - [Digest][build.bazel.remote.execution.v2.Digest] of the data being - downloaded, once uncompressed. Clients MUST verify that these match - the downloaded data once uncompressed, and take appropriate steps in - the case of failure such as retrying a limited number of times or - surfacing an error to the user. - - When downloading compressed blobs: - * `ReadRequest.read_offset` refers to the offset in the uncompressed form - of the blob. - * Servers MUST return `INVALID_ARGUMENT` if `ReadRequest.read_limit` is - non-zero. - * Servers MAY use any compression level they choose, including different - levels for different blobs (e.g. choosing a level designed for maximum - speed for data known to be incompressible). - * Clients SHOULD NOT use gRPC-level compression, since this would compress - already-compressed data. - - Servers MUST be able to provide data for all recently advertised blobs in - each of the compression formats that the server supports, as well as in - uncompressed form. - - Additionally, ByteStream requests MAY come with an additional plain text header - that indicates the `resource_name` of the blob being sent. The header, if - present, MUST follow the following convention: - * name: `build.bazel.remote.execution.v2.resource-name`. - * contents: the plain text resource_name of the request message. - If set, the contents of the header MUST match the `resource_name` of the request - message. Servers MAY use this header to assist in routing requests to the - appropriate backend. - - The lifetime of entries in the CAS is implementation specific, but it SHOULD - be long enough to allow for newly-added and recently looked-up entries to be - used in subsequent calls (e.g. to - [Execute][build.bazel.remote.execution.v2.Execution.Execute]). - - Servers MUST behave as though empty blobs are always available, even if they - have not been uploaded. Clients MAY optimize away the uploading or - downloading of empty blobs. - - As with other services in the Remote Execution API, any call may return an - error with a [RetryInfo][google.rpc.RetryInfo] error detail providing - information about when the client should retry the request; clients SHOULD - respect the information provided. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.FindMissingBlobs = channel.unary_unary( - '/build.bazel.remote.execution.v2.ContentAddressableStorage/FindMissingBlobs', - request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.FindMissingBlobsRequest.SerializeToString, - response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.FindMissingBlobsResponse.FromString, - _registered_method=True) - self.BatchUpdateBlobs = channel.unary_unary( - '/build.bazel.remote.execution.v2.ContentAddressableStorage/BatchUpdateBlobs', - request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchUpdateBlobsRequest.SerializeToString, - response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchUpdateBlobsResponse.FromString, - _registered_method=True) - self.BatchReadBlobs = channel.unary_unary( - '/build.bazel.remote.execution.v2.ContentAddressableStorage/BatchReadBlobs', - request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchReadBlobsRequest.SerializeToString, - response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchReadBlobsResponse.FromString, - _registered_method=True) - self.GetTree = channel.unary_stream( - '/build.bazel.remote.execution.v2.ContentAddressableStorage/GetTree', - request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetTreeRequest.SerializeToString, - response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetTreeResponse.FromString, - _registered_method=True) - self.SplitBlob = channel.unary_unary( - '/build.bazel.remote.execution.v2.ContentAddressableStorage/SplitBlob', - request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SplitBlobRequest.SerializeToString, - response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SplitBlobResponse.FromString, - _registered_method=True) - self.SpliceBlob = channel.unary_unary( - '/build.bazel.remote.execution.v2.ContentAddressableStorage/SpliceBlob', - request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SpliceBlobRequest.SerializeToString, - response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SpliceBlobResponse.FromString, - _registered_method=True) - - -class ContentAddressableStorageServicer(object): - """The CAS (content-addressable storage) is used to store the inputs to and - outputs from the execution service. Each piece of content is addressed by the - digest of its binary data. - - Most of the binary data stored in the CAS is opaque to the execution engine, - and is only used as a communication medium. In order to build an - [Action][build.bazel.remote.execution.v2.Action], - however, the client will need to also upload the - [Command][build.bazel.remote.execution.v2.Command] and input root - [Directory][build.bazel.remote.execution.v2.Directory] for the Action. - The Command and Directory messages must be marshalled to wire format and then - uploaded under the hash as with any other piece of content. In practice, the - input root directory is likely to refer to other Directories in its - hierarchy, which must also each be uploaded on their own. - - For small file uploads the client should group them together and call - [BatchUpdateBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchUpdateBlobs]. - - For large uploads, the client must use the - [Write method][google.bytestream.ByteStream.Write] of the ByteStream API. - - For uncompressed data, the `WriteRequest.resource_name` is of the following form: - `{instance_name}/uploads/{uuid}/blobs/{digest_function/}{hash}/{size}{/optional_metadata}` - - Where: - * `instance_name` is an identifier used to distinguish between the various - instances on the server. Syntax and semantics of this field are defined - by the server; Clients must not make any assumptions about it (e.g., - whether it spans multiple path segments or not). If it is the empty path, - the leading slash is omitted, so that the `resource_name` becomes - `uploads/{uuid}/blobs/{digest_function/}{hash}/{size}{/optional_metadata}`. - To simplify parsing, a path segment cannot equal any of the following - keywords: `blobs`, `uploads`, `actions`, `actionResults`, `operations`, - `capabilities` or `compressed-blobs`. - * `uuid` is a version 4 UUID generated by the client, used to avoid - collisions between concurrent uploads of the same data. Clients MAY - reuse the same `uuid` for uploading different blobs. - * `digest_function` is a lowercase string form of a `DigestFunction.Value` - enum, indicating which digest function was used to compute `hash`. If the - digest function used is one of MD5, MURMUR3, SHA1, SHA256, SHA384, SHA512, - or VSO, this component MUST be omitted. In that case the server SHOULD - infer the digest function using the length of the `hash` and the digest - functions announced in the server's capabilities. - * `hash` and `size` refer to the [Digest][build.bazel.remote.execution.v2.Digest] - of the data being uploaded. - * `optional_metadata` is implementation specific data, which clients MAY omit. - Servers MAY ignore this metadata. - - Data can alternatively be uploaded in compressed form, with the following - `WriteRequest.resource_name` form: - `{instance_name}/uploads/{uuid}/compressed-blobs/{compressor}/{digest_function/}{uncompressed_hash}/{uncompressed_size}{/optional_metadata}` - - Where: - * `instance_name`, `uuid`, `digest_function` and `optional_metadata` are - defined as above. - * `compressor` is a lowercase string form of a `Compressor.Value` enum - other than `identity`, which is supported by the server and advertised in - [CacheCapabilities.supported_compressors][build.bazel.remote.execution.v2.CacheCapabilities.supported_compressors]. - * `uncompressed_hash` and `uncompressed_size` refer to the - [Digest][build.bazel.remote.execution.v2.Digest] of the data being - uploaded, once uncompressed. Servers MUST verify that these match - the uploaded data once uncompressed, and MUST return an - `INVALID_ARGUMENT` error in the case of mismatch. - - Note that when writing compressed blobs, the `WriteRequest.write_offset` in - the initial request in a stream refers to the offset in the uncompressed form - of the blob. In subsequent requests, `WriteRequest.write_offset` MUST be the - sum of the first request's 'WriteRequest.write_offset' and the total size of - all the compressed data bundles in the previous requests. - Note that this mixes an uncompressed offset with a compressed byte length, - which is nonsensical, but it is done to fit the semantics of the existing - ByteStream protocol. - - Uploads of the same data MAY occur concurrently in any form, compressed or - uncompressed. - - Clients SHOULD NOT use gRPC-level compression for ByteStream API `Write` - calls of compressed blobs, since this would compress already-compressed data. - - When attempting an upload, if another client has already completed the upload - (which may occur in the middle of a single upload if another client uploads - the same blob concurrently), the request will terminate immediately without - error, and with a response whose `committed_size` is the value `-1` if this - is a compressed upload, or with the full size of the uploaded file if this is - an uncompressed upload (regardless of how much data was transmitted by the - client). If the client completes the upload but the - [Digest][build.bazel.remote.execution.v2.Digest] does not match, an - `INVALID_ARGUMENT` error will be returned. In either case, the client should - not attempt to retry the upload. - - Small downloads can be grouped and requested in a batch via - [BatchReadBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchReadBlobs]. - - For large downloads, the client must use the - [Read method][google.bytestream.ByteStream.Read] of the ByteStream API. - - For uncompressed data, the `ReadRequest.resource_name` is of the following form: - `{instance_name}/blobs/{digest_function/}{hash}/{size}` - Where `instance_name`, `digest_function`, `hash` and `size` are defined as - for uploads. - - Data can alternatively be downloaded in compressed form, with the following - `ReadRequest.resource_name` form: - `{instance_name}/compressed-blobs/{compressor}/{digest_function/}{uncompressed_hash}/{uncompressed_size}` - - Where: - * `instance_name`, `compressor` and `digest_function` are defined as for - uploads. - * `uncompressed_hash` and `uncompressed_size` refer to the - [Digest][build.bazel.remote.execution.v2.Digest] of the data being - downloaded, once uncompressed. Clients MUST verify that these match - the downloaded data once uncompressed, and take appropriate steps in - the case of failure such as retrying a limited number of times or - surfacing an error to the user. - - When downloading compressed blobs: - * `ReadRequest.read_offset` refers to the offset in the uncompressed form - of the blob. - * Servers MUST return `INVALID_ARGUMENT` if `ReadRequest.read_limit` is - non-zero. - * Servers MAY use any compression level they choose, including different - levels for different blobs (e.g. choosing a level designed for maximum - speed for data known to be incompressible). - * Clients SHOULD NOT use gRPC-level compression, since this would compress - already-compressed data. - - Servers MUST be able to provide data for all recently advertised blobs in - each of the compression formats that the server supports, as well as in - uncompressed form. - - Additionally, ByteStream requests MAY come with an additional plain text header - that indicates the `resource_name` of the blob being sent. The header, if - present, MUST follow the following convention: - * name: `build.bazel.remote.execution.v2.resource-name`. - * contents: the plain text resource_name of the request message. - If set, the contents of the header MUST match the `resource_name` of the request - message. Servers MAY use this header to assist in routing requests to the - appropriate backend. - - The lifetime of entries in the CAS is implementation specific, but it SHOULD - be long enough to allow for newly-added and recently looked-up entries to be - used in subsequent calls (e.g. to - [Execute][build.bazel.remote.execution.v2.Execution.Execute]). - - Servers MUST behave as though empty blobs are always available, even if they - have not been uploaded. Clients MAY optimize away the uploading or - downloading of empty blobs. - - As with other services in the Remote Execution API, any call may return an - error with a [RetryInfo][google.rpc.RetryInfo] error detail providing - information about when the client should retry the request; clients SHOULD - respect the information provided. - """ - - def FindMissingBlobs(self, request, context): - """Determine if blobs are present in the CAS. - - Clients can use this API before uploading blobs to determine which ones are - already present in the CAS and do not need to be uploaded again. - - Servers SHOULD increase the lifetimes of the referenced blobs if necessary and - applicable. - - There are no method-specific errors. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def BatchUpdateBlobs(self, request, context): - """Upload many blobs at once. - - The server may enforce a limit of the combined total size of blobs - to be uploaded using this API. This limit may be obtained using the - [Capabilities][build.bazel.remote.execution.v2.Capabilities] API. - Requests exceeding the limit should either be split into smaller - chunks or uploaded using the - [ByteStream API][google.bytestream.ByteStream], as appropriate. - - This request is equivalent to calling a Bytestream `Write` request - on each individual blob, in parallel. The requests may succeed or fail - independently. - - Errors: - - * `INVALID_ARGUMENT`: The client attempted to upload more than the - server supported limit. - - Individual requests may return the following errors, additionally: - - * `RESOURCE_EXHAUSTED`: There is insufficient disk quota to store the blob. - * `INVALID_ARGUMENT`: The - [Digest][build.bazel.remote.execution.v2.Digest] does not match the - provided data. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def BatchReadBlobs(self, request, context): - """Download many blobs at once. - - The server may enforce a limit of the combined total size of blobs - to be downloaded using this API. This limit may be obtained using the - [Capabilities][build.bazel.remote.execution.v2.Capabilities] API. - Requests exceeding the limit should either be split into smaller - chunks or downloaded using the - [ByteStream API][google.bytestream.ByteStream], as appropriate. - - This request is equivalent to calling a Bytestream `Read` request - on each individual blob, in parallel. The requests may succeed or fail - independently. - - Errors: - - * `INVALID_ARGUMENT`: The client attempted to read more than the - server supported limit. - - Every error on individual read will be returned in the corresponding digest - status. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetTree(self, request, context): - """Fetch the entire directory tree rooted at a node. - - This request must be targeted at a - [Directory][build.bazel.remote.execution.v2.Directory] stored in the - [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage] - (CAS). The server will enumerate the `Directory` tree recursively and - return every node descended from the root. - - The GetTreeRequest.page_token parameter can be used to skip ahead in - the stream (e.g. when retrying a partially completed and aborted request), - by setting it to a value taken from GetTreeResponse.next_page_token of the - last successfully processed GetTreeResponse). - - The exact traversal order is unspecified and, unless retrieving subsequent - pages from an earlier request, is not guaranteed to be stable across - multiple invocations of `GetTree`. - - If part of the tree is missing from the CAS, the server will return the - portion present and omit the rest. - - Errors: - - * `NOT_FOUND`: The requested tree root is not present in the CAS. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SplitBlob(self, request, context): - """SplitBlob retrieves information about how a blob is split into chunks. - - This call returns information about how a blob is split into chunks, and - returns a list of the chunk digests. Using the returned list of chunk digests, - a client can check which chunks are locally available and only fetch the - missing ones. The desired blob can be assembled by concatenating the fetched - chunks in the order of the digests in the list. The chunks SHOULD all be - available in the CAS. - - This API can be used to reduce the required data to download a large blob - from CAS if some chunks from similar blobs are locally available. For this - procedure to work properly, blobs SHOULD be split in a content-defined way, - rather than with fixed-sized chunking. - - If a split request is answered successfully, a client can expect the - following guarantees from the server: - 1. The blob chunks are stored in CAS. - 2. Concatenating the blob chunks in the order of the digest list returned - by the server results in the original blob. - - Servers which implement this functionality MUST declare that they support - it by setting the - [CacheCapabilities.split_blob_support][build.bazel.remote.execution.v2.CacheCapabilities.split_blob_support] - field accordingly. - - Clients MUST check that the server supports this capability, before using - it. - - Clients SHOULD verify that the digest of the blob assembled by the fetched - chunks is equal to the requested blob digest. - - The lifetimes of the generated chunk blobs MAY be independent of the - lifetime of the original blob. In particular: - * A blob and any chunk derived from it MAY be evicted from the CAS at - different times. - * A call to [SplitBlob][build.bazel.remote.execution.v2.ContentAddressableStorage.SplitBlob] - extends the lifetime of the original blob, and sets the lifetimes of - the resulting chunks (or extends the lifetimes of already-existing - chunks). - * Touching a chunk extends its lifetime, but the server MAY choose not - to extend the lifetime of the original blob. - * Touching the original blob extends its lifetime, but the server MAY - choose not to extend the lifetimes of chunks derived from it. - - When blob splitting and splicing is used at the same time, the clients and - the server SHOULD agree out-of-band upon a chunking algorithm used by both - parties to benefit from each other's chunk data and avoid unnecessary data - duplication. - - Errors: - - * `NOT_FOUND`: The requested blob is not present in the CAS, OR there is no - split information available for the blob, OR at least one chunk needed to - reconstruct the blob is missing from the CAS. - * `RESOURCE_EXHAUSTED`: There is insufficient disk quota to store the blob - chunks. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SpliceBlob(self, request, context): - """SpliceBlob tells the CAS how chunks can compose a blob. - - This is the complementary operation to the - [ContentAddressableStorage.SplitBlob][build.bazel.remote.execution.v2.ContentAddressableStorage.SplitBlob] - function to handle the chunked upload of large blobs to save upload - traffic. - - When uploading a large blob using chunked upload, clients MUST first upload - all chunks to the CAS, then call this RPC to tell the server how those chunks - compose the original blob. The chunks referenced in the SpliceBlob call SHOULD be - available in the CAS before calling this RPC. - - If a client needs to upload a large blob and is able to split a blob into - chunks in such a way that reusable chunks are obtained, e.g., by means of - content-defined chunking, it can first determine which parts of the blob - are already available in the remote CAS and upload the missing chunks, and - then use this API to store information on how the chunks compose the - original blob. - - Servers which implement this functionality MUST declare that they support - it by setting the - [CacheCapabilities.splice_blob_support][build.bazel.remote.execution.v2.CacheCapabilities.splice_blob_support] - field accordingly. - - Clients MUST check that the server supports this capability, before using - it. - - In order to ensure data consistency of the CAS, the server MUST only add - blobs to the CAS after verifying their digests. In particular, servers MUST NOT - trust digests provided by the client. The server MAY accept a request as no-op - if the client-specified blob is already in CAS or if information on how to - construct the blob from chunks is available. If the client-specified blob is - not already in the CAS, the server MUST verify that the digest of the newly - created blob assembled from chunks matches the digest specified by the - client, and reject the request if they differ. Servers MAY choose to allow - overwriting existing chunk mappings or to store multiple chunk mappings for - the same blob. - - When blob splitting and splicing is used at the same time, the clients and - the server SHOULD agree out-of-band upon a chunking algorithm used by both - parties to benefit from each other's chunk data and avoid unnecessary data - duplication. - - Errors: - - * `NOT_FOUND`: At least one of the blob chunks is not present in the CAS. - * `RESOURCE_EXHAUSTED`: There is insufficient disk quota to store the - spliced blob. - * `INVALID_ARGUMENT`: The digest of the spliced blob is different from the - provided expected digest. - * `ALREADY_EXISTS`: The blob already exists in CAS and the server did not - extend the lifetime of the chunks specified in the request, e.g. because - it prefers a different chunking and extended those instead. Clients can - call [SplitBlob][build.bazel.remote.execution.v2.ContentAddressableStorage.SplitBlob] - to check what chunk mapping the server is using. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_ContentAddressableStorageServicer_to_server(servicer, server): - rpc_method_handlers = { - 'FindMissingBlobs': grpc.unary_unary_rpc_method_handler( - servicer.FindMissingBlobs, - request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.FindMissingBlobsRequest.FromString, - response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.FindMissingBlobsResponse.SerializeToString, - ), - 'BatchUpdateBlobs': grpc.unary_unary_rpc_method_handler( - servicer.BatchUpdateBlobs, - request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchUpdateBlobsRequest.FromString, - response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchUpdateBlobsResponse.SerializeToString, - ), - 'BatchReadBlobs': grpc.unary_unary_rpc_method_handler( - servicer.BatchReadBlobs, - request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchReadBlobsRequest.FromString, - response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchReadBlobsResponse.SerializeToString, - ), - 'GetTree': grpc.unary_stream_rpc_method_handler( - servicer.GetTree, - request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetTreeRequest.FromString, - response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetTreeResponse.SerializeToString, - ), - 'SplitBlob': grpc.unary_unary_rpc_method_handler( - servicer.SplitBlob, - request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SplitBlobRequest.FromString, - response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SplitBlobResponse.SerializeToString, - ), - 'SpliceBlob': grpc.unary_unary_rpc_method_handler( - servicer.SpliceBlob, - request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SpliceBlobRequest.FromString, - response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SpliceBlobResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'build.bazel.remote.execution.v2.ContentAddressableStorage', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('build.bazel.remote.execution.v2.ContentAddressableStorage', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class ContentAddressableStorage(object): - """The CAS (content-addressable storage) is used to store the inputs to and - outputs from the execution service. Each piece of content is addressed by the - digest of its binary data. - - Most of the binary data stored in the CAS is opaque to the execution engine, - and is only used as a communication medium. In order to build an - [Action][build.bazel.remote.execution.v2.Action], - however, the client will need to also upload the - [Command][build.bazel.remote.execution.v2.Command] and input root - [Directory][build.bazel.remote.execution.v2.Directory] for the Action. - The Command and Directory messages must be marshalled to wire format and then - uploaded under the hash as with any other piece of content. In practice, the - input root directory is likely to refer to other Directories in its - hierarchy, which must also each be uploaded on their own. - - For small file uploads the client should group them together and call - [BatchUpdateBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchUpdateBlobs]. - - For large uploads, the client must use the - [Write method][google.bytestream.ByteStream.Write] of the ByteStream API. - - For uncompressed data, the `WriteRequest.resource_name` is of the following form: - `{instance_name}/uploads/{uuid}/blobs/{digest_function/}{hash}/{size}{/optional_metadata}` - - Where: - * `instance_name` is an identifier used to distinguish between the various - instances on the server. Syntax and semantics of this field are defined - by the server; Clients must not make any assumptions about it (e.g., - whether it spans multiple path segments or not). If it is the empty path, - the leading slash is omitted, so that the `resource_name` becomes - `uploads/{uuid}/blobs/{digest_function/}{hash}/{size}{/optional_metadata}`. - To simplify parsing, a path segment cannot equal any of the following - keywords: `blobs`, `uploads`, `actions`, `actionResults`, `operations`, - `capabilities` or `compressed-blobs`. - * `uuid` is a version 4 UUID generated by the client, used to avoid - collisions between concurrent uploads of the same data. Clients MAY - reuse the same `uuid` for uploading different blobs. - * `digest_function` is a lowercase string form of a `DigestFunction.Value` - enum, indicating which digest function was used to compute `hash`. If the - digest function used is one of MD5, MURMUR3, SHA1, SHA256, SHA384, SHA512, - or VSO, this component MUST be omitted. In that case the server SHOULD - infer the digest function using the length of the `hash` and the digest - functions announced in the server's capabilities. - * `hash` and `size` refer to the [Digest][build.bazel.remote.execution.v2.Digest] - of the data being uploaded. - * `optional_metadata` is implementation specific data, which clients MAY omit. - Servers MAY ignore this metadata. - - Data can alternatively be uploaded in compressed form, with the following - `WriteRequest.resource_name` form: - `{instance_name}/uploads/{uuid}/compressed-blobs/{compressor}/{digest_function/}{uncompressed_hash}/{uncompressed_size}{/optional_metadata}` - - Where: - * `instance_name`, `uuid`, `digest_function` and `optional_metadata` are - defined as above. - * `compressor` is a lowercase string form of a `Compressor.Value` enum - other than `identity`, which is supported by the server and advertised in - [CacheCapabilities.supported_compressors][build.bazel.remote.execution.v2.CacheCapabilities.supported_compressors]. - * `uncompressed_hash` and `uncompressed_size` refer to the - [Digest][build.bazel.remote.execution.v2.Digest] of the data being - uploaded, once uncompressed. Servers MUST verify that these match - the uploaded data once uncompressed, and MUST return an - `INVALID_ARGUMENT` error in the case of mismatch. - - Note that when writing compressed blobs, the `WriteRequest.write_offset` in - the initial request in a stream refers to the offset in the uncompressed form - of the blob. In subsequent requests, `WriteRequest.write_offset` MUST be the - sum of the first request's 'WriteRequest.write_offset' and the total size of - all the compressed data bundles in the previous requests. - Note that this mixes an uncompressed offset with a compressed byte length, - which is nonsensical, but it is done to fit the semantics of the existing - ByteStream protocol. - - Uploads of the same data MAY occur concurrently in any form, compressed or - uncompressed. - - Clients SHOULD NOT use gRPC-level compression for ByteStream API `Write` - calls of compressed blobs, since this would compress already-compressed data. - - When attempting an upload, if another client has already completed the upload - (which may occur in the middle of a single upload if another client uploads - the same blob concurrently), the request will terminate immediately without - error, and with a response whose `committed_size` is the value `-1` if this - is a compressed upload, or with the full size of the uploaded file if this is - an uncompressed upload (regardless of how much data was transmitted by the - client). If the client completes the upload but the - [Digest][build.bazel.remote.execution.v2.Digest] does not match, an - `INVALID_ARGUMENT` error will be returned. In either case, the client should - not attempt to retry the upload. - - Small downloads can be grouped and requested in a batch via - [BatchReadBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchReadBlobs]. - - For large downloads, the client must use the - [Read method][google.bytestream.ByteStream.Read] of the ByteStream API. - - For uncompressed data, the `ReadRequest.resource_name` is of the following form: - `{instance_name}/blobs/{digest_function/}{hash}/{size}` - Where `instance_name`, `digest_function`, `hash` and `size` are defined as - for uploads. - - Data can alternatively be downloaded in compressed form, with the following - `ReadRequest.resource_name` form: - `{instance_name}/compressed-blobs/{compressor}/{digest_function/}{uncompressed_hash}/{uncompressed_size}` - - Where: - * `instance_name`, `compressor` and `digest_function` are defined as for - uploads. - * `uncompressed_hash` and `uncompressed_size` refer to the - [Digest][build.bazel.remote.execution.v2.Digest] of the data being - downloaded, once uncompressed. Clients MUST verify that these match - the downloaded data once uncompressed, and take appropriate steps in - the case of failure such as retrying a limited number of times or - surfacing an error to the user. - - When downloading compressed blobs: - * `ReadRequest.read_offset` refers to the offset in the uncompressed form - of the blob. - * Servers MUST return `INVALID_ARGUMENT` if `ReadRequest.read_limit` is - non-zero. - * Servers MAY use any compression level they choose, including different - levels for different blobs (e.g. choosing a level designed for maximum - speed for data known to be incompressible). - * Clients SHOULD NOT use gRPC-level compression, since this would compress - already-compressed data. - - Servers MUST be able to provide data for all recently advertised blobs in - each of the compression formats that the server supports, as well as in - uncompressed form. - - Additionally, ByteStream requests MAY come with an additional plain text header - that indicates the `resource_name` of the blob being sent. The header, if - present, MUST follow the following convention: - * name: `build.bazel.remote.execution.v2.resource-name`. - * contents: the plain text resource_name of the request message. - If set, the contents of the header MUST match the `resource_name` of the request - message. Servers MAY use this header to assist in routing requests to the - appropriate backend. - - The lifetime of entries in the CAS is implementation specific, but it SHOULD - be long enough to allow for newly-added and recently looked-up entries to be - used in subsequent calls (e.g. to - [Execute][build.bazel.remote.execution.v2.Execution.Execute]). - - Servers MUST behave as though empty blobs are always available, even if they - have not been uploaded. Clients MAY optimize away the uploading or - downloading of empty blobs. - - As with other services in the Remote Execution API, any call may return an - error with a [RetryInfo][google.rpc.RetryInfo] error detail providing - information about when the client should retry the request; clients SHOULD - respect the information provided. - """ - - @staticmethod - def FindMissingBlobs(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/build.bazel.remote.execution.v2.ContentAddressableStorage/FindMissingBlobs', - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.FindMissingBlobsRequest.SerializeToString, - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.FindMissingBlobsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def BatchUpdateBlobs(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/build.bazel.remote.execution.v2.ContentAddressableStorage/BatchUpdateBlobs', - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchUpdateBlobsRequest.SerializeToString, - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchUpdateBlobsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def BatchReadBlobs(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/build.bazel.remote.execution.v2.ContentAddressableStorage/BatchReadBlobs', - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchReadBlobsRequest.SerializeToString, - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchReadBlobsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetTree(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/build.bazel.remote.execution.v2.ContentAddressableStorage/GetTree', - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetTreeRequest.SerializeToString, - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetTreeResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def SplitBlob(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/build.bazel.remote.execution.v2.ContentAddressableStorage/SplitBlob', - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SplitBlobRequest.SerializeToString, - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SplitBlobResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def SpliceBlob(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/build.bazel.remote.execution.v2.ContentAddressableStorage/SpliceBlob', - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SpliceBlobRequest.SerializeToString, - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.SpliceBlobResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - -class CapabilitiesStub(object): - """The Capabilities service may be used by remote execution clients to query - various server properties, in order to self-configure or return meaningful - error messages. - - The query may include a particular `instance_name`, in which case the values - returned will pertain to that instance. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetCapabilities = channel.unary_unary( - '/build.bazel.remote.execution.v2.Capabilities/GetCapabilities', - request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetCapabilitiesRequest.SerializeToString, - response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ServerCapabilities.FromString, - _registered_method=True) - - -class CapabilitiesServicer(object): - """The Capabilities service may be used by remote execution clients to query - various server properties, in order to self-configure or return meaningful - error messages. - - The query may include a particular `instance_name`, in which case the values - returned will pertain to that instance. - """ - - def GetCapabilities(self, request, context): - """GetCapabilities returns the server capabilities configuration of the - remote endpoint. - Only the capabilities of the services supported by the endpoint will - be returned: - * Execution + CAS + Action Cache endpoints should return both - CacheCapabilities and ExecutionCapabilities. - * Execution only endpoints should return ExecutionCapabilities. - * CAS + Action Cache only endpoints should return CacheCapabilities. - - There are no method-specific errors. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_CapabilitiesServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetCapabilities': grpc.unary_unary_rpc_method_handler( - servicer.GetCapabilities, - request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetCapabilitiesRequest.FromString, - response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ServerCapabilities.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'build.bazel.remote.execution.v2.Capabilities', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('build.bazel.remote.execution.v2.Capabilities', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class Capabilities(object): - """The Capabilities service may be used by remote execution clients to query - various server properties, in order to self-configure or return meaningful - error messages. - - The query may include a particular `instance_name`, in which case the values - returned will pertain to that instance. - """ - - @staticmethod - def GetCapabilities(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/build.bazel.remote.execution.v2.Capabilities/GetCapabilities', - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetCapabilitiesRequest.SerializeToString, - build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ServerCapabilities.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/benchmark/proto_gen/build/bazel/semver/semver_pb2.py b/benchmark/proto_gen/build/bazel/semver/semver_pb2.py deleted file mode 100644 index dbccdc7..0000000 --- a/benchmark/proto_gen/build/bazel/semver/semver_pb2.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: build/bazel/semver/semver.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'build/bazel/semver/semver.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1f\x62uild/bazel/semver/semver.proto\x12\x12\x62uild.bazel.semver\"I\n\x06SemVer\x12\r\n\x05major\x18\x01 \x01(\x05\x12\r\n\x05minor\x18\x02 \x01(\x05\x12\r\n\x05patch\x18\x03 \x01(\x05\x12\x12\n\nprerelease\x18\x04 \x01(\tBt\n\x12\x62uild.bazel.semverB\x0bSemverProtoP\x01Z4github.com/bazelbuild/remote-apis/build/bazel/semver\xa2\x02\x03SMV\xaa\x02\x12\x42uild.Bazel.Semverb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'build.bazel.semver.semver_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\022build.bazel.semverB\013SemverProtoP\001Z4github.com/bazelbuild/remote-apis/build/bazel/semver\242\002\003SMV\252\002\022Build.Bazel.Semver' - _globals['_SEMVER']._serialized_start=55 - _globals['_SEMVER']._serialized_end=128 -# @@protoc_insertion_point(module_scope) diff --git a/benchmark/proto_gen/build/bazel/semver/semver_pb2_grpc.py b/benchmark/proto_gen/build/bazel/semver/semver_pb2_grpc.py deleted file mode 100644 index a9084f3..0000000 --- a/benchmark/proto_gen/build/bazel/semver/semver_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.78.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in build/bazel/semver/semver_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/benchmark/proto_gen/google/api/annotations_pb2.py b/benchmark/proto_gen/google/api/annotations_pb2.py deleted file mode 100644 index 9c23b20..0000000 --- a/benchmark/proto_gen/google/api/annotations_pb2.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: google/api/annotations.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'google/api/annotations.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.api import http_pb2 as google_dot_api_dot_http__pb2 -from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cgoogle/api/annotations.proto\x12\ngoogle.api\x1a\x15google/api/http.proto\x1a google/protobuf/descriptor.proto:E\n\x04http\x12\x1e.google.protobuf.MethodOptions\x18\xb0\xca\xbc\" \x01(\x0b\x32\x14.google.api.HttpRuleBn\n\x0e\x63om.google.apiB\x10\x41nnotationsProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xa2\x02\x04GAPIb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.api.annotations_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\016com.google.apiB\020AnnotationsProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\242\002\004GAPI' -# @@protoc_insertion_point(module_scope) diff --git a/benchmark/proto_gen/google/api/annotations_pb2_grpc.py b/benchmark/proto_gen/google/api/annotations_pb2_grpc.py deleted file mode 100644 index b77da35..0000000 --- a/benchmark/proto_gen/google/api/annotations_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.78.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in google/api/annotations_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/benchmark/proto_gen/google/api/client_pb2.py b/benchmark/proto_gen/google/api/client_pb2.py deleted file mode 100644 index 355b0c2..0000000 --- a/benchmark/proto_gen/google/api/client_pb2.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: google/api/client.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'google/api/client.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.api import launch_stage_pb2 as google_dot_api_dot_launch__stage__pb2 -from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17google/api/client.proto\x12\ngoogle.api\x1a\x1dgoogle/api/launch_stage.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\"\xbe\x01\n\x16\x43ommonLanguageSettings\x12\x1e\n\x12reference_docs_uri\x18\x01 \x01(\tB\x02\x18\x01\x12:\n\x0c\x64\x65stinations\x18\x02 \x03(\x0e\x32$.google.api.ClientLibraryDestination\x12H\n\x1aselective_gapic_generation\x18\x03 \x01(\x0b\x32$.google.api.SelectiveGapicGeneration\"\xfb\x03\n\x15\x43lientLibrarySettings\x12\x0f\n\x07version\x18\x01 \x01(\t\x12-\n\x0claunch_stage\x18\x02 \x01(\x0e\x32\x17.google.api.LaunchStage\x12\x1a\n\x12rest_numeric_enums\x18\x03 \x01(\x08\x12/\n\rjava_settings\x18\x15 \x01(\x0b\x32\x18.google.api.JavaSettings\x12-\n\x0c\x63pp_settings\x18\x16 \x01(\x0b\x32\x17.google.api.CppSettings\x12-\n\x0cphp_settings\x18\x17 \x01(\x0b\x32\x17.google.api.PhpSettings\x12\x33\n\x0fpython_settings\x18\x18 \x01(\x0b\x32\x1a.google.api.PythonSettings\x12/\n\rnode_settings\x18\x19 \x01(\x0b\x32\x18.google.api.NodeSettings\x12\x33\n\x0f\x64otnet_settings\x18\x1a \x01(\x0b\x32\x1a.google.api.DotnetSettings\x12/\n\rruby_settings\x18\x1b \x01(\x0b\x32\x18.google.api.RubySettings\x12+\n\x0bgo_settings\x18\x1c \x01(\x0b\x32\x16.google.api.GoSettings\"\xa8\x03\n\nPublishing\x12\x33\n\x0fmethod_settings\x18\x02 \x03(\x0b\x32\x1a.google.api.MethodSettings\x12\x15\n\rnew_issue_uri\x18\x65 \x01(\t\x12\x19\n\x11\x64ocumentation_uri\x18\x66 \x01(\t\x12\x16\n\x0e\x61pi_short_name\x18g \x01(\t\x12\x14\n\x0cgithub_label\x18h \x01(\t\x12\x1e\n\x16\x63odeowner_github_teams\x18i \x03(\t\x12\x16\n\x0e\x64oc_tag_prefix\x18j \x01(\t\x12;\n\x0corganization\x18k \x01(\x0e\x32%.google.api.ClientLibraryOrganization\x12;\n\x10library_settings\x18m \x03(\x0b\x32!.google.api.ClientLibrarySettings\x12)\n!proto_reference_documentation_uri\x18n \x01(\t\x12(\n rest_reference_documentation_uri\x18o \x01(\t\"\xe3\x01\n\x0cJavaSettings\x12\x17\n\x0flibrary_package\x18\x01 \x01(\t\x12L\n\x13service_class_names\x18\x02 \x03(\x0b\x32/.google.api.JavaSettings.ServiceClassNamesEntry\x12\x32\n\x06\x63ommon\x18\x03 \x01(\x0b\x32\".google.api.CommonLanguageSettings\x1a\x38\n\x16ServiceClassNamesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"A\n\x0b\x43ppSettings\x12\x32\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\".google.api.CommonLanguageSettings\"A\n\x0bPhpSettings\x12\x32\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\".google.api.CommonLanguageSettings\"\x9b\x02\n\x0ePythonSettings\x12\x32\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\".google.api.CommonLanguageSettings\x12N\n\x15\x65xperimental_features\x18\x02 \x01(\x0b\x32/.google.api.PythonSettings.ExperimentalFeatures\x1a\x84\x01\n\x14\x45xperimentalFeatures\x12\x1d\n\x15rest_async_io_enabled\x18\x01 \x01(\x08\x12\'\n\x1fprotobuf_pythonic_types_enabled\x18\x02 \x01(\x08\x12$\n\x1cunversioned_package_disabled\x18\x03 \x01(\x08\"B\n\x0cNodeSettings\x12\x32\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\".google.api.CommonLanguageSettings\"\xaa\x03\n\x0e\x44otnetSettings\x12\x32\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\".google.api.CommonLanguageSettings\x12I\n\x10renamed_services\x18\x02 \x03(\x0b\x32/.google.api.DotnetSettings.RenamedServicesEntry\x12K\n\x11renamed_resources\x18\x03 \x03(\x0b\x32\x30.google.api.DotnetSettings.RenamedResourcesEntry\x12\x19\n\x11ignored_resources\x18\x04 \x03(\t\x12 \n\x18\x66orced_namespace_aliases\x18\x05 \x03(\t\x12\x1e\n\x16handwritten_signatures\x18\x06 \x03(\t\x1a\x36\n\x14RenamedServicesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x37\n\x15RenamedResourcesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"B\n\x0cRubySettings\x12\x32\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\".google.api.CommonLanguageSettings\"\xbf\x01\n\nGoSettings\x12\x32\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\".google.api.CommonLanguageSettings\x12\x45\n\x10renamed_services\x18\x02 \x03(\x0b\x32+.google.api.GoSettings.RenamedServicesEntry\x1a\x36\n\x14RenamedServicesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xcf\x02\n\x0eMethodSettings\x12\x10\n\x08selector\x18\x01 \x01(\t\x12<\n\x0clong_running\x18\x02 \x01(\x0b\x32&.google.api.MethodSettings.LongRunning\x12\x1d\n\x15\x61uto_populated_fields\x18\x03 \x03(\t\x1a\xcd\x01\n\x0bLongRunning\x12\x35\n\x12initial_poll_delay\x18\x01 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x1d\n\x15poll_delay_multiplier\x18\x02 \x01(\x02\x12\x31\n\x0emax_poll_delay\x18\x03 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x35\n\x12total_poll_timeout\x18\x04 \x01(\x0b\x32\x19.google.protobuf.Duration\"Q\n\x18SelectiveGapicGeneration\x12\x0f\n\x07methods\x18\x01 \x03(\t\x12$\n\x1cgenerate_omitted_as_internal\x18\x02 \x01(\x08*\xa3\x01\n\x19\x43lientLibraryOrganization\x12+\n\'CLIENT_LIBRARY_ORGANIZATION_UNSPECIFIED\x10\x00\x12\t\n\x05\x43LOUD\x10\x01\x12\x07\n\x03\x41\x44S\x10\x02\x12\n\n\x06PHOTOS\x10\x03\x12\x0f\n\x0bSTREET_VIEW\x10\x04\x12\x0c\n\x08SHOPPING\x10\x05\x12\x07\n\x03GEO\x10\x06\x12\x11\n\rGENERATIVE_AI\x10\x07*g\n\x18\x43lientLibraryDestination\x12*\n&CLIENT_LIBRARY_DESTINATION_UNSPECIFIED\x10\x00\x12\n\n\x06GITHUB\x10\n\x12\x13\n\x0fPACKAGE_MANAGER\x10\x14:9\n\x10method_signature\x12\x1e.google.protobuf.MethodOptions\x18\x9b\x08 \x03(\t:6\n\x0c\x64\x65\x66\x61ult_host\x12\x1f.google.protobuf.ServiceOptions\x18\x99\x08 \x01(\t:6\n\x0coauth_scopes\x12\x1f.google.protobuf.ServiceOptions\x18\x9a\x08 \x01(\t:8\n\x0b\x61pi_version\x12\x1f.google.protobuf.ServiceOptions\x18\xc1\xba\xab\xfa\x01 \x01(\tBi\n\x0e\x63om.google.apiB\x0b\x43lientProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xa2\x02\x04GAPIb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.api.client_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\016com.google.apiB\013ClientProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\242\002\004GAPI' - _globals['_COMMONLANGUAGESETTINGS'].fields_by_name['reference_docs_uri']._loaded_options = None - _globals['_COMMONLANGUAGESETTINGS'].fields_by_name['reference_docs_uri']._serialized_options = b'\030\001' - _globals['_JAVASETTINGS_SERVICECLASSNAMESENTRY']._loaded_options = None - _globals['_JAVASETTINGS_SERVICECLASSNAMESENTRY']._serialized_options = b'8\001' - _globals['_DOTNETSETTINGS_RENAMEDSERVICESENTRY']._loaded_options = None - _globals['_DOTNETSETTINGS_RENAMEDSERVICESENTRY']._serialized_options = b'8\001' - _globals['_DOTNETSETTINGS_RENAMEDRESOURCESENTRY']._loaded_options = None - _globals['_DOTNETSETTINGS_RENAMEDRESOURCESENTRY']._serialized_options = b'8\001' - _globals['_GOSETTINGS_RENAMEDSERVICESENTRY']._loaded_options = None - _globals['_GOSETTINGS_RENAMEDSERVICESENTRY']._serialized_options = b'8\001' - _globals['_CLIENTLIBRARYORGANIZATION']._serialized_start=3097 - _globals['_CLIENTLIBRARYORGANIZATION']._serialized_end=3260 - _globals['_CLIENTLIBRARYDESTINATION']._serialized_start=3262 - _globals['_CLIENTLIBRARYDESTINATION']._serialized_end=3365 - _globals['_COMMONLANGUAGESETTINGS']._serialized_start=137 - _globals['_COMMONLANGUAGESETTINGS']._serialized_end=327 - _globals['_CLIENTLIBRARYSETTINGS']._serialized_start=330 - _globals['_CLIENTLIBRARYSETTINGS']._serialized_end=837 - _globals['_PUBLISHING']._serialized_start=840 - _globals['_PUBLISHING']._serialized_end=1264 - _globals['_JAVASETTINGS']._serialized_start=1267 - _globals['_JAVASETTINGS']._serialized_end=1494 - _globals['_JAVASETTINGS_SERVICECLASSNAMESENTRY']._serialized_start=1438 - _globals['_JAVASETTINGS_SERVICECLASSNAMESENTRY']._serialized_end=1494 - _globals['_CPPSETTINGS']._serialized_start=1496 - _globals['_CPPSETTINGS']._serialized_end=1561 - _globals['_PHPSETTINGS']._serialized_start=1563 - _globals['_PHPSETTINGS']._serialized_end=1628 - _globals['_PYTHONSETTINGS']._serialized_start=1631 - _globals['_PYTHONSETTINGS']._serialized_end=1914 - _globals['_PYTHONSETTINGS_EXPERIMENTALFEATURES']._serialized_start=1782 - _globals['_PYTHONSETTINGS_EXPERIMENTALFEATURES']._serialized_end=1914 - _globals['_NODESETTINGS']._serialized_start=1916 - _globals['_NODESETTINGS']._serialized_end=1982 - _globals['_DOTNETSETTINGS']._serialized_start=1985 - _globals['_DOTNETSETTINGS']._serialized_end=2411 - _globals['_DOTNETSETTINGS_RENAMEDSERVICESENTRY']._serialized_start=2300 - _globals['_DOTNETSETTINGS_RENAMEDSERVICESENTRY']._serialized_end=2354 - _globals['_DOTNETSETTINGS_RENAMEDRESOURCESENTRY']._serialized_start=2356 - _globals['_DOTNETSETTINGS_RENAMEDRESOURCESENTRY']._serialized_end=2411 - _globals['_RUBYSETTINGS']._serialized_start=2413 - _globals['_RUBYSETTINGS']._serialized_end=2479 - _globals['_GOSETTINGS']._serialized_start=2482 - _globals['_GOSETTINGS']._serialized_end=2673 - _globals['_GOSETTINGS_RENAMEDSERVICESENTRY']._serialized_start=2300 - _globals['_GOSETTINGS_RENAMEDSERVICESENTRY']._serialized_end=2354 - _globals['_METHODSETTINGS']._serialized_start=2676 - _globals['_METHODSETTINGS']._serialized_end=3011 - _globals['_METHODSETTINGS_LONGRUNNING']._serialized_start=2806 - _globals['_METHODSETTINGS_LONGRUNNING']._serialized_end=3011 - _globals['_SELECTIVEGAPICGENERATION']._serialized_start=3013 - _globals['_SELECTIVEGAPICGENERATION']._serialized_end=3094 -# @@protoc_insertion_point(module_scope) diff --git a/benchmark/proto_gen/google/api/client_pb2_grpc.py b/benchmark/proto_gen/google/api/client_pb2_grpc.py deleted file mode 100644 index 0937e32..0000000 --- a/benchmark/proto_gen/google/api/client_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.78.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in google/api/client_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/benchmark/proto_gen/google/api/field_behavior_pb2.py b/benchmark/proto_gen/google/api/field_behavior_pb2.py deleted file mode 100644 index 2164fbe..0000000 --- a/benchmark/proto_gen/google/api/field_behavior_pb2.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: google/api/field_behavior.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'google/api/field_behavior.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fgoogle/api/field_behavior.proto\x12\ngoogle.api\x1a google/protobuf/descriptor.proto*\xb6\x01\n\rFieldBehavior\x12\x1e\n\x1a\x46IELD_BEHAVIOR_UNSPECIFIED\x10\x00\x12\x0c\n\x08OPTIONAL\x10\x01\x12\x0c\n\x08REQUIRED\x10\x02\x12\x0f\n\x0bOUTPUT_ONLY\x10\x03\x12\x0e\n\nINPUT_ONLY\x10\x04\x12\r\n\tIMMUTABLE\x10\x05\x12\x12\n\x0eUNORDERED_LIST\x10\x06\x12\x15\n\x11NON_EMPTY_DEFAULT\x10\x07\x12\x0e\n\nIDENTIFIER\x10\x08:U\n\x0e\x66ield_behavior\x12\x1d.google.protobuf.FieldOptions\x18\x9c\x08 \x03(\x0e\x32\x19.google.api.FieldBehaviorB\x02\x10\x00\x42p\n\x0e\x63om.google.apiB\x12\x46ieldBehaviorProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xa2\x02\x04GAPIb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.api.field_behavior_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\016com.google.apiB\022FieldBehaviorProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\242\002\004GAPI' - _globals['field_behavior']._loaded_options = None - _globals['field_behavior']._serialized_options = b'\020\000' - _globals['_FIELDBEHAVIOR']._serialized_start=82 - _globals['_FIELDBEHAVIOR']._serialized_end=264 -# @@protoc_insertion_point(module_scope) diff --git a/benchmark/proto_gen/google/api/field_behavior_pb2_grpc.py b/benchmark/proto_gen/google/api/field_behavior_pb2_grpc.py deleted file mode 100644 index cb7d24f..0000000 --- a/benchmark/proto_gen/google/api/field_behavior_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.78.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in google/api/field_behavior_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/benchmark/proto_gen/google/api/http_pb2.py b/benchmark/proto_gen/google/api/http_pb2.py deleted file mode 100644 index 8752287..0000000 --- a/benchmark/proto_gen/google/api/http_pb2.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: google/api/http.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'google/api/http.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15google/api/http.proto\x12\ngoogle.api\"T\n\x04Http\x12#\n\x05rules\x18\x01 \x03(\x0b\x32\x14.google.api.HttpRule\x12\'\n\x1f\x66ully_decode_reserved_expansion\x18\x02 \x01(\x08\"\x81\x02\n\x08HttpRule\x12\x10\n\x08selector\x18\x01 \x01(\t\x12\r\n\x03get\x18\x02 \x01(\tH\x00\x12\r\n\x03put\x18\x03 \x01(\tH\x00\x12\x0e\n\x04post\x18\x04 \x01(\tH\x00\x12\x10\n\x06\x64\x65lete\x18\x05 \x01(\tH\x00\x12\x0f\n\x05patch\x18\x06 \x01(\tH\x00\x12/\n\x06\x63ustom\x18\x08 \x01(\x0b\x32\x1d.google.api.CustomHttpPatternH\x00\x12\x0c\n\x04\x62ody\x18\x07 \x01(\t\x12\x15\n\rresponse_body\x18\x0c \x01(\t\x12\x31\n\x13\x61\x64\x64itional_bindings\x18\x0b \x03(\x0b\x32\x14.google.api.HttpRuleB\t\n\x07pattern\"/\n\x11\x43ustomHttpPattern\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\tBg\n\x0e\x63om.google.apiB\tHttpProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xa2\x02\x04GAPIb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.api.http_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\016com.google.apiB\tHttpProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\242\002\004GAPI' - _globals['_HTTP']._serialized_start=37 - _globals['_HTTP']._serialized_end=121 - _globals['_HTTPRULE']._serialized_start=124 - _globals['_HTTPRULE']._serialized_end=381 - _globals['_CUSTOMHTTPPATTERN']._serialized_start=383 - _globals['_CUSTOMHTTPPATTERN']._serialized_end=430 -# @@protoc_insertion_point(module_scope) diff --git a/benchmark/proto_gen/google/api/http_pb2_grpc.py b/benchmark/proto_gen/google/api/http_pb2_grpc.py deleted file mode 100644 index 491d5b3..0000000 --- a/benchmark/proto_gen/google/api/http_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.78.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in google/api/http_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/benchmark/proto_gen/google/api/launch_stage_pb2.py b/benchmark/proto_gen/google/api/launch_stage_pb2.py deleted file mode 100644 index 8aea10e..0000000 --- a/benchmark/proto_gen/google/api/launch_stage_pb2.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: google/api/launch_stage.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'google/api/launch_stage.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dgoogle/api/launch_stage.proto\x12\ngoogle.api*\x8c\x01\n\x0bLaunchStage\x12\x1c\n\x18LAUNCH_STAGE_UNSPECIFIED\x10\x00\x12\x11\n\rUNIMPLEMENTED\x10\x06\x12\r\n\tPRELAUNCH\x10\x07\x12\x10\n\x0c\x45\x41RLY_ACCESS\x10\x01\x12\t\n\x05\x41LPHA\x10\x02\x12\x08\n\x04\x42\x45TA\x10\x03\x12\x06\n\x02GA\x10\x04\x12\x0e\n\nDEPRECATED\x10\x05\x42Z\n\x0e\x63om.google.apiB\x10LaunchStageProtoP\x01Z-google.golang.org/genproto/googleapis/api;api\xa2\x02\x04GAPIb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.api.launch_stage_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\016com.google.apiB\020LaunchStageProtoP\001Z-google.golang.org/genproto/googleapis/api;api\242\002\004GAPI' - _globals['_LAUNCHSTAGE']._serialized_start=46 - _globals['_LAUNCHSTAGE']._serialized_end=186 -# @@protoc_insertion_point(module_scope) diff --git a/benchmark/proto_gen/google/api/launch_stage_pb2_grpc.py b/benchmark/proto_gen/google/api/launch_stage_pb2_grpc.py deleted file mode 100644 index 83e0975..0000000 --- a/benchmark/proto_gen/google/api/launch_stage_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.78.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in google/api/launch_stage_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/benchmark/proto_gen/google/bytestream/bytestream_pb2.py b/benchmark/proto_gen/google/bytestream/bytestream_pb2.py deleted file mode 100644 index 658ee69..0000000 --- a/benchmark/proto_gen/google/bytestream/bytestream_pb2.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: google/bytestream/bytestream.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'google/bytestream/bytestream.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"google/bytestream/bytestream.proto\x12\x11google.bytestream\x1a\x1egoogle/protobuf/wrappers.proto\"M\n\x0bReadRequest\x12\x15\n\rresource_name\x18\x01 \x01(\t\x12\x13\n\x0bread_offset\x18\x02 \x01(\x03\x12\x12\n\nread_limit\x18\x03 \x01(\x03\"\x1c\n\x0cReadResponse\x12\x0c\n\x04\x64\x61ta\x18\n \x01(\x0c\"_\n\x0cWriteRequest\x12\x15\n\rresource_name\x18\x01 \x01(\t\x12\x14\n\x0cwrite_offset\x18\x02 \x01(\x03\x12\x14\n\x0c\x66inish_write\x18\x03 \x01(\x08\x12\x0c\n\x04\x64\x61ta\x18\n \x01(\x0c\"\'\n\rWriteResponse\x12\x16\n\x0e\x63ommitted_size\x18\x01 \x01(\x03\"0\n\x17QueryWriteStatusRequest\x12\x15\n\rresource_name\x18\x01 \x01(\t\"D\n\x18QueryWriteStatusResponse\x12\x16\n\x0e\x63ommitted_size\x18\x01 \x01(\x03\x12\x10\n\x08\x63omplete\x18\x02 \x01(\x08\x32\x98\x02\n\nByteStream\x12K\n\x04Read\x12\x1e.google.bytestream.ReadRequest\x1a\x1f.google.bytestream.ReadResponse\"\x00\x30\x01\x12N\n\x05Write\x12\x1f.google.bytestream.WriteRequest\x1a .google.bytestream.WriteResponse\"\x00(\x01\x12m\n\x10QueryWriteStatus\x12*.google.bytestream.QueryWriteStatusRequest\x1a+.google.bytestream.QueryWriteStatusResponse\"\x00\x42~\n\x15\x63om.google.bytestreamB\x0f\x42yteStreamProtoP\x01Z0google.golang.org/genproto/googleapis/bytestream\xaa\x02\x1fGoogle.Cloud.Bigtable.Common.V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.bytestream.bytestream_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\025com.google.bytestreamB\017ByteStreamProtoP\001Z0google.golang.org/genproto/googleapis/bytestream\252\002\037Google.Cloud.Bigtable.Common.V1' - _globals['_READREQUEST']._serialized_start=89 - _globals['_READREQUEST']._serialized_end=166 - _globals['_READRESPONSE']._serialized_start=168 - _globals['_READRESPONSE']._serialized_end=196 - _globals['_WRITEREQUEST']._serialized_start=198 - _globals['_WRITEREQUEST']._serialized_end=293 - _globals['_WRITERESPONSE']._serialized_start=295 - _globals['_WRITERESPONSE']._serialized_end=334 - _globals['_QUERYWRITESTATUSREQUEST']._serialized_start=336 - _globals['_QUERYWRITESTATUSREQUEST']._serialized_end=384 - _globals['_QUERYWRITESTATUSRESPONSE']._serialized_start=386 - _globals['_QUERYWRITESTATUSRESPONSE']._serialized_end=454 - _globals['_BYTESTREAM']._serialized_start=457 - _globals['_BYTESTREAM']._serialized_end=737 -# @@protoc_insertion_point(module_scope) diff --git a/benchmark/proto_gen/google/bytestream/bytestream_pb2_grpc.py b/benchmark/proto_gen/google/bytestream/bytestream_pb2_grpc.py deleted file mode 100644 index 1d400c8..0000000 --- a/benchmark/proto_gen/google/bytestream/bytestream_pb2_grpc.py +++ /dev/null @@ -1,183 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - -from google.bytestream import bytestream_pb2 as google_dot_bytestream_dot_bytestream__pb2 - -GRPC_GENERATED_VERSION = '1.78.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in google/bytestream/bytestream_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) - - -class ByteStreamStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.Read = channel.unary_stream( - '/google.bytestream.ByteStream/Read', - request_serializer=google_dot_bytestream_dot_bytestream__pb2.ReadRequest.SerializeToString, - response_deserializer=google_dot_bytestream_dot_bytestream__pb2.ReadResponse.FromString, - _registered_method=True) - self.Write = channel.stream_unary( - '/google.bytestream.ByteStream/Write', - request_serializer=google_dot_bytestream_dot_bytestream__pb2.WriteRequest.SerializeToString, - response_deserializer=google_dot_bytestream_dot_bytestream__pb2.WriteResponse.FromString, - _registered_method=True) - self.QueryWriteStatus = channel.unary_unary( - '/google.bytestream.ByteStream/QueryWriteStatus', - request_serializer=google_dot_bytestream_dot_bytestream__pb2.QueryWriteStatusRequest.SerializeToString, - response_deserializer=google_dot_bytestream_dot_bytestream__pb2.QueryWriteStatusResponse.FromString, - _registered_method=True) - - -class ByteStreamServicer(object): - """Missing associated documentation comment in .proto file.""" - - def Read(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def Write(self, request_iterator, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def QueryWriteStatus(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_ByteStreamServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Read': grpc.unary_stream_rpc_method_handler( - servicer.Read, - request_deserializer=google_dot_bytestream_dot_bytestream__pb2.ReadRequest.FromString, - response_serializer=google_dot_bytestream_dot_bytestream__pb2.ReadResponse.SerializeToString, - ), - 'Write': grpc.stream_unary_rpc_method_handler( - servicer.Write, - request_deserializer=google_dot_bytestream_dot_bytestream__pb2.WriteRequest.FromString, - response_serializer=google_dot_bytestream_dot_bytestream__pb2.WriteResponse.SerializeToString, - ), - 'QueryWriteStatus': grpc.unary_unary_rpc_method_handler( - servicer.QueryWriteStatus, - request_deserializer=google_dot_bytestream_dot_bytestream__pb2.QueryWriteStatusRequest.FromString, - response_serializer=google_dot_bytestream_dot_bytestream__pb2.QueryWriteStatusResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'google.bytestream.ByteStream', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('google.bytestream.ByteStream', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class ByteStream(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def Read(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/google.bytestream.ByteStream/Read', - google_dot_bytestream_dot_bytestream__pb2.ReadRequest.SerializeToString, - google_dot_bytestream_dot_bytestream__pb2.ReadResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def Write(request_iterator, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.stream_unary( - request_iterator, - target, - '/google.bytestream.ByteStream/Write', - google_dot_bytestream_dot_bytestream__pb2.WriteRequest.SerializeToString, - google_dot_bytestream_dot_bytestream__pb2.WriteResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def QueryWriteStatus(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/google.bytestream.ByteStream/QueryWriteStatus', - google_dot_bytestream_dot_bytestream__pb2.QueryWriteStatusRequest.SerializeToString, - google_dot_bytestream_dot_bytestream__pb2.QueryWriteStatusResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/benchmark/proto_gen/google/longrunning/operations_pb2.py b/benchmark/proto_gen/google/longrunning/operations_pb2.py deleted file mode 100644 index b6084cc..0000000 --- a/benchmark/proto_gen/google/longrunning/operations_pb2.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: google/longrunning/operations.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'google/longrunning/operations.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2 -from google.api import client_pb2 as google_dot_api_dot_client__pb2 -from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2 -from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 -from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from google.rpc import status_pb2 as google_dot_rpc_dot_status__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#google/longrunning/operations.proto\x12\x12google.longrunning\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/protobuf/any.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x17google/rpc/status.proto\"\xa8\x01\n\tOperation\x12\x0c\n\x04name\x18\x01 \x01(\t\x12&\n\x08metadata\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any\x12\x0c\n\x04\x64one\x18\x03 \x01(\x08\x12#\n\x05\x65rror\x18\x04 \x01(\x0b\x32\x12.google.rpc.StatusH\x00\x12(\n\x08response\x18\x05 \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x42\x08\n\x06result\"#\n\x13GetOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"|\n\x15ListOperationsRequest\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x0e\n\x06\x66ilter\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\x12\x1e\n\x16return_partial_success\x18\x05 \x01(\x08\"~\n\x16ListOperationsResponse\x12\x31\n\noperations\x18\x01 \x03(\x0b\x32\x1d.google.longrunning.Operation\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\x12\x18\n\x0bunreachable\x18\x03 \x03(\tB\x03\xe0\x41\x06\"&\n\x16\x43\x61ncelOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"&\n\x16\x44\x65leteOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"P\n\x14WaitOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12*\n\x07timeout\x18\x02 \x01(\x0b\x32\x19.google.protobuf.Duration\"=\n\rOperationInfo\x12\x15\n\rresponse_type\x18\x01 \x01(\t\x12\x15\n\rmetadata_type\x18\x02 \x01(\t2\xaa\x05\n\nOperations\x12\x94\x01\n\x0eListOperations\x12).google.longrunning.ListOperationsRequest\x1a*.google.longrunning.ListOperationsResponse\"+\xda\x41\x0bname,filter\x82\xd3\xe4\x93\x02\x17\x12\x15/v1/{name=operations}\x12\x7f\n\x0cGetOperation\x12\'.google.longrunning.GetOperationRequest\x1a\x1d.google.longrunning.Operation\"\'\xda\x41\x04name\x82\xd3\xe4\x93\x02\x1a\x12\x18/v1/{name=operations/**}\x12~\n\x0f\x44\x65leteOperation\x12*.google.longrunning.DeleteOperationRequest\x1a\x16.google.protobuf.Empty\"\'\xda\x41\x04name\x82\xd3\xe4\x93\x02\x1a*\x18/v1/{name=operations/**}\x12\x88\x01\n\x0f\x43\x61ncelOperation\x12*.google.longrunning.CancelOperationRequest\x1a\x16.google.protobuf.Empty\"1\xda\x41\x04name\x82\xd3\xe4\x93\x02$\"\x1f/v1/{name=operations/**}:cancel:\x01*\x12Z\n\rWaitOperation\x12(.google.longrunning.WaitOperationRequest\x1a\x1d.google.longrunning.Operation\"\x00\x1a\x1d\xca\x41\x1alongrunning.googleapis.com:Z\n\x0eoperation_info\x12\x1e.google.protobuf.MethodOptions\x18\x99\x08 \x01(\x0b\x32!.google.longrunning.OperationInfoB\xa2\x01\n\x16\x63om.google.longrunningB\x0fOperationsProtoP\x01ZCcloud.google.com/go/longrunning/autogen/longrunningpb;longrunningpb\xa2\x02\x05GLRUN\xaa\x02\x12Google.LongRunning\xca\x02\x12Google\\LongRunningb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.longrunning.operations_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\026com.google.longrunningB\017OperationsProtoP\001ZCcloud.google.com/go/longrunning/autogen/longrunningpb;longrunningpb\242\002\005GLRUN\252\002\022Google.LongRunning\312\002\022Google\\LongRunning' - _globals['_LISTOPERATIONSRESPONSE'].fields_by_name['unreachable']._loaded_options = None - _globals['_LISTOPERATIONSRESPONSE'].fields_by_name['unreachable']._serialized_options = b'\340A\006' - _globals['_OPERATIONS']._loaded_options = None - _globals['_OPERATIONS']._serialized_options = b'\312A\032longrunning.googleapis.com' - _globals['_OPERATIONS'].methods_by_name['ListOperations']._loaded_options = None - _globals['_OPERATIONS'].methods_by_name['ListOperations']._serialized_options = b'\332A\013name,filter\202\323\344\223\002\027\022\025/v1/{name=operations}' - _globals['_OPERATIONS'].methods_by_name['GetOperation']._loaded_options = None - _globals['_OPERATIONS'].methods_by_name['GetOperation']._serialized_options = b'\332A\004name\202\323\344\223\002\032\022\030/v1/{name=operations/**}' - _globals['_OPERATIONS'].methods_by_name['DeleteOperation']._loaded_options = None - _globals['_OPERATIONS'].methods_by_name['DeleteOperation']._serialized_options = b'\332A\004name\202\323\344\223\002\032*\030/v1/{name=operations/**}' - _globals['_OPERATIONS'].methods_by_name['CancelOperation']._loaded_options = None - _globals['_OPERATIONS'].methods_by_name['CancelOperation']._serialized_options = b'\332A\004name\202\323\344\223\002$\"\037/v1/{name=operations/**}:cancel:\001*' - _globals['_OPERATION']._serialized_start=295 - _globals['_OPERATION']._serialized_end=463 - _globals['_GETOPERATIONREQUEST']._serialized_start=465 - _globals['_GETOPERATIONREQUEST']._serialized_end=500 - _globals['_LISTOPERATIONSREQUEST']._serialized_start=502 - _globals['_LISTOPERATIONSREQUEST']._serialized_end=626 - _globals['_LISTOPERATIONSRESPONSE']._serialized_start=628 - _globals['_LISTOPERATIONSRESPONSE']._serialized_end=754 - _globals['_CANCELOPERATIONREQUEST']._serialized_start=756 - _globals['_CANCELOPERATIONREQUEST']._serialized_end=794 - _globals['_DELETEOPERATIONREQUEST']._serialized_start=796 - _globals['_DELETEOPERATIONREQUEST']._serialized_end=834 - _globals['_WAITOPERATIONREQUEST']._serialized_start=836 - _globals['_WAITOPERATIONREQUEST']._serialized_end=916 - _globals['_OPERATIONINFO']._serialized_start=918 - _globals['_OPERATIONINFO']._serialized_end=979 - _globals['_OPERATIONS']._serialized_start=982 - _globals['_OPERATIONS']._serialized_end=1664 -# @@protoc_insertion_point(module_scope) diff --git a/benchmark/proto_gen/google/longrunning/operations_pb2_grpc.py b/benchmark/proto_gen/google/longrunning/operations_pb2_grpc.py deleted file mode 100644 index c5ac9c6..0000000 --- a/benchmark/proto_gen/google/longrunning/operations_pb2_grpc.py +++ /dev/null @@ -1,326 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - -from google.longrunning import operations_pb2 as google_dot_longrunning_dot_operations__pb2 -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 - -GRPC_GENERATED_VERSION = '1.78.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in google/longrunning/operations_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) - - -class OperationsStub(object): - """Manages long-running operations with an API service. - - When an API method normally takes long time to complete, it can be designed - to return [Operation][google.longrunning.Operation] to the client, and the - client can use this interface to receive the real response asynchronously by - polling the operation resource, or pass the operation resource to another API - (such as Pub/Sub API) to receive the response. Any API service that returns - long-running operations should implement the `Operations` interface so - developers can have a consistent client experience. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.ListOperations = channel.unary_unary( - '/google.longrunning.Operations/ListOperations', - request_serializer=google_dot_longrunning_dot_operations__pb2.ListOperationsRequest.SerializeToString, - response_deserializer=google_dot_longrunning_dot_operations__pb2.ListOperationsResponse.FromString, - _registered_method=True) - self.GetOperation = channel.unary_unary( - '/google.longrunning.Operations/GetOperation', - request_serializer=google_dot_longrunning_dot_operations__pb2.GetOperationRequest.SerializeToString, - response_deserializer=google_dot_longrunning_dot_operations__pb2.Operation.FromString, - _registered_method=True) - self.DeleteOperation = channel.unary_unary( - '/google.longrunning.Operations/DeleteOperation', - request_serializer=google_dot_longrunning_dot_operations__pb2.DeleteOperationRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - self.CancelOperation = channel.unary_unary( - '/google.longrunning.Operations/CancelOperation', - request_serializer=google_dot_longrunning_dot_operations__pb2.CancelOperationRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - self.WaitOperation = channel.unary_unary( - '/google.longrunning.Operations/WaitOperation', - request_serializer=google_dot_longrunning_dot_operations__pb2.WaitOperationRequest.SerializeToString, - response_deserializer=google_dot_longrunning_dot_operations__pb2.Operation.FromString, - _registered_method=True) - - -class OperationsServicer(object): - """Manages long-running operations with an API service. - - When an API method normally takes long time to complete, it can be designed - to return [Operation][google.longrunning.Operation] to the client, and the - client can use this interface to receive the real response asynchronously by - polling the operation resource, or pass the operation resource to another API - (such as Pub/Sub API) to receive the response. Any API service that returns - long-running operations should implement the `Operations` interface so - developers can have a consistent client experience. - """ - - def ListOperations(self, request, context): - """Lists operations that match the specified filter in the request. If the - server doesn't support this method, it returns `UNIMPLEMENTED`. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetOperation(self, request, context): - """Gets the latest state of a long-running operation. Clients can use this - method to poll the operation result at intervals as recommended by the API - service. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteOperation(self, request, context): - """Deletes a long-running operation. This method indicates that the client is - no longer interested in the operation result. It does not cancel the - operation. If the server doesn't support this method, it returns - `google.rpc.Code.UNIMPLEMENTED`. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CancelOperation(self, request, context): - """Starts asynchronous cancellation on a long-running operation. The server - makes a best effort to cancel the operation, but success is not - guaranteed. If the server doesn't support this method, it returns - `google.rpc.Code.UNIMPLEMENTED`. Clients can use - [Operations.GetOperation][google.longrunning.Operations.GetOperation] or - other methods to check whether the cancellation succeeded or whether the - operation completed despite cancellation. On successful cancellation, - the operation is not deleted; instead, it becomes an operation with - an [Operation.error][google.longrunning.Operation.error] value with a - [google.rpc.Status.code][google.rpc.Status.code] of `1`, corresponding to - `Code.CANCELLED`. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def WaitOperation(self, request, context): - """Waits until the specified long-running operation is done or reaches at most - a specified timeout, returning the latest state. If the operation is - already done, the latest state is immediately returned. If the timeout - specified is greater than the default HTTP/RPC timeout, the HTTP/RPC - timeout is used. If the server does not support this method, it returns - `google.rpc.Code.UNIMPLEMENTED`. - Note that this method is on a best-effort basis. It may return the latest - state before the specified timeout (including immediately), meaning even an - immediate response is no guarantee that the operation is done. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_OperationsServicer_to_server(servicer, server): - rpc_method_handlers = { - 'ListOperations': grpc.unary_unary_rpc_method_handler( - servicer.ListOperations, - request_deserializer=google_dot_longrunning_dot_operations__pb2.ListOperationsRequest.FromString, - response_serializer=google_dot_longrunning_dot_operations__pb2.ListOperationsResponse.SerializeToString, - ), - 'GetOperation': grpc.unary_unary_rpc_method_handler( - servicer.GetOperation, - request_deserializer=google_dot_longrunning_dot_operations__pb2.GetOperationRequest.FromString, - response_serializer=google_dot_longrunning_dot_operations__pb2.Operation.SerializeToString, - ), - 'DeleteOperation': grpc.unary_unary_rpc_method_handler( - servicer.DeleteOperation, - request_deserializer=google_dot_longrunning_dot_operations__pb2.DeleteOperationRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'CancelOperation': grpc.unary_unary_rpc_method_handler( - servicer.CancelOperation, - request_deserializer=google_dot_longrunning_dot_operations__pb2.CancelOperationRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'WaitOperation': grpc.unary_unary_rpc_method_handler( - servicer.WaitOperation, - request_deserializer=google_dot_longrunning_dot_operations__pb2.WaitOperationRequest.FromString, - response_serializer=google_dot_longrunning_dot_operations__pb2.Operation.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'google.longrunning.Operations', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('google.longrunning.Operations', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class Operations(object): - """Manages long-running operations with an API service. - - When an API method normally takes long time to complete, it can be designed - to return [Operation][google.longrunning.Operation] to the client, and the - client can use this interface to receive the real response asynchronously by - polling the operation resource, or pass the operation resource to another API - (such as Pub/Sub API) to receive the response. Any API service that returns - long-running operations should implement the `Operations` interface so - developers can have a consistent client experience. - """ - - @staticmethod - def ListOperations(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/google.longrunning.Operations/ListOperations', - google_dot_longrunning_dot_operations__pb2.ListOperationsRequest.SerializeToString, - google_dot_longrunning_dot_operations__pb2.ListOperationsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetOperation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/google.longrunning.Operations/GetOperation', - google_dot_longrunning_dot_operations__pb2.GetOperationRequest.SerializeToString, - google_dot_longrunning_dot_operations__pb2.Operation.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeleteOperation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/google.longrunning.Operations/DeleteOperation', - google_dot_longrunning_dot_operations__pb2.DeleteOperationRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def CancelOperation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/google.longrunning.Operations/CancelOperation', - google_dot_longrunning_dot_operations__pb2.CancelOperationRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def WaitOperation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/google.longrunning.Operations/WaitOperation', - google_dot_longrunning_dot_operations__pb2.WaitOperationRequest.SerializeToString, - google_dot_longrunning_dot_operations__pb2.Operation.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/benchmark/proto_gen/google/rpc/status_pb2.py b/benchmark/proto_gen/google/rpc/status_pb2.py deleted file mode 100644 index 78f43bf..0000000 --- a/benchmark/proto_gen/google/rpc/status_pb2.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: google/rpc/status.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'google/rpc/status.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17google/rpc/status.proto\x12\ngoogle.rpc\x1a\x19google/protobuf/any.proto\"N\n\x06Status\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\t\x12%\n\x07\x64\x65tails\x18\x03 \x03(\x0b\x32\x14.google.protobuf.AnyBa\n\x0e\x63om.google.rpcB\x0bStatusProtoP\x01Z7google.golang.org/genproto/googleapis/rpc/status;status\xf8\x01\x01\xa2\x02\x03RPCb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.rpc.status_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\016com.google.rpcB\013StatusProtoP\001Z7google.golang.org/genproto/googleapis/rpc/status;status\370\001\001\242\002\003RPC' - _globals['_STATUS']._serialized_start=66 - _globals['_STATUS']._serialized_end=144 -# @@protoc_insertion_point(module_scope) diff --git a/benchmark/proto_gen/google/rpc/status_pb2_grpc.py b/benchmark/proto_gen/google/rpc/status_pb2_grpc.py deleted file mode 100644 index fc98b74..0000000 --- a/benchmark/proto_gen/google/rpc/status_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.78.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in google/rpc/status_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/benchmark/requirements.txt b/benchmark/requirements.txt new file mode 100644 index 0000000..b1e7e25 --- /dev/null +++ b/benchmark/requirements.txt @@ -0,0 +1,7 @@ +# Python dependencies for the FerrisRBE benchmark suite. +# These are consumed by rules_python via pip.parse in MODULE.bazel. + +grpcio==1.64.1 +grpcio-tools==1.64.1 +protobuf==5.27.2 +setuptools==70.3.0 diff --git a/benchmark/results/.gitkeep b/benchmark/results/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/benchmark/results/BENCHMARK_REPORT_20260226_221106.md b/benchmark/results/BENCHMARK_REPORT_20260226_221106.md deleted file mode 100644 index 7f12ad0..0000000 --- a/benchmark/results/BENCHMARK_REPORT_20260226_221106.md +++ /dev/null @@ -1,23 +0,0 @@ -# RBE Benchmark Report - -**Date:** 2026-02-27 04:11:25 UTC -**Runner:** abel@MacBook-Pro-de-Cesar.local - -## Results - -solution memory_mb memory_limit cpu_percent status -FerrisRBE 6.7 7.653GiB .13 success - -## Methodology - -- Container started with default configuration -- 5-second warmup period -- 5 samples collected at 1-second intervals -- Average calculated from samples -- Container removed after measurement - -## Environment - -- OS: Darwin 24.6.0 -- Docker: Docker version 29.2.1, build a5c7197 -- Architecture: arm64 diff --git a/benchmark/results/BENCHMARK_REPORT_20260301_204418.md b/benchmark/results/BENCHMARK_REPORT_20260301_204418.md deleted file mode 100644 index 0cb61ac..0000000 --- a/benchmark/results/BENCHMARK_REPORT_20260301_204418.md +++ /dev/null @@ -1,171 +0,0 @@ -# FerrisRBE Benchmark Report - -**Generated:** 2026-03-02 02:44:18 UTC -**Commit:** N/A -**Mode:** unknown -**Image:** ferrisrbe/server:latest - -## Executive Summary - -| Metric | Value | Threshold | Status | -|--------|-------|-----------|--------| -| **Memory Footprint** | N/A MB | < 20 MB | ⚠️ | -| **Cold Start** | N/A ms | < 500 ms | ⚠️ | -| **Cache P99 Latency** | 30.73 ms | < 1 ms | ❌ | -| **Execution P99 Latency** | 215.92 ms | < 100 ms | ❌ | -| **Cleanup Rate** | N/A% | > 95% | ⚠️ | - -### Overall Status: ❌ FAIL - ---- - -## Detailed Results - -### 1. Memory Footprint - -**Container Memory Usage:** - -``` -Idle Memory: N/A MB -Threshold: 20 MB -Status: ⚠️ -``` - -**Analysis:** -Memory data not available or inconclusive. - -### 2. Cold Start Time - -**Container Startup Performance:** - -``` -Cold Start: N/A ms -Threshold: 500 ms -Status: ⚠️ -``` - -**Analysis:** -Cold start data not available. - -### 3. Action Cache Performance - -**Test Configuration:** -- Operations: 5000 -- Concurrency: 50 -- Type: read - -**Results:** - -| Metric | Value | -|--------|-------| -| Throughput | 138.39 ops/sec | -| P99 Latency | 30.73 ms | -| Success Rate | 5000/5000 | -| Cache Hits | 5000 (N/A -N/A%) | - -**Analysis:** -⚠️ Cache latency higher than expected. Check for contention or backend issues. - -### 4. CAS Operations - -**Test Configuration:** -- Blobs: 100 -- Size per blob: 1 MB -- Total data: 200 MB - -**Results:** - -| Operation | P50 | P95 | P99 | -|-----------|-----|-----|-----| -| Upload | 85.97 ms | 153.18 ms | 164.06 ms | -| Download | 66.46 ms | 88.68 ms | 102.26 ms | - -**Analysis:** -CAS operations completing successfully. Upload and download latencies are within acceptable ranges for blob storage operations. - -### 5. Execution Throughput - -**Test Configuration:** -- Actions: 50 -- Concurrency: 5 - -**Results:** - -| Metric | Value | -|--------|-------| -| Throughput | 8.39 actions/sec | -| Success | 46/50 | -| P50 Latency | 110.59 ms | -| P99 Latency | 215.92 ms | -| Jitter (StdDev) | 29.66 ms | - -**Analysis:** -⚠️ Execution latency exceeds threshold. Check scheduler and worker pool health. - -### 6. Cache Stampede Protection - -**Test Configuration:** -- Total Requests: 2000 -- Concurrency: 100 -- Target: Same uncached action digest - -**Results:** - -| Metric | Value | -|--------|-------| -| Cache Hits | 0 | -| Cache Misses | 2000 | -| Errors | 0 | -| P99/Mean Ratio | 2.26x | - -**Analysis:** -✅ Good stampede protection. Request coalescing or fast backend prevents thundering herd issues. - -### 7. Connection Churn - -**Results:** - -| Metric | Value | -|--------|-------| -| Connections Tested | N/A | -| Abrupt Disconnects | N/A | -| Cleanup Rate | N/A% | -| Zombie Resources | N/A | - -**Analysis:** -Connection churn data incomplete. - ---- - -## Comparison with Official Release - -*No comparison data available. Run `./scripts/compare-branches.sh` to compare against official release.* - ---- - -## Recommendations - -### ⚠️ Performance Regressions Detected - -1. Review failed metrics above -2. Compare against baseline (`xangcastle/ferris-server:latest`) -3. Profile the application to identify bottlenecks -4. Re-run benchmarks after optimizations - ---- - -## Raw Data Files - -| Test | File | -|------|------| -| Action Cache | `cache_full.json` | -| CAS Load | `cas_full.json` | -| Execution | `execution_full.json` | -| Cache Stampede | `stampede_full.json` | -| Connection Churn | `` | -| Benchmark Data | `` | - ---- - -*Generated by FerrisRBE Benchmark Suite v2.0 - Container Native* diff --git a/benchmark/results/BENCHMARK_REPORT_20260301_204540.md b/benchmark/results/BENCHMARK_REPORT_20260301_204540.md deleted file mode 100644 index 9fdeb20..0000000 --- a/benchmark/results/BENCHMARK_REPORT_20260301_204540.md +++ /dev/null @@ -1,170 +0,0 @@ -# FerrisRBE Benchmark Report - -**Generated:** 2026-03-02 02:45:41 UTC -**Commit:** N/A -**Mode:** unknown -**Image:** ferrisrbe/server:latest - -## Executive Summary - -| Metric | Value | Threshold | Status | -|--------|-------|-----------|--------| -| **Memory Footprint** | N/A MB | < 20 MB | ⚠️ | -| **Cold Start** | N/A ms | < 500 ms | ⚠️ | -| **Cache P99 Latency** | 30.73 ms | < 1 ms | ❌ | -| **Execution P99 Latency** | 215.92 ms | < 100 ms | ❌ | -| **Cleanup Rate** | N/A% | > 95% | ⚠️ | - -### Overall Status: ❌ FAIL - ---- - -## Detailed Results - -### 1. Memory Footprint - -**Container Memory Usage:** - -``` -Idle Memory: N/A MB -Threshold: 20 MB -Status: ⚠️ -``` - -**Analysis:** -Memory data not available or inconclusive. - -### 2. Cold Start Time - -**Container Startup Performance:** - -``` -Cold Start: N/A ms -Threshold: 500 ms -Status: ⚠️ -``` - -**Analysis:** -Cold start data not available. - -### 3. Action Cache Performance - -**Test Configuration:** -- Operations: 5000 -- Concurrency: 50 -- Type: read - -**Results:** - -| Metric | Value | -|--------|-------| -| Throughput | 138.39 ops/sec | -| P99 Latency | 30.73 ms | -| Success Rate | 5000/5000 (100.0%) | -| Cache Hits | 5000 | - -**Analysis:** -⚠️ Cache latency higher than expected. Check for contention or backend issues. - -### 4. CAS Operations - -**Test Configuration:** -- Blobs: 100 -- Size per blob: 1 MB -- Total data: 200 MB - -**Results:** - -| Operation | P50 | P95 | P99 | -|-----------|-----|-----|-----| -| Upload | 85.97 ms | 153.18 ms | 164.06 ms | -| Download | 66.46 ms | 88.68 ms | 102.26 ms | - -**Analysis:** -CAS operations completing successfully. Upload and download latencies are within acceptable ranges for blob storage operations. - -### 5. Execution Throughput - -**Test Configuration:** -- Actions: 50 -- Concurrency: 5 - -**Results:** - -| Metric | Value | -|--------|-------| -| Throughput | 8.39 actions/sec | -| Success | 46/50 | -| P50 Latency | 110.59 ms | -| P99 Latency | 215.92 ms | -| Jitter (StdDev) | 29.66 ms | - -**Analysis:** -⚠️ Execution latency exceeds threshold. Check scheduler and worker pool health. - -### 6. Cache Stampede Protection - -**Test Configuration:** -- Total Requests: 2000 -- Concurrency: 100 -- Target: Same uncached action digest - -**Results:** - -| Metric | Value | -|--------|-------| -| Cache Hits | 0 | -| Cache Misses | 2000 | -| Errors | 0 | -| P99/Mean Ratio | 2.26x | - -**Analysis:** -✅ Good stampede protection. Request coalescing or fast backend prevents thundering herd issues. - -### 7. Connection Churn - -**Results:** - -| Metric | Value | -|--------|-------| -| Connections Tested | N/A | -| Abrupt Disconnects | N/A | -| Cleanup Rate | N/A% | -| Zombie Resources | N/A | - -**Analysis:** -Connection churn data incomplete. - ---- - -## Comparison with Official Release - -*No comparison data available. Run `./scripts/compare-branches.sh` to compare against official release.* - ---- - -## Recommendations - -### ⚠️ Performance Regressions Detected - -1. Review failed metrics above -2. Compare against baseline (`xangcastle/ferris-server:latest`) -3. Profile the application to identify bottlenecks -4. Re-run benchmarks after optimizations - ---- - -## Raw Data Files - -| Test | File | -|------|------| -| Action Cache | `cache_full.json` | -| CAS Load | `cas_full.json` | -| Execution | `execution_full.json` | -| Cache Stampede | `stampede_full.json` | -| Connection Churn | `` | -| Benchmark Data | `` | - ---- - -*Generated by FerrisRBE Benchmark Suite v2.0 - Container Native* diff --git a/benchmark/results/BENCHMARK_RESULTS.md b/benchmark/results/BENCHMARK_RESULTS.md deleted file mode 100644 index c96eb8a..0000000 --- a/benchmark/results/BENCHMARK_RESULTS.md +++ /dev/null @@ -1,326 +0,0 @@ -# RBE Benchmark Results - -**Date:** 2026-02-26 -**Tester:** Automated Benchmark Suite -**Method:** Docker container isolation with 5-second warmup, 5 samples averaged - -## Executive Summary - -| Solution | Language | Idle Memory | Peak Memory | Notes | -|----------|----------|-------------|-------------|-------| -| **FerrisRBE** | Rust | **6.7 MB** | ~50-150 MB | ✅ Zero-GC, distroless | -| Buildbarn | Go | ~120-200 MB | ~300-500 MB | Minimal GC | -| Buildfarm | Java | ~800-1200 MB | ~3-4 GB | G1GC pauses | -| BuildBuddy | Java/Go | ~1.2-2 GB | ~4-6 GB | Enterprise features | - -## Measured Results - -### FerrisRBE (Our Solution) - -**Idle Memory Footprint:** 6.7 MB (averaged over 5 samples) - -``` -Container: bench-ferrisrbe-20260226_221106 -Image: ferrisrbe-server:latest (Rust 1.85 + distroless) -Memory: 6.7MiB / 7.653GiB (0.09%) -CPU: 0.13% -Status: success -``` - -**Key Observations:** -- Low baseline memory (~6.7 MB average) -- Zero garbage collection (Rust ownership model) -- Distroless container (no shell, no package manager) -- Predictable memory usage - no GC spikes - -**Under Load:** -- Peak observed: ~50-150 MB during heavy CAS operations -- O(1) streaming - memory doesn't grow with blob size -- p99 latency: Consistent (no GC pauses) - -### Peak Memory Observations (All Solutions) - -| Solution | Idle Memory | Peak Memory (Load) | Memory Pattern | -|----------|-------------|-------------------|----------------| -| **FerrisRBE** | 6.7 MB | ~50-150 MB | 📊 Flat line - no GC spikes | -| Buildbarn | 120-200 MB | ~400-800 MB | 📈 Moderate growth | -| Buildfarm | 800-1200 MB | ~2.5-4 GB | 📈📈 GC spikes visible | -| BuildBuddy | 1.2-2 GB | ~4-6 GB | 📈📈📈 High variance | - -**Key Insight:** FerrisRBE's peak-to-idle ratio is ~10-20x, while JVM solutions reach 3-5x. More importantly, FerrisRBE shows **zero GC spikes** - memory grows smoothly and predictably. - -### Comparative Analysis - -#### Memory Efficiency Ranking - -| Rank | Solution | Idle Memory | vs FerrisRBE | -|------|----------|-------------|--------------| -| 1 | **FerrisRBE** (Rust) | **6.7 MB** | **1x (baseline)** | -| 2 | Buildbarn (Go) | 120-200 MB | ~18-30x | -| 3 | Buildfarm (Java) | 800-1200 MB | ~120-180x | -| 4 | BuildBuddy (Java/Go) | 1.2-2 GB | ~180-300x | - -#### GC Behavior Comparison - -| Solution | GC Type | Pause Duration | Impact | -|----------|---------|----------------|--------| -| FerrisRBE | **None** | **0 ms** | ✅ No jitter | -| Buildbarn | Go GC | <1 ms | Minimal | -| Buildfarm | G1GC | 50-200 ms | ⚠️ Visible | -| BuildBuddy | G1GC | 50-200 ms | ⚠️ Visible | - -#### O(1) Streaming Comparison (Large File Handling) - -Test: Upload 5GB + 10GB files concurrent with 1KB files, measure memory delta - -| Solution | Memory Delta (5GB) | Memory Delta (10GB) | Behavior | -|----------|-------------------|---------------------|----------| -| **FerrisRBE** | **<50 MB** | **<50 MB** | ✅ O(1) - True streaming | -| Buildbarn | ~200-400 MB | ~400-800 MB | ⚠️ O(n) - Buffering detected | -| Buildfarm | ~1-2 GB | ~2-4 GB | ❌ O(n) - Risk of OOM | -| BuildBuddy | ~1.5-3 GB | ~3-6 GB | ❌ O(n) - High OOM risk | - -**Why FerrisRBE Wins:** Rust's async Tokio streams process data in chunks without buffering entire files. JVM solutions often buffer or have inefficient streaming implementations. - -#### Connection Churn Comparison (Resource Cleanup) - -Test: 1000 connections, 30% abrupt disconnections, measure cleanup rate - -| Solution | Cleanup Rate | Zombie Resources | Notes | -|----------|--------------|------------------|-------| -| **FerrisRBE** | **100%** | **None** | ✅ Tokio task cancellation | -| Buildbarn | ~95-98% | Minimal | Go goroutines cleanup well | -| Buildfarm | ~85-92% | Some threads persist | JVM thread cleanup delayed | -| BuildBuddy | ~80-90% | Connection leaks possible | Complex cleanup path | - -**Why FerrisRBE Wins:** Tokio's native task cancellation releases resources immediately when a connection drops. JVM requires garbage collection to clean up thread objects. - -#### Cache Stampede Comparison (Thundering Herd) - -Test: 10000 simultaneous requests for same uncached action digest - -| Solution | P99 Latency | P99/Mean Ratio | Backend Impact | -|----------|-------------|----------------|----------------| -| **FerrisRBE** | ~15 ms | **1.5x** | ✅ Request coalescing | -| Buildbarn | ~45 ms | 3.0x | Moderate load | -| Buildfarm | ~120 ms | 5.0x | Redis overloaded | -| BuildBuddy | ~200 ms | 6.0x | DB connection pool stress | - -**Why FerrisRBE Wins:** DashMap + in-memory L1 cache with request coalescing means only one backend lookup is performed for identical concurrent requests. - -## Methodology - -### Test Environment -- **OS:** macOS Darwin 24.6.0 (Docker Desktop) -- **Docker Version:** 29.2.1 -- **CPU:** Apple Silicon ARM64 -- **RAM:** 16 GB - -### Measurement Method -1. Build FerrisRBE server binary using Bazel (dogfooding) -2. Start server natively or in container (using OCI image built with rules_oci) -3. Wait 5 seconds for initialization -4. Collect memory readings (5 samples at 1-second intervals) -5. Calculate average memory usage -6. Stop server - -### Why These Metrics Matter - -**Idle Memory:** Represents the baseline cost of running the RBE infrastructure. Even with zero builds, JVM-based solutions consume 800MB-2GB of RAM. - -**GC Pauses:** During builds, garbage collection causes unpredictable latency spikes. This affects build consistency and developer experience. - -## Reproducibility - -### FerrisRBE Test (using Bazel) -```bash -# Build with Bazel (dogfooding - we use our own build system) -bazel build //:rbe-server --config=release - -# Run test (native binary) -./bazel-bin/rbe-server & -sleep 5 - -# Check memory -ps -o rss= -p $(pgrep rbe-server) | awk '{print $1/1024 "MB"}' - -# Stop server -kill %1 -``` - -### Buildfarm (Java/RBE Reference) -```bash -# Pull and run official Buildfarm server -docker run -d --name test-buildfarm -p 9092:9092 \ - -e JAVA_OPTS="-Xmx2g -Xms1g -XX:+UseG1GC" \ - bazelbuild/buildfarm-server:latest - -# Wait for JVM startup (slower than native) -sleep 15 -docker stats test-buildfarm --no-stream -docker rm -f test-buildfarm -``` - -### Buildbarn (Go/Modular) -```bash -# Note: Buildbarn requires multiple services (frontend, scheduler, storage) -# Using docker-compose is recommended -docker-compose -f docker-compose.buildbarn.yml up -d - -# Wait for all services -sleep 10 -docker stats buildbarn-frontend --no-stream -docker-compose -f docker-compose.buildbarn.yml down -v -``` - -### BuildBuddy (Java+Go/Enterprise) -```bash -# BuildBuddy requires PostgreSQL and Redis -# Using docker-compose is required -docker-compose -f docker-compose.buildbuddy.yml up -d - -# Wait for all services (slowest due to DB migrations) -sleep 30 -docker stats buildbuddy-server --no-stream -docker-compose -f docker-compose.buildbuddy.yml down -v -``` - -### Full Benchmark Suite -```bash -cd benchmark - -# FerrisRBE (fastest) -./scripts/benchmark.sh - -# All RBE solutions (takes longer due to JVM startup) -./scripts/run-benchmark.sh all -d 300 -``` - -### Comparative Testing -```bash -# Run same test against different RBE servers - -# 1. Start FerrisRBE -docker-compose -f docker-compose.ferrisrbe.yml up -d -./scripts/execution-load-test.py --server localhost:9092 --output ferrisrbe.json -docker-compose -f docker-compose.ferrisrbe.yml down - -# 2. Start Buildfarm -docker-compose -f docker-compose.buildfarm.yml up -d -./scripts/execution-load-test.py --server localhost:9092 --output buildfarm.json -docker-compose -f docker-compose.buildfarm.yml down - -# 3. Compare results -./scripts/compare-results.py ferrisrbe.json buildfarm.json -``` - -## Stress Test Results Summary - -### O(1) Streaming Test Results - -**Test Configuration:** -- Large files: 5GB, 10GB -- Small files: 1000 x 1KB -- Concurrent uploads: 10 - -**FerrisRBE Results:** -``` -5GB file: Memory delta 42 MB (✅ O(1) streaming confirmed) -10GB file: Memory delta 38 MB (✅ O(1) streaming confirmed) -1KB files: Avg latency 12ms (✅ No interference from large files) -``` - -**Buildfarm (Java) Results:** -``` -5GB file: Memory delta 1.2 GB (❌ O(n) behavior) -10GB file: Memory delta 2.8 GB (❌ Approaching heap limit) -GC pauses: 150-300ms observed during large uploads -``` - -### Connection Churn Test Results - -**Test Configuration:** -- Total connections: 1000 -- Abrupt disconnections: 30% (300 connections) -- Operations: mix of upload, download, execute - -**FerrisRBE Results:** -``` -Cleanup rate: 100% (1000/1000 connections) -Avg cleanup time: 2.3ms -Zombie resources: None detected -Memory stable after test: Yes -``` - -**Buildfarm (Java) Results:** -``` -Cleanup rate: 87% (870/1000 connections) -Zombie threads: ~130 persisted for 30+ seconds -Memory increase after test: +450 MB (leaked resources) -Required restart to reclaim memory: Yes -``` - -### Cache Stampede Test Results - -**Test Configuration:** -- Total requests: 10000 -- Concurrency: 100 -- Target: Single uncached action digest - -**FerrisRBE Results:** -``` -Cache hits: 0 (expected - uncached) -Cache misses: 10000 -P50 latency: 8ms -P99 latency: 15ms -P99/Mean ratio: 1.5x (✅ Coalescing working) -Backend queries: ~100 (coalesced from 10000) -``` - -**Buildfarm (Redis) Results:** -``` -Cache hits: 0 -Cache misses: 10000 -P50 latency: 35ms -P99 latency: 180ms -P99/Mean ratio: 5.1x (❌ No coalescing) -Redis CPU: 95%+ during test -Redis latency spikes: 200-500ms -``` - -## Conclusion - -FerrisRBE demonstrates **superior resource efficiency** compared to existing RBE solutions: - -- **18-300x less idle memory** than alternative solutions -- **Zero GC pauses** = predictable latencies -- **O(1) streaming** = constant memory regardless of artifact size (6.7MB → 50MB vs 800MB → 4GB) -- **Immediate resource cleanup** = no memory leaks on connection drops -- **Request coalescing** = protects backend from thundering herd - -### Cost Impact Analysis - -For a 20-node RBE cluster handling enterprise workloads: - -| Solution | Monthly Infra Cost | Notes | -|----------|-------------------|-------| -| **FerrisRBE** | **$200-400** | Small instances, no GC tuning needed | -| Buildbarn | $800-1200 | Medium instances | -| Buildfarm | $3000-5000 | Large instances, GC tuning required | -| BuildBuddy | $5000-8000 | XL instances, dedicated DBA | - -**Additional Savings with FerrisRBE:** -- No JVM tuning specialists required -- No midnight pages for GC pauses -- No OOM crashes during large artifact uploads -- Faster autoscaling = less over-provisioning - -This translates to: -- **Lower infrastructure costs** (more builds per node) -- **Better developer experience** (no GC-related build stalls) -- **Predictable performance at scale** (no OOM surprises) -- **Simpler operations** (no GC tuning, no memory leak debugging) - ---- - -*Generated by FerrisRBE Benchmark Suite v1.0* diff --git a/benchmark/results/LATEST_REPORT.md b/benchmark/results/LATEST_REPORT.md deleted file mode 120000 index a382472..0000000 --- a/benchmark/results/LATEST_REPORT.md +++ /dev/null @@ -1 +0,0 @@ -BENCHMARK_REPORT_20260301_204540.md \ No newline at end of file diff --git a/benchmark/results/benchmark_20260226_221106.csv b/benchmark/results/benchmark_20260226_221106.csv deleted file mode 100644 index 8b8d01d..0000000 --- a/benchmark/results/benchmark_20260226_221106.csv +++ /dev/null @@ -1,2 +0,0 @@ -solution,memory_mb,memory_limit,cpu_percent,status -FerrisRBE,6.7,7.653GiB,.13,success diff --git a/benchmark/results/cache_20260307_140658.json b/benchmark/results/cache_20260307_140658.json deleted file mode 100644 index 5359a5b..0000000 --- a/benchmark/results/cache_20260307_140658.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "server": "localhost:9092", - "operation": "read", - "total_operations": 1000, - "concurrent": 20, - "success_count": 1000, - "hit_count": 1000, - "throughput": 361.3945719977252, - "latencies_us": { - "min": 965.6659999999206, - "max": 9943.75000000003, - "mean": 2767.058714999998, - "p50": 2638.791500000015, - "p95": 4029.916999999994, - "p99": 7931.250000000056 - } -} \ No newline at end of file diff --git a/benchmark/results/cache_full.json b/benchmark/results/cache_full.json deleted file mode 100644 index ca2eb2e..0000000 --- a/benchmark/results/cache_full.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "server": "localhost:9092", - "operation": "read", - "total_operations": 5000, - "concurrent": 50, - "success_count": 5000, - "hit_count": 5000, - "throughput": 138.38576885561284, - "latencies_us": { - "min": 345.9580000000351, - "max": 45388.74999999987, - "mean": 7226.176566199999, - "p50": 6730.8750000000255, - "p95": 13010.291000000063, - "p99": 30726.87499999982 - } -} \ No newline at end of file diff --git a/benchmark/results/cache_test.json b/benchmark/results/cache_test.json deleted file mode 100644 index cc3a878..0000000 --- a/benchmark/results/cache_test.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "server": "localhost:9092", - "operation": "read", - "total_operations": 100, - "concurrent": 10, - "success_count": 100, - "hit_count": 100, - "throughput": 632.1742172512086, - "latencies_us": { - "min": 693.3329999999904, - "max": 3512.582999999986, - "mean": 1581.84243, - "p50": 1544.645499999997, - "p95": 2301.041000000004, - "p99": 3512.582999999986 - } -} \ No newline at end of file diff --git a/benchmark/results/cas_20260307_140637.json b/benchmark/results/cas_20260307_140637.json deleted file mode 100644 index cbcd244..0000000 --- a/benchmark/results/cas_20260307_140637.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "server": "localhost:9092", - "total_blobs": 500, - "blob_size": 1024, - "concurrent": 20, - "success_count": 1000, - "fail_count": 0, - "total_bytes": 1024000, - "upload_latencies": { - "min": 3.2224579999999836, - "max": 53.49845800000003, - "mean": 10.929769918, - "p50": 8.084042000000014, - "p95": 28.46599999999999, - "p99": 50.57270799999996 - }, - "download_latencies": { - "min": 2.102375000000045, - "max": 9.13629199999999, - "mean": 5.843897655999997, - "p50": 5.880416499999985, - "p95": 7.993417000000003, - "p99": 9.025165999999917 - } -} \ No newline at end of file diff --git a/benchmark/results/cas_full.json b/benchmark/results/cas_full.json deleted file mode 100644 index f487d24..0000000 --- a/benchmark/results/cas_full.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "server": "localhost:9092", - "total_blobs": 100, - "blob_size": 1048576, - "concurrent": 10, - "success_count": 200, - "fail_count": 0, - "total_bytes": 209715200, - "upload_latencies": { - "min": 35.87604199999994, - "max": 164.063042, - "mean": 95.33555956000001, - "p50": 85.97164549999991, - "p95": 153.178458, - "p99": 164.063042 - }, - "download_latencies": { - "min": 23.425209000000002, - "max": 102.25691700000006, - "mean": 64.6854783, - "p50": 66.4585000000002, - "p95": 88.68416700000003, - "p99": 102.25691700000006 - } -} \ No newline at end of file diff --git a/benchmark/results/cas_test.json b/benchmark/results/cas_test.json deleted file mode 100644 index 9807ef8..0000000 --- a/benchmark/results/cas_test.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "server": "localhost:9092", - "total_blobs": 50, - "blob_size": 1048576, - "concurrent": 5, - "success_count": 100, - "fail_count": 0, - "total_bytes": 104857600, - "upload_latencies": { - "min": 19.620833000000005, - "max": 107.01904200000001, - "mean": 55.4323208, - "p50": 43.56212500000001, - "p95": 105.81591600000002, - "p99": 107.01904200000001 - }, - "download_latencies": { - "min": 20.71520799999993, - "max": 37.116916, - "mean": 31.03744246000001, - "p50": 31.687396000000035, - "p95": 32.874833000000045, - "p99": 37.116916 - } -} \ No newline at end of file diff --git a/benchmark/results/execution_20260307_140520.json b/benchmark/results/execution_20260307_140520.json deleted file mode 100644 index 65c5af9..0000000 --- a/benchmark/results/execution_20260307_140520.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "server": "localhost:9092", - "total_actions": 100, - "concurrent": 10, - "success_count": 91, - "fail_count": 9, - "throughput": 8.318678831040733, - "latencies": { - "min": 105.06441699999996, - "max": 215.42766700000016, - "mean": 120.2113965824176, - "p50": 111.71262499999912, - "p95": 211.50358300000073, - "p99": 215.42766700000016, - "stddev": 28.52947102859011 - } -} \ No newline at end of file diff --git a/benchmark/results/execution_full.json b/benchmark/results/execution_full.json deleted file mode 100644 index f01af3d..0000000 --- a/benchmark/results/execution_full.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "server": "localhost:9092", - "total_actions": 50, - "concurrent": 5, - "success_count": 46, - "fail_count": 4, - "throughput": 8.390112316472845, - "latencies": { - "min": 103.70224999999999, - "max": 215.92079199999992, - "mean": 119.18791576086961, - "p50": 110.59343750000016, - "p95": 213.21108300000003, - "p99": 215.92079199999992, - "stddev": 29.657308792540896 - } -} \ No newline at end of file diff --git a/benchmark/results/execution_mini.json b/benchmark/results/execution_mini.json deleted file mode 100644 index 4774ae3..0000000 --- a/benchmark/results/execution_mini.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "server": "localhost:9092", - "total_actions": 5, - "concurrent": 1, - "success_count": 5, - "fail_count": 0, - "throughput": 9.125715399051565, - "latencies": { - "min": 105.652833, - "max": 110.92666699999998, - "mean": 109.58045, - "p50": 110.288, - "p95": 110.92666699999998, - "p99": 110.92666699999998, - "stddev": 2.2258612924723056 - } -} \ No newline at end of file diff --git a/benchmark/results/execution_test.json b/benchmark/results/execution_test.json deleted file mode 100644 index 6b1ce40..0000000 --- a/benchmark/results/execution_test.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "server": "localhost:9092", - "total_actions": 100, - "concurrent": 10, - "success_count": 91, - "fail_count": 9, - "throughput": 8.467962800125518, - "latencies": { - "min": 105.227792, - "max": 213.79258299999958, - "mean": 118.09215789010992, - "p50": 112.59341600000016, - "p95": 211.63620799999984, - "p99": 213.79258299999958, - "stddev": 23.166264782645115 - } -} \ No newline at end of file diff --git a/benchmark/results/scheduler_full.json b/benchmark/results/scheduler_full.json deleted file mode 100644 index 7575db3..0000000 --- a/benchmark/results/scheduler_full.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "server": "localhost:9092", - "slow_actions": 5, - "fast_actions": 20, - "fast_timings": [ - { - "action_id": "fast-17", - "queue_time_ms": 14.33873176574707, - "total_time_ms": 124.237060546875 - }, - { - "action_id": "fast-14", - "queue_time_ms": 17.732858657836914, - "total_time_ms": 179455.38878440857 - }, - { - "action_id": "fast-6", - "queue_time_ms": 20.009756088256836, - "total_time_ms": 179456.1891555786 - }, - { - "action_id": "fast-13", - "queue_time_ms": 16.30401611328125, - "total_time_ms": 179455.98196983337 - }, - { - "action_id": "fast-4", - "queue_time_ms": 17.93503761291504, - "total_time_ms": 179457.01909065247 - }, - { - "action_id": "fast-9", - "queue_time_ms": 17.176151275634766, - "total_time_ms": 179456.67815208435 - }, - { - "action_id": "fast-19", - "queue_time_ms": 17.364978790283203, - "total_time_ms": 179456.09211921692 - }, - { - "action_id": "fast-10", - "queue_time_ms": 19.481897354125977, - "total_time_ms": 179456.9399356842 - }, - { - "action_id": "fast-5", - "queue_time_ms": 16.130924224853516, - "total_time_ms": 179457.65590667725 - }, - { - "action_id": "fast-15", - "queue_time_ms": 16.5250301361084, - "total_time_ms": 179456.91108703613 - }, - { - "action_id": "fast-0", - "queue_time_ms": 17.302989959716797, - "total_time_ms": 179458.49609375 - }, - { - "action_id": "fast-12", - "queue_time_ms": 17.01807975769043, - "total_time_ms": 179457.55410194397 - }, - { - "action_id": "fast-11", - "queue_time_ms": 17.780065536499023, - "total_time_ms": 179457.91602134705 - }, - { - "action_id": "fast-2", - "queue_time_ms": 18.468141555786133, - "total_time_ms": 179458.86206626892 - }, - { - "action_id": "fast-18", - "queue_time_ms": 14.845848083496094, - "total_time_ms": 179457.89003372192 - }, - { - "action_id": "fast-7", - "queue_time_ms": 19.639968872070312, - "total_time_ms": 179458.8737487793 - }, - { - "action_id": "fast-16", - "queue_time_ms": 17.24076271057129, - "total_time_ms": 179458.55689048767 - }, - { - "action_id": "fast-1", - "queue_time_ms": 18.91303062438965, - "total_time_ms": 179459.84888076782 - }, - { - "action_id": "fast-8", - "queue_time_ms": 15.231847763061523, - "total_time_ms": 179459.38086509705 - }, - { - "action_id": "fast-3", - "queue_time_ms": 20.804166793823242, - "total_time_ms": 179460.16788482666 - } - ], - "slow_timings": [ - { - "action_id": "slow-4", - "queue_time_ms": 16.875028610229492, - "total_time_ms": 226.59611701965332 - }, - { - "action_id": "slow-2", - "queue_time_ms": 21.734952926635742, - "total_time_ms": 179455.28292655945 - }, - { - "action_id": "slow-1", - "queue_time_ms": 18.910884857177734, - "total_time_ms": 179456.91013336182 - }, - { - "action_id": "slow-3", - "queue_time_ms": 21.79408073425293, - "total_time_ms": 179459.37728881836 - }, - { - "action_id": "slow-0", - "queue_time_ms": 18.221139907836914, - "total_time_ms": 179460.15095710754 - } - ] -} \ No newline at end of file diff --git a/benchmark/results/stampede_20260307_141427.json b/benchmark/results/stampede_20260307_141427.json deleted file mode 100644 index 252a45e..0000000 --- a/benchmark/results/stampede_20260307_141427.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "server": "localhost:9092", - "total_requests": 500, - "concurrent": 50, - "cache_hits": 0, - "cache_misses": 500, - "errors": 0, - "latencies_ms": { - "min": 3.0068329999999865, - "max": 21.55291599999998, - "mean": 8.370380163999998, - "p50": 7.851562499999992, - "p95": 13.87866700000001, - "p99": 18.065666999999994 - } -} \ No newline at end of file diff --git a/benchmark/results/stampede_full.json b/benchmark/results/stampede_full.json deleted file mode 100644 index 5ec9d03..0000000 --- a/benchmark/results/stampede_full.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "server": "localhost:9092", - "total_requests": 2000, - "concurrent": 100, - "cache_hits": 0, - "cache_misses": 2000, - "errors": 0, - "latencies_ms": { - "min": 4.7489589999999975, - "max": 51.38925, - "mean": 16.126950702, - "p50": 15.67260450000002, - "p95": 28.381042000000022, - "p99": 36.462875000000004 - } -} \ No newline at end of file diff --git a/benchmark/results/stampede_test.json b/benchmark/results/stampede_test.json deleted file mode 100644 index 95f41a1..0000000 --- a/benchmark/results/stampede_test.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "server": "localhost:9092", - "total_requests": 500, - "concurrent": 50, - "cache_hits": 0, - "cache_misses": 500, - "errors": 0, - "latencies_ms": { - "min": 2.8326250000000055, - "max": 20.406875000000007, - "mean": 7.655330233999999, - "p50": 7.227895499999984, - "p95": 12.466875000000002, - "p99": 16.765791000000004 - } -} \ No newline at end of file diff --git a/benchmark/scripts/BUILD.bazel b/benchmark/scripts/BUILD.bazel new file mode 100644 index 0000000..61fcc60 --- /dev/null +++ b/benchmark/scripts/BUILD.bazel @@ -0,0 +1,106 @@ +load("@rules_python//python:defs.bzl", "py_binary", "py_library") +load("@benchmark_pip//:requirements.bzl", "requirement") + +package(default_visibility = ["//benchmark:__subpackages__"]) + +py_library( + name = "benchmark_lib", + srcs = ["benchmark_lib.py"], + imports = [".."], + deps = [ + "//benchmark/proto:reapi_py_proto", + requirement("grpcio"), + requirement("protobuf"), + ], +) + +py_binary( + name = "execution_load_test", + srcs = ["execution-load-test.py"], + main = "execution-load-test.py", + deps = [ + ":benchmark_lib", + "//benchmark/proto:reapi_py_proto", + ], +) + +py_binary( + name = "action_cache_test", + srcs = ["action-cache-test.py"], + main = "action-cache-test.py", + deps = [ + ":benchmark_lib", + "//benchmark/proto:reapi_py_proto", + ], +) + +py_binary( + name = "cas_load_test", + srcs = ["cas-load-test.py"], + main = "cas-load-test.py", + deps = [ + ":benchmark_lib", + "//benchmark/proto:reapi_py_proto", + ], +) + +py_binary( + name = "o1_streaming_test", + srcs = ["o1-streaming-test.py"], + main = "o1-streaming-test.py", + deps = [ + ":benchmark_lib", + "//benchmark/proto:reapi_py_proto", + ], +) + +py_binary( + name = "connection_churn_test", + srcs = ["connection-churn-test.py"], + main = "connection-churn-test.py", + deps = [ + ":benchmark_lib", + "//benchmark/proto:reapi_py_proto", + ], +) + +py_binary( + name = "cache_stampede_test", + srcs = ["cache-stampede-test.py"], + main = "cache-stampede-test.py", + deps = [ + ":benchmark_lib", + "//benchmark/proto:reapi_py_proto", + ], +) + +py_binary( + name = "noisy_neighbor_test", + srcs = ["noisy-neighbor-test.py"], + main = "noisy-neighbor-test.py", + deps = [ + ":benchmark_lib", + "//benchmark/proto:reapi_py_proto", + ], +) + +py_binary( + name = "check_regression", + srcs = ["check-regression.py"], + main = "check-regression.py", + deps = [":benchmark_lib"], +) + +filegroup( + name = "all", + srcs = [ + ":action_cache_test", + ":cache_stampede_test", + ":cas_load_test", + ":check_regression", + ":connection_churn_test", + ":execution_load_test", + ":noisy_neighbor_test", + ":o1_streaming_test", + ], +) diff --git a/benchmark/scripts/action-cache-test.py b/benchmark/scripts/action-cache-test.py index 378a9bc..9907041 100755 --- a/benchmark/scripts/action-cache-test.py +++ b/benchmark/scripts/action-cache-test.py @@ -10,8 +10,6 @@ import argparse import asyncio import hashlib -import os -import sys import time import statistics from dataclasses import dataclass, field @@ -20,14 +18,8 @@ import grpc -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'proto_gen')) - -try: - from build.bazel.remote.execution.v2 import remote_execution_pb2 - from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc -except ImportError: - print("Warning: Protocol buffer modules not found.") - sys.exit(1) +from build.bazel.remote.execution.v2 import remote_execution_pb2 +from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc @dataclass diff --git a/benchmark/scripts/bazel-utils.sh b/benchmark/scripts/bazel-utils.sh deleted file mode 100755 index 489b994..0000000 --- a/benchmark/scripts/bazel-utils.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/bin/bash -# Bazel utility functions for benchmark scripts -# Handles custom symlink prefixes and output locations - -# Get the actual Bazel output directory (handles custom --symlink_prefix) -get_bazel_bin() { - local workspace="${1:-.}" - cd "$workspace" && bazel info bazel-bin 2>/dev/null -} - -# Get the actual Bazel genfiles directory -get_bazel_genfiles() { - local workspace="${1:-.}" - cd "$workspace" && bazel info bazel-genfiles 2>/dev/null -} - -# Get Bazel execution root -get_bazel_execution_root() { - local workspace="${1:-.}" - cd "$workspace" && bazel info execution_root 2>/dev/null -} - -# Find a Bazel output file, searching in multiple possible locations -find_bazel_output() { - local target="$1" # e.g., "//:rbe-server" - local workspace="${2:-.}" - - cd "$workspace" - - # Get actual bazel-bin path - local bazel_bin=$(bazel info bazel-bin 2>/dev/null) - - if [ -z "$bazel_bin" ]; then - echo "" - return 1 - fi - - # Convert target to path - # //:rbe-server -> bazel-bin/rbe-server - # //src:server -> bazel-bin/src/server - local target_path=$(echo "$target" | sed 's|//||' | sed 's|^:||' | sed 's|:|/|g') - - local full_path="$bazel_bin/$target_path" - - if [ -f "$full_path" ]; then - echo "$full_path" - return 0 - fi - - # Try common variations - local variations=( - "$bazel_bin/rbe-server" - "$bazel_bin/rbe-worker" - ) - - for path in "${variations[@]}"; do - if [ -f "$path" ]; then - echo "$path" - return 0 - fi - done - - echo "" - return 1 -} - -# Build a target and return the output path -bazel_build_and_get_output() { - local target="$1" - local workspace="${2:-.}" - local config="${3:-release}" - - cd "$workspace" - - # Build - bazel build "$target" --config="$config" 2>/dev/null || bazel build "$target" - - # Find output - local output=$(find_bazel_output "$target" "$workspace") - - if [ -n "$output" ] && [ -f "$output" ]; then - echo "$output" - return 0 - fi - - echo "" - return 1 -} - -# Check if Bazel is available -check_bazel() { - if ! command -v bazel &> /dev/null; then - echo "ERROR: Bazel not found" >&2 - return 1 - fi - - local version=$(bazel --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') - if [ -n "$version" ]; then - echo "Bazel $version" - return 0 - fi - - return 0 -} - -# Get workspace root -get_workspace_root() { - local dir="${1:-.}" - cd "$dir" && bazel info workspace 2>/dev/null || pwd -} - -# Export functions if sourced -if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then - export -f get_bazel_bin - export -f get_bazel_genfiles - export -f get_bazel_execution_root - export -f find_bazel_output - export -f bazel_build_and_get_output - export -f check_bazel - export -f get_workspace_root -fi diff --git a/benchmark/scripts/benchmark-ci.sh b/benchmark/scripts/benchmark-ci.sh deleted file mode 100755 index 728cc53..0000000 --- a/benchmark/scripts/benchmark-ci.sh +++ /dev/null @@ -1,731 +0,0 @@ -#!/bin/bash -# CI/CD Benchmark Script - Container-Native Version -# Runs benchmarks using Docker containers (like production deployment) -# Works with any Bazel configuration (--symlink_prefix) -# -# This script tests the actual OCI images that get deployed to Kubernetes, -# providing more realistic benchmarks than native binary execution. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BENCHMARK_DIR="$(dirname "$SCRIPT_DIR")" - -# Source Bazel utilities -source "$SCRIPT_DIR/bazel-utils.sh" - -PROJECT_ROOT="$(get_workspace_root "$BENCHMARK_DIR/..")" -RESULTS_DIR="$BENCHMARK_DIR/results" -MODE="${1:-light}" - -# Container configuration -CONTAINER_NAME="ferrisrbe-benchmark" -NETWORK_NAME="ferrisrbe-benchmark-net" -TIMESTAMP=$(date +%Y%m%d_%H%M%S) -CONTAINER_NAME_FULL="${CONTAINER_NAME}-${TIMESTAMP}" - -LOCAL_IMAGE_TAG="ferrisrbe/server:latest" -LOCAL_WORKER_IMAGE_TAG="ferrisrbe/worker:latest" -WORKER_CONTAINER_NAME="ferrisrbe-worker" -WORKER_CONTAINER_FULL="${WORKER_CONTAINER_NAME}-${TIMESTAMP}" - -# Worker configuration -WORKER_MEMORY_LIMIT="${WORKER_MEMORY_LIMIT:-512m}" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } - -mkdir -p "$RESULTS_DIR" - -cleanup() { - log_info "Cleaning up containers and network..." - - # Stop and remove benchmark container - if docker ps -q --filter "name=${CONTAINER_NAME_FULL}" | grep -q .; then - log_info "Stopping benchmark container..." - docker stop "${CONTAINER_NAME_FULL}" >/dev/null 2>&1 || true - docker rm "${CONTAINER_NAME_FULL}" >/dev/null 2>&1 || true - fi - - # Stop and remove any lingering containers with our prefix - docker ps -aq --filter "name=${CONTAINER_NAME}-" | xargs -r docker rm -f >/dev/null 2>&1 || true - - # Stop worker container - stop_worker_container - - # Remove network if it exists - if docker network ls --format '{{.Name}}' | grep -q "^${NETWORK_NAME}$"; then - docker network rm "${NETWORK_NAME}" >/dev/null 2>&1 || true - fi - - # Stop supporting services if we started them - stop_services - - log_success "Cleanup complete" -} - -trap cleanup EXIT - -# Build FerrisRBE OCI image using Bazel (dogfooding) -build_image() { - log_info "Building FerrisRBE OCI image with Bazel..." - - cd "$PROJECT_ROOT" - - # Detect architecture - local arch=$(uname -m) - local load_target="//oci:server_load_amd64" - - if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then - load_target="//oci:server_load" - log_info "Detected ARM64 architecture" - else - log_info "Detected AMD64 architecture" - fi - - # Build and load image to Docker - log_info "Building and loading OCI image: $load_target" - if ! bazel run "$load_target" 2>&1; then - log_error "Failed to build and load OCI image" - log_info "Make sure Bazel is installed and configured" - exit 1 - fi - - # Verify image was loaded - if ! docker image inspect "$LOCAL_IMAGE_TAG" >/dev/null 2>&1; then - log_error "Image $LOCAL_IMAGE_TAG not found in Docker" - exit 1 - fi - - log_success "OCI image built and loaded: $LOCAL_IMAGE_TAG" - - # Get image size - local image_size=$(docker images --format "{{.Size}}" "$LOCAL_IMAGE_TAG" | head -1) - log_info "Image size: $image_size" -} - -# Start supporting services (rbe-cache for CAS) -start_services() { - log_info "Starting supporting services..." - - # Check if we're using GitHub Actions services - if [ -n "$BENCHMARK_SERVICES" ]; then - log_info "Using GitHub Actions services for rbe-cache..." - # Wait for the service to be ready (GitHub Actions starts it automatically) - log_info "Waiting for rbe-cache to initialize..." - for i in {1..90}; do - if nc -z localhost 9094 2>/dev/null; then - log_success "CAS (rbe-cache) is ready on port 9094" - sleep 3 - return 0 - fi - if [ $((i % 10)) -eq 0 ]; then - log_info "Still waiting for rbe-cache... (attempt $i/90)" - fi - sleep 2 - done - log_warn "rbe-cache did not become ready in time" - return 1 - fi - - # Check if we're in local mode (auto-detect and start rbe-cache if needed) - if [ -n "$BENCHMARK_LOCAL" ] || [ -z "$BENCHMARK_SERVICES" ]; then - log_info "Checking for rbe-cache..." - - # Check if rbe-cache is already running (container or native) - if docker ps --filter "name=rbe-cache" --format '{{.Names}}' | grep -q "rbe-cache" || \ - nc -z localhost 9094 2>/dev/null; then - log_success "CAS (rbe-cache) already running on port 9094" - return 0 - fi - - # Try to start rbe-cache in Docker - start_rbe_cache_container - return $? - fi - - # Verify CAS is available - if nc -z localhost 9094 2>/dev/null; then - log_success "CAS (rbe-cache) available on port 9094" - else - log_warn "CAS not available on port 9094, some tests may fail" - fi -} - -# Start rbe-cache in a container -start_rbe_cache_container() { - log_info "Starting rbe-cache container..." - - # Check if a container already exists (stopped) - if docker ps -aq --filter "name=rbe-cache" | grep -q .; then - log_info "Removing existing rbe-cache container..." - docker rm -f rbe-cache >/dev/null 2>&1 || true - fi - - # Create data directory - mkdir -p "$RESULTS_DIR/rbe-cache-data" - - # Start container - docker run -d \ - --name rbe-cache \ - --network host \ - -p 9094:9094 \ - -p 8080:8080 \ - -v "$RESULTS_DIR/rbe-cache-data:/data" \ - -e RUST_LOG=info \ - -e RBE_CACHE_PORT=9094 \ - -e RBE_CACHE_HTTP_PORT=8080 \ - -e RBE_CACHE_DIR=/data \ - -e RBE_CACHE_MAX_SIZE_GB=1 \ - -e RBE_CACHE_AC_MAX_SIZE_GB=1 \ - xangcastle/ferris-cache:latest >/dev/null 2>&1 - - RBE_CACHE_CONTAINER="rbe-cache" - - # Wait for rbe-cache to be ready - for i in {1..60}; do - if nc -z localhost 9094 2>/dev/null; then - log_success "rbe-cache container ready" - return 0 - fi - if [ $((i % 10)) -eq 0 ]; then - log_info "Waiting for rbe-cache... (attempt $i/60)" - fi - sleep 1 - done - - log_warn "rbe-cache failed to start within 60 seconds" - docker logs rbe-cache 2>/dev/null || true - return 1 -} - -# Stop supporting services -stop_services() { - if [ -n "$RBE_CACHE_CONTAINER" ]; then - log_info "Stopping rbe-cache container..." - docker stop "$RBE_CACHE_CONTAINER" >/dev/null 2>&1 || true - docker rm "$RBE_CACHE_CONTAINER" >/dev/null 2>&1 || true - fi -} - -# Start FerrisRBE server in container -start_server_container() { - local image_tag="${1:-$LOCAL_IMAGE_TAG}" - local container_name="${CONTAINER_NAME_FULL}" - - log_info "Starting FerrisRBE server container: $image_tag" - - # Verify image exists - if ! docker image inspect "$image_tag" >/dev/null 2>&1; then - log_error "Image $image_tag not found in Docker" - log_info "Available images:" - docker images | grep ferrisrbe || true - exit 1 - fi - - # Ensure CAS_ENDPOINT is set - if [ -z "$CAS_ENDPOINT" ]; then - export CAS_ENDPOINT="localhost:9094" - fi - - log_info "Using CAS_ENDPOINT: $CAS_ENDPOINT" - - # Run container with host networking for simplicity - # This matches how many CI environments work - docker run -d \ - --name "$container_name" \ - --network host \ - -p 9092:9092 \ - -e RBE_PORT=9092 \ - -e RBE_BIND_ADDRESS=0.0.0.0 \ - -e RUST_LOG=info \ - -e CAS_ENDPOINT="$CAS_ENDPOINT" \ - -e RBE_L1_CACHE_CAPACITY=100000 \ - -e RBE_L1_CACHE_TTL_SECS=3600 \ - --memory=512m \ - --memory-reservation=64m \ - "$image_tag" >/dev/null 2>&1 - - SERVER_CONTAINER="$container_name" - - # Wait for server to be ready - log_info "Waiting for server to be ready..." - for i in {1..60}; do - if nc -z localhost 9092 2>/dev/null; then - log_success "Server container ready" - return 0 - fi - if [ $((i % 10)) -eq 0 ]; then - log_info "Waiting for server... (attempt $i/60)" - docker logs "$container_name" --tail 5 2>/dev/null || true - fi - sleep 1 - done - - log_error "Server failed to start within 60 seconds" - docker logs "$container_name" 2>/dev/null || true - exit 1 -} - -# Stop server container -stop_server_container() { - local container_name="${1:-$SERVER_CONTAINER}" - - if [ -n "$container_name" ]; then - log_info "Stopping server container: $container_name" - docker stop "$container_name" >/dev/null 2>&1 || true - docker rm "$container_name" >/dev/null 2>&1 || true - fi -} - -# Start worker container -start_worker_container() { - local image_tag="${1:-$LOCAL_WORKER_IMAGE_TAG}" - local container_name="${WORKER_CONTAINER_FULL}" - - log_info "Starting FerrisRBE worker container: $image_tag" - - # Verify image exists - if ! docker image inspect "$image_tag" >/dev/null 2>&1; then - log_error "Worker image $image_tag not found" - exit 1 - fi - - # Generate unique worker ID - local worker_id="benchmark-worker-$(hostname)-$(date +%s)" - - # Ensure CAS_ENDPOINT is set - if [ -z "$CAS_ENDPOINT" ]; then - export CAS_ENDPOINT="localhost:9094" - fi - - log_info "Using SERVER_ENDPOINT: http://localhost:9092" - log_info "Using CAS_ENDPOINT: $CAS_ENDPOINT" - log_info "Worker ID: $worker_id" - - # Start container with host networking - # Note: Using host networking for simplicity in CI, but could use bridge network - docker run -d \ - --name "$container_name" \ - --network host \ - -e WORKER_ID="$worker_id" \ - -e SERVER_ENDPOINT="http://localhost:9092" \ - -e CAS_ENDPOINT="$CAS_ENDPOINT" \ - -e RUST_LOG=info \ - -e MAX_CONCURRENT=4 \ - --memory="$WORKER_MEMORY_LIMIT" \ - "$image_tag" >/dev/null 2>&1 - - WORKER_CONTAINER="$container_name" - - # Wait for worker to register - log_info "Waiting for worker to register..." - for i in {1..30}; do - logs=$(docker logs "$container_name" 2>&1 || true) - # Check for successful registration indicators and no errors - if echo "$logs" | grep -qiE "(registration successful|connected to server|worker ready)" && \ - ! echo "$logs" | grep -qiE "(error|failed|connection refused)"; then - log_success "Worker registered successfully" - sleep 1 - return 0 - fi - # Check for early errors - if echo "$logs" | grep -qiE "(error|failed|connection refused)"; then - log_error "Worker failed to start. Logs:" - echo "$logs" | tail -20 - exit 1 - fi - if [ $((i % 10)) -eq 0 ]; then - log_info "Waiting for worker registration... (attempt $i/30)" - docker logs "$container_name" --tail 3 2>/dev/null || true - fi - sleep 1 - done - - log_warn "Worker may not have registered yet, continuing anyway..." - docker logs "$container_name" --tail 10 2>/dev/null || true -} - -# Stop worker container -stop_worker_container() { - local container_name="${1:-$WORKER_CONTAINER}" - - if [ -n "$container_name" ]; then - log_info "Stopping worker container: $container_name" - docker stop "$container_name" >/dev/null 2>&1 || true - docker rm "$container_name" >/dev/null 2>&1 || true - fi -} - -# Get container memory usage -get_container_memory() { - local container_name="${1:-$SERVER_CONTAINER}" - - # Get memory usage in MB from docker stats - docker stats "$container_name" --no-stream --format "{{.MemUsage}}" 2>/dev/null | \ - awk '{print $1}' | sed 's/MiB//' | sed 's/GiB/*1024/' | bc 2>/dev/null || echo "0" -} - -# Run memory benchmark using container stats -run_memory_benchmark() { - log_info "Running memory footprint benchmark (container mode)..." - - # Wait for container to stabilize - sleep 3 - - # Collect multiple samples - for i in {1..5}; do - get_container_memory "$SERVER_CONTAINER" - sleep 1 - done | tee "$RESULTS_DIR/memory_${TIMESTAMP}.txt" - - # Parse and store result (median of non-zero values) - MEMORY_MB=$(grep -v "^0$" "$RESULTS_DIR/memory_${TIMESTAMP}.txt" | sort -n | head -1) - if [ -z "$MEMORY_MB" ]; then - MEMORY_MB="0" - fi - - echo "$MEMORY_MB" > "$RESULTS_DIR/memory_baseline.txt" - - log_success "Memory baseline: ${MEMORY_MB}MB (container)" - - # Also get container stats for additional info - docker stats "$SERVER_CONTAINER" --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}" > "$RESULTS_DIR/container_stats_${TIMESTAMP}.txt" 2>/dev/null || true -} - -# Run lightweight benchmarks -run_light_benchmarks() { - log_info "=== LIGHTWEIGHT BENCHMARK SUITE (Container) ===" - log_info "Running quick tests suitable for PR validation..." - - # 1. Memory footprint (quick) - log_info "Test 1/5: Memory footprint..." - run_memory_benchmark - - # Start worker for execution tests - log_info "Starting worker container for execution tests..." - start_worker_container - sleep 2 - - # 2. Execution throughput (reduced load) - log_info "Test 2/5: Execution throughput (light)..." - python3 "$SCRIPT_DIR/execution-load-test.py" \ - --server localhost:9092 \ - --actions 100 \ - --concurrent 10 \ - --output "$RESULTS_DIR/execution_${TIMESTAMP}.json" || true - - # 3. Action cache (reduced operations) - log_info "Test 3/5: Action cache performance (light)..." - python3 "$SCRIPT_DIR/action-cache-test.py" \ - --server localhost:9092 \ - --operations 1000 \ - --concurrent 20 \ - --operation read \ - --output "$RESULTS_DIR/cache_${TIMESTAMP}.json" || true - - # 4. Cold start (single measurement) - measured AFTER rbe-cache is ready - log_info "Test 4/5: Cold start time (container)..." - - # Ensure rbe-cache is ready before measuring cold start - log_info " Verifying rbe-cache is ready..." - if ! nc -z localhost 9094 2>/dev/null; then - log_warn " rbe-cache not ready, waiting..." - for i in {1..30}; do - if nc -z localhost 9094 2>/dev/null; then - break - fi - sleep 1 - done - fi - - if nc -z localhost 9094 2>/dev/null; then - log_info " rbe-cache ready, measuring container cold start..." - - # Stop current container - stop_server_container "$SERVER_CONTAINER" - sleep 1 - - # Measure container startup time - START_TIME=$(date +%s%N) - - # Start new container - NEW_CONTAINER="${CONTAINER_NAME}-coldstart-${TIMESTAMP}" - docker run -d \ - --name "$NEW_CONTAINER" \ - --network host \ - -p 9092:9092 \ - -e RBE_PORT=9092 \ - -e RBE_BIND_ADDRESS=0.0.0.0 \ - -e RUST_LOG=info \ - -e CAS_ENDPOINT="$CAS_ENDPOINT" \ - "$LOCAL_IMAGE_TAG" >/dev/null 2>&1 - - # Wait for server to be ready - for i in {1..60}; do - if nc -z localhost 9092 2>/dev/null; then - END_TIME=$(date +%s%N) - COLD_START_MS=$(( (END_TIME - START_TIME) / 1000000 )) - echo "$COLD_START_MS" > "$RESULTS_DIR/coldstart_${TIMESTAMP}.txt" - log_success "Cold start: ${COLD_START_MS}ms (container startup + server ready)" - break - fi - sleep 0.1 - done - - # Update SERVER_CONTAINER to new one - stop_server_container "$SERVER_CONTAINER" - SERVER_CONTAINER="$NEW_CONTAINER" - - if ! nc -z localhost 9092 2>/dev/null; then - log_error "Server container failed to start for cold start measurement" - stop_server_container "$NEW_CONTAINER" - fi - else - log_warn " rbe-cache not available, skipping cold start measurement" - echo "0" > "$RESULTS_DIR/coldstart_${TIMESTAMP}.txt" - fi - - # 5. Connection churn (reduced) - log_info "Test 5/5: Connection churn (light)..." - python3 "$SCRIPT_DIR/connection-churn-test.py" \ - --server localhost:9092 \ - --connections 100 \ - --disconnect-rate 0.3 \ - --output "$RESULTS_DIR/churn_${TIMESTAMP}.json" || true - - log_success "Lightweight benchmarks complete!" -} - -# Run full benchmarks -run_full_benchmarks() { - log_info "=== FULL BENCHMARK SUITE (Container) ===" - log_info "Running comprehensive tests (this will take time)..." - - # 1. Memory footprint (extended) - log_info "Test 1/8: Memory footprint..." - run_memory_benchmark - - # Start worker for execution tests - log_info "Starting worker container for execution tests..." - start_worker_container - sleep 2 - - # 2. Execution throughput - log_info "Test 2/8: Execution throughput..." - python3 "$SCRIPT_DIR/execution-load-test.py" \ - --server localhost:9092 \ - --actions 1000 \ - --concurrent 50 \ - --output "$RESULTS_DIR/execution_${TIMESTAMP}.json" - - # 3. Action cache - log_info "Test 3/8: Action cache performance..." - python3 "$SCRIPT_DIR/action-cache-test.py" \ - --server localhost:9092 \ - --operations 10000 \ - --concurrent 100 \ - --operation read \ - --output "$RESULTS_DIR/cache_${TIMESTAMP}.json" - - # 4. Noisy neighbor (scheduler fairness) - log_info "Test 4/8: Scheduler fairness (noisy neighbor)..." - python3 "$SCRIPT_DIR/noisy-neighbor-test.py" \ - --server localhost:9092 \ - --slow 10 \ - --fast 50 \ - --output "$RESULTS_DIR/scheduler_${TIMESTAMP}.json" - - # 5. O(1) Streaming (with smaller files for CI) - log_info "Test 5/8: O(1) Streaming..." - python3 "$SCRIPT_DIR/o1-streaming-test.py" \ - --server localhost:9092 \ - --large-sizes 1 \ - --small-count 100 \ - --container "$SERVER_CONTAINER" \ - --output "$RESULTS_DIR/streaming_${TIMESTAMP}.json" || true - - # 6. Connection churn - log_info "Test 6/8: Connection churn..." - python3 "$SCRIPT_DIR/connection-churn-test.py" \ - --server localhost:9092 \ - --connections 1000 \ - --disconnect-rate 0.3 \ - --output "$RESULTS_DIR/churn_${TIMESTAMP}.json" - - # 7. Cache stampede - log_info "Test 7/8: Cache stampede (thundering herd)..." - python3 "$SCRIPT_DIR/cache-stampede-test.py" \ - --server localhost:9092 \ - --requests 10000 \ - --concurrent 100 \ - --output "$RESULTS_DIR/stampede_${TIMESTAMP}.json" - - # 8. CAS load test - log_info "Test 8/8: CAS operations..." - python3 "$SCRIPT_DIR/cas-load-test.py" \ - --server localhost:9092 \ - --blobs 100 \ - --size 1048576 \ - --concurrent 10 \ - --output "$RESULTS_DIR/cas_${TIMESTAMP}.json" - - log_success "Full benchmarks complete!" -} - -# Generate summary report -generate_summary() { - log_info "Generating summary report..." - - SUMMARY_FILE="$RESULTS_DIR/benchmark_summary.md" - - # Get container image info - local image_id=$(docker images --format "{{.ID}}" "$LOCAL_IMAGE_TAG" | head -1) - local image_created=$(docker images --format "{{.CreatedAt}}" "$LOCAL_IMAGE_TAG" | head -1) - local image_size=$(docker images --format "{{.Size}}" "$LOCAL_IMAGE_TAG" | head -1) - - cat > "$SUMMARY_FILE" << EOF -### Benchmark Results Summary (Container Mode) - -**Mode:** ${MODE} -**Timestamp:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") -**Commit:** ${GITHUB_SHA:-N/A} -**Image:** ${LOCAL_IMAGE_TAG} -**Image ID:** ${image_id:-N/A} -**Image Size:** ${image_size:-N/A} - -EOF - - # Memory results - if [ -f "$RESULTS_DIR/memory_baseline.txt" ]; then - MEMORY=$(cat "$RESULTS_DIR/memory_baseline.txt") - echo "#### Memory Footprint (Container)" >> "$SUMMARY_FILE" - echo "- **Idle Memory:** ${MEMORY} MB" >> "$SUMMARY_FILE" - echo "" >> "$SUMMARY_FILE" - - # Threshold check - if (( $(echo "$MEMORY > 20" | bc -l) )); then - echo "⚠️ **WARNING:** Memory usage (${MEMORY}MB) exceeds threshold (20MB)" >> "$SUMMARY_FILE" - else - echo "✅ Memory usage within expected range" >> "$SUMMARY_FILE" - fi - echo "" >> "$SUMMARY_FILE" - fi - - # Cold start results - if [ -f "$RESULTS_DIR/coldstart_${TIMESTAMP}.txt" ]; then - COLD_START=$(cat "$RESULTS_DIR/coldstart_${TIMESTAMP}.txt") - echo "#### Cold Start Time (Container)" >> "$SUMMARY_FILE" - echo "- **Startup Time:** ${COLD_START}ms" >> "$SUMMARY_FILE" - echo "" >> "$SUMMARY_FILE" - - if [ "$COLD_START" -gt 500 ]; then - echo "⚠️ **WARNING:** Cold start (${COLD_START}ms) exceeds threshold (500ms)" >> "$SUMMARY_FILE" - else - echo "✅ Cold start within expected range" >> "$SUMMARY_FILE" - fi - echo "" >> "$SUMMARY_FILE" - fi - - # Container stats - if [ -f "$RESULTS_DIR/container_stats_${TIMESTAMP}.txt" ]; then - echo "#### Container Statistics" >> "$SUMMARY_FILE" - echo "\`\`\`" >> "$SUMMARY_FILE" - cat "$RESULTS_DIR/container_stats_${TIMESTAMP}.txt" >> "$SUMMARY_FILE" - echo "\`\`\`" >> "$SUMMARY_FILE" - echo "" >> "$SUMMARY_FILE" - fi - - # JSON results summary - echo "#### Detailed Results" >> "$SUMMARY_FILE" - echo "" >> "$SUMMARY_FILE" - - for json in "$RESULTS_DIR"/*.json; do - if [ -f "$json" ]; then - BASENAME=$(basename "$json" .json) - echo "- ${BASENAME}: ✅ Completed" >> "$SUMMARY_FILE" - fi - done - - echo "" >> "$SUMMARY_FILE" - echo "---" >> "$SUMMARY_FILE" - echo "*Generated by FerrisRBE Benchmark Suite (Container Mode)*" >> "$SUMMARY_FILE" - - # Also generate JSON for programmatic access - cat > "$RESULTS_DIR/benchmark_data.json" << EOF -{ - "timestamp": "$TIMESTAMP", - "mode": "$MODE", - "commit": "${GITHUB_SHA:-N/A}", - "container": { - "image": "$LOCAL_IMAGE_TAG", - "image_id": "${image_id:-null}", - "image_size": "${image_size:-null}", - "container_name": "$SERVER_CONTAINER" - }, - "results": { - "memory_mb": $(cat "$RESULTS_DIR/memory_baseline.txt" 2>/dev/null || echo "null"), - "cold_start_ms": $(cat "$RESULTS_DIR/coldstart_${TIMESTAMP}.txt" 2>/dev/null || echo "null") - } -} -EOF - - log_success "Summary generated: $SUMMARY_FILE" - - # Display summary - cat "$SUMMARY_FILE" -} - -# Main execution -main() { - log_info "Starting CI Benchmark Suite (Container Mode)" - log_info "Mode: $MODE" - log_info "Results directory: $RESULTS_DIR" - - # Check Docker is available - if ! command -v docker &> /dev/null; then - log_error "Docker is not installed or not in PATH" - exit 1 - fi - - log_info "Docker version: $(docker --version)" - - # Build OCI image - build_image - - # Start supporting services first - start_services - - # Start server container - start_server_container "$LOCAL_IMAGE_TAG" - - # Run appropriate benchmark suite - case "$MODE" in - light) - run_light_benchmarks - ;; - full) - run_full_benchmarks - ;; - *) - log_error "Unknown mode: $MODE. Use 'light' or 'full'" - exit 1 - ;; - esac - - # Generate summary - generate_summary - - log_success "Benchmark suite complete!" -} - -main "$@" diff --git a/benchmark/scripts/benchmark-local.sh b/benchmark/scripts/benchmark-local.sh deleted file mode 100755 index 5ac115f..0000000 --- a/benchmark/scripts/benchmark-local.sh +++ /dev/null @@ -1,362 +0,0 @@ -#!/bin/bash -# Local Benchmark Script -# Run benchmarks locally without CI dependencies -# Supports Docker for rbe-cache or local binary - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BENCHMARK_DIR="$(dirname "$SCRIPT_DIR")" - -# Source Bazel utilities -source "$SCRIPT_DIR/bazel-utils.sh" - -PROJECT_ROOT="$(get_workspace_root "$BENCHMARK_DIR/..")" -RESULTS_DIR="$BENCHMARK_DIR/results" -MODE="${1:-light}" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } - -mkdir -p "$RESULTS_DIR" - -# Generate timestamp -TIMESTAMP=$(date +%Y%m%d_%H%M%S) - -# Check prerequisites -check_prerequisites() { - log_info "Checking prerequisites..." - - local missing=() - - if ! command -v nc &> /dev/null; then - missing+=("netcat (nc)") - fi - - if ! command -v python3 &> /dev/null; then - missing+=("python3") - fi - - if [ ${#missing[@]} -gt 0 ]; then - log_error "Missing prerequisites: ${missing[*]}" - log_info "Install with:" - log_info " macOS: brew install netcat python3" - log_info " Linux: sudo apt-get install netcat-openbsd python3" - exit 1 - fi - - log_success "Prerequisites OK" -} - -# Default cache image for the local benchmark (built and loaded by Bazel). -# Override with RBE_CACHE_IMAGE to use a different image. -RBE_CACHE_IMAGE="${RBE_CACHE_IMAGE:-ferrisrbe/cache:latest}" - -# Build the rbe-cache OCI image using Bazel and load it into Docker. -build_cache_image_with_bazel() { - if ! command -v bazel &> /dev/null; then - return 1 - fi - - local target="//oci:cache_load" - local arch - arch=$(uname -m) - case "$arch" in - x86_64|amd64) target="//oci:cache_load_amd64" ;; - arm64|aarch64) target="//oci:cache_load" ;; - esac - - log_info "Building rbe-cache image with Bazel ($target)..." - cd "$PROJECT_ROOT" - bazel run "$target" -} - -# Start rbe-cache using Docker (preferred) or local binary -start_cas() { - log_info "Starting CAS (rbe-cache)..." - - # Check if already running - if nc -z localhost 9094 2>/dev/null; then - log_success "CAS already running on port 9094" - return 0 - fi - - # Try Docker first - if command -v docker &> /dev/null; then - # Build the image with Bazel if it isn't available locally yet. - if ! docker image inspect "$RBE_CACHE_IMAGE" >/dev/null 2>&1; then - log_info "Cache image $RBE_CACHE_IMAGE not found locally." - if ! build_cache_image_with_bazel; then - log_warn "Could not build image with Bazel, trying local binary..." - fi - fi - - log_info "Starting rbe-cache via Docker ($RBE_CACHE_IMAGE)..." - docker run -d \ - --name ferrisrbe-benchmark-cas \ - -p 9094:9094 \ - -p 8080:8080 \ - -v "$RESULTS_DIR/rbe-cache-data:/data" \ - -e RUST_LOG=info \ - -e RBE_CACHE_PORT=9094 \ - -e RBE_CACHE_HTTP_PORT=8080 \ - -e RBE_CACHE_DIR=/data \ - -e RBE_CACHE_MAX_SIZE_GB=1 \ - -e RBE_CACHE_AC_MAX_SIZE_GB=1 \ - "$RBE_CACHE_IMAGE" 2>/dev/null && { - - # Wait for it to be ready - for i in {1..30}; do - if nc -z localhost 9094 2>/dev/null; then - log_success "CAS ready (Docker)" - return 0 - fi - sleep 1 - done - } - - log_warn "Docker failed to start CAS, trying local binary..." - docker rm -f ferrisrbe-benchmark-cas 2>/dev/null || true - fi - - # Try local binary - if command -v rbe-cache &> /dev/null; then - log_info "Starting rbe-cache via local binary..." - mkdir -p "$RESULTS_DIR/rbe-cache-data" - RBE_CACHE_PORT=9094 \ - RBE_CACHE_HTTP_PORT=8080 \ - RBE_CACHE_DIR="$RESULTS_DIR/rbe-cache-data" \ - RBE_CACHE_MAX_SIZE_GB=1 \ - RBE_CACHE_AC_MAX_SIZE_GB=1 \ - rbe-cache & - RBE_CACHE_PID=$! - - # Wait for it to be ready - for i in {1..30}; do - if nc -z localhost 9094 2>/dev/null; then - log_success "CAS ready (local binary, PID: $RBE_CACHE_PID)" - return 0 - fi - sleep 1 - done - - log_warn "Local binary failed to start CAS" - kill $RBE_CACHE_PID 2>/dev/null || true - fi - - log_error "Could not start CAS. Please install one of:" - log_error " - Docker/Podman: https://docs.docker.com/get-docker/" - log_error " - Build rbe-cache locally: bazel build //:rbe-cache" - exit 1 -} - -# Stop CAS -stop_cas() { - if [ -n "$RBE_CACHE_PID" ]; then - log_info "Stopping local rbe-cache..." - kill $RBE_CACHE_PID 2>/dev/null || true - wait $RBE_CACHE_PID 2>/dev/null || true - fi - - # Also stop Docker container if we started it - if command -v docker &> /dev/null; then - docker rm -f ferrisrbe-benchmark-cas 2>/dev/null || true - fi -} - -# Get server binary -get_server_binary() { - # Try Bazel output first - local bazel_bin=$(get_bazel_bin "$PROJECT_ROOT" 2>/dev/null) - if [ -n "$bazel_bin" ] && [ -f "$bazel_bin/rbe-server" ]; then - echo "$bazel_bin/rbe-server" - return 0 - fi - - # Not found - echo "" - return 1 -} - -# Build server -build_server() { - log_info "Building FerrisRBE server..." - - cd "$PROJECT_ROOT" - - if command -v bazel &> /dev/null; then - log_info "Building with Bazel..." - bazel build //:rbe-server --config=release - else - log_error "Bazel not found. Please install Bazelisk." - exit 1 - fi - - log_success "Build complete" -} - -# Start server -SERVER_PID="" -start_server() { - local binary="$(get_server_binary)" - - if [ -z "$binary" ]; then - log_info "Binary not found, building..." - build_server - binary="$(get_server_binary)" - fi - - log_info "Starting server: $binary" - - export CAS_ENDPOINT="localhost:9094" - "$binary" & - SERVER_PID=$! - - # Wait for server - log_info "Waiting for server to be ready..." - for i in {1..30}; do - if nc -z localhost 9092 2>/dev/null; then - log_success "Server ready (PID: $SERVER_PID)" - return 0 - fi - sleep 1 - done - - log_error "Server failed to start" - return 1 -} - -# Stop server -stop_server() { - if [ -n "$SERVER_PID" ]; then - log_info "Stopping server..." - kill $SERVER_PID 2>/dev/null || true - wait $SERVER_PID 2>/dev/null || true - fi -} - -# Cleanup on exit -cleanup() { - log_info "Cleaning up..." - stop_server - stop_cas -} -trap cleanup EXIT - -# Run memory benchmark -run_memory_benchmark() { - log_info "Running memory benchmark..." - - for i in {1..5}; do - ps -o rss= -p $SERVER_PID 2>/dev/null | awk '{print $1/1024}' || echo "0" - sleep 1 - done | tee "$RESULTS_DIR/memory_${TIMESTAMP}.txt" - - local memory=$(grep -v "^0$" "$RESULTS_DIR/memory_${TIMESTAMP}.txt" | head -1) - [ -z "$memory" ] && memory="0" - - echo "$memory" > "$RESULTS_DIR/memory_baseline.txt" - log_success "Memory baseline: ${memory}MB" -} - -# Run quick benchmarks -run_benchmarks() { - log_info "=== LOCAL BENCHMARK SUITE ($MODE) ===" - - # 1. Memory - log_info "Test 1/3: Memory footprint..." - run_memory_benchmark - - # 2. Execution throughput - log_info "Test 2/3: Execution throughput..." - python3 "$SCRIPT_DIR/execution-load-test.py" \ - --server localhost:9092 \ - --actions 100 \ - --concurrent 10 \ - --output "$RESULTS_DIR/execution_${TIMESTAMP}.json" || { - log_warn "Execution test failed (CAS may be unavailable)" - } - - # 3. Action cache - log_info "Test 3/3: Action cache performance..." - python3 "$SCRIPT_DIR/action-cache-test.py" \ - --server localhost:9092 \ - --operations 1000 \ - --concurrent 20 \ - --operation read \ - --output "$RESULTS_DIR/cache_${TIMESTAMP}.json" || { - log_warn "Cache test failed (CAS may be unavailable)" - } -} - -# Generate summary -generate_summary() { - log_info "Generating summary..." - - local summary_file="$RESULTS_DIR/benchmark_summary.md" - - cat > "$summary_file" << EOF -### Local Benchmark Results - -**Mode:** ${MODE} -**Timestamp:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") - -EOF - - if [ -f "$RESULTS_DIR/memory_baseline.txt" ]; then - local memory=$(cat "$RESULTS_DIR/memory_baseline.txt") - echo "#### Memory Footprint" >> "$summary_file" - echo "- **Idle Memory:** ${memory} MB" >> "$summary_file" - echo "" >> "$summary_file" - - if command -v bc &> /dev/null; then - if (( $(echo "$memory > 20" | bc -l) )); then - echo "⚠️ **WARNING:** Memory usage (${memory}MB) exceeds threshold (20MB)" >> "$summary_file" - else - echo "✅ Memory usage within expected range" >> "$summary_file" - fi - fi - echo "" >> "$summary_file" - fi - - echo "#### Test Results" >> "$summary_file" - for json in "$RESULTS_DIR"/*.json; do - if [ -f "$json" ]; then - local basename=$(basename "$json" .json) - echo "- ${basename}: ✅ Completed" >> "$summary_file" - fi - done - - echo "" >> "$summary_file" - echo "---" >> "$summary_file" - echo "*Generated by FerrisRBE Local Benchmark*" >> "$summary_file" - - cat "$summary_file" -} - -# Main -main() { - log_info "========================================" - log_info "FerrisRBE Local Benchmark" - log_info "========================================" - - check_prerequisites - start_cas - start_server - run_benchmarks - generate_summary - - log_success "Benchmark complete!" - log_info "Results: $RESULTS_DIR/" -} - -main "$@" diff --git a/benchmark/scripts/benchmark.sh b/benchmark/scripts/benchmark.sh deleted file mode 100755 index 26d0b8a..0000000 --- a/benchmark/scripts/benchmark.sh +++ /dev/null @@ -1,187 +0,0 @@ -#!/bin/bash -# RBE Memory Footprint Benchmark -# Professional benchmark script with proper metric collection - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -RESULTS_DIR="${SCRIPT_DIR}/../results" -TIMESTAMP=$(date +%Y%m%d_%H%M%S) - -mkdir -p "$RESULTS_DIR" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } - -# Parse memory value from docker stats to MB -parse_memory_mb() { - local mem_str="$1" - # Extract first value (before /) - local value=$(echo "$mem_str" | awk '{print $1}') - - # Convert to MB - if [[ "$value" == *"GiB" ]]; then - echo "$value" | sed 's/GiB//' | awk '{printf "%.1f", $1 * 1024}' - elif [[ "$value" == *"MiB" ]]; then - echo "$value" | sed 's/MiB//' - elif [[ "$value" == *"KiB" ]]; then - echo "$value" | sed 's/KiB//' | awk '{printf "%.1f", $1 / 1024}' - else - echo "0" - fi -} - -# Run benchmark for a single solution -run_benchmark() { - local name="$1" - local image="$2" - local port="$3" - local env_vars="${4:-}" - - log_info "Benchmarking $name..." - - # Run container - local container_name="bench-$(echo "$name" | tr '[:upper:]' '[:lower:]')-${TIMESTAMP}" - local run_cmd="docker run -d --name $container_name --network benchmark-network -p $port:$port" - - if [ -n "$env_vars" ]; then - run_cmd="$run_cmd $env_vars" - fi - - run_cmd="$run_cmd $image" - - eval "$run_cmd" 2>/dev/null || { - log_warn "Failed to start $name container" - echo "$name,N/A,N/A,N/A,failed" >> "$RESULTS_DIR/benchmark_${TIMESTAMP}.csv" - return 1 - } - - # Wait for initialization - sleep 5 - - # Collect multiple samples - local samples=() - local cpu_samples=() - - for i in {1..5}; do - local stats=$(docker stats "$container_name" --no-stream --format "{{.MemUsage}},{{.MemPerc}},{{.CPUPerc}}" 2>/dev/null) - if [ -n "$stats" ]; then - local mem_str=$(echo "$stats" | cut -d',' -f1) - local mem_mb=$(parse_memory_mb "$mem_str") - local cpu_pct=$(echo "$stats" | cut -d',' -f3 | sed 's/%//') - - samples+=("$mem_mb") - cpu_samples+=("$cpu_pct") - fi - sleep 1 - done - - # Calculate average - local avg_mem=0 - local avg_cpu=0 - - if [ ${#samples[@]} -gt 0 ]; then - local sum=0 - for s in "${samples[@]}"; do - sum=$(echo "$sum + $s" | bc) - done - avg_mem=$(echo "scale=1; $sum / ${#samples[@]}" | bc) - - local cpu_sum=0 - for c in "${cpu_samples[@]}"; do - cpu_sum=$(echo "$cpu_sum + $c" | bc) - done - avg_cpu=$(echo "scale=2; $cpu_sum / ${#cpu_samples[@]}" | bc) - fi - - # Get final stats - local final_stats=$(docker stats "$container_name" --no-stream --format "{{.MemUsage}},{{.MemPerc}}" 2>/dev/null) - local mem_limit=$(echo "$final_stats" | cut -d',' -f1 | awk -F'/' '{print $2}' | xargs) - - # Cleanup - docker rm -f "$container_name" >/dev/null 2>&1 - - # Output results - echo "$name,$avg_mem,$mem_limit,$avg_cpu,success" >> "$RESULTS_DIR/benchmark_${TIMESTAMP}.csv" - - log_success "$name: ${avg_mem} MB (CPU: ${avg_cpu}%)" -} - -# Main benchmark -main() { - echo "========================================" - echo "RBE Memory Footprint Benchmark" - echo "Timestamp: $TIMESTAMP" - echo "========================================" - echo "" - - # Create network - docker network create benchmark-network 2>/dev/null || true - - # Initialize CSV - echo "solution,memory_mb,memory_limit,cpu_percent,status" > "$RESULTS_DIR/benchmark_${TIMESTAMP}.csv" - - # Check if FerrisRBE image exists - if ! docker image inspect ferrisrbe-server:latest >/dev/null 2>&1; then - log_warn "FerrisRBE image not found. Build with Bazel:" - echo " cd benchmark && ./scripts/build-with-bazel.sh image" - echo "" - echo "Or manually:" - echo " bazel build //oci:server_image" - echo " bazel run //oci:server_load" - exit 1 - fi - - # Benchmark FerrisRBE - run_benchmark "FerrisRBE" "ferrisrbe-server:latest" "9092" "-e RBE_PORT=9092 -e RBE_BIND_ADDRESS=0.0.0.0" - - # Cleanup network - docker network rm benchmark-network 2>/dev/null || true - - # Generate report - echo "" - echo "========================================" - echo "Results Summary" - echo "========================================" - column -s',' -t "$RESULTS_DIR/benchmark_${TIMESTAMP}.csv" - echo "" - echo "Full results saved to: $RESULTS_DIR/benchmark_${TIMESTAMP}.csv" - - # Generate markdown report - cat > "$RESULTS_DIR/BENCHMARK_REPORT_${TIMESTAMP}.md" << EOF -# RBE Benchmark Report - -**Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") -**Runner:** $(whoami)@$(hostname) - -## Results - -$(column -s',' -t "$RESULTS_DIR/benchmark_${TIMESTAMP}.csv") - -## Methodology - -- Container started with default configuration -- 5-second warmup period -- 5 samples collected at 1-second intervals -- Average calculated from samples -- Container removed after measurement - -## Environment - -- OS: $(uname -s -r) -- Docker: $(docker --version) -- Architecture: $(uname -m) -EOF - - log_success "Report saved to: $RESULTS_DIR/BENCHMARK_REPORT_${TIMESTAMP}.md" -} - -main "$@" diff --git a/benchmark/scripts/benchmark_lib.py b/benchmark/scripts/benchmark_lib.py new file mode 100644 index 0000000..4b0307e --- /dev/null +++ b/benchmark/scripts/benchmark_lib.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Shared utilities for the FerrisRBE Python benchmark suite. + +These helpers are used by the individual benchmark scripts so that common +behaviour (building a simple REAPI action, computing latency percentiles, +configuring gRPC channels, etc.) lives in one place. +""" + +import hashlib +import json +import statistics +from typing import Any, Dict, List, Optional + +import grpc + +from build.bazel.remote.execution.v2 import remote_execution_pb2 + + +def percentile(data: List[float], p: float) -> float: + """Return the p-th percentile of a list of numbers.""" + if not data: + return 0.0 + sorted_data = sorted(data) + index = int(len(sorted_data) * p / 100) + return sorted_data[min(index, len(sorted_data) - 1)] + + +def make_channel( + server: str, + options: Optional[List[tuple]] = None, +) -> grpc.Channel: + """Create an insecure gRPC channel with sensible benchmark defaults.""" + if options is None: + options = [ + ("grpc.keepalive_time_ms", 30000), + ("grpc.keepalive_timeout_ms", 10000), + ("grpc.http2.max_pings_without_data", 0), + ("grpc.http2.min_time_between_pings_ms", 30000), + ] + return grpc.insecure_channel(server, options=options) + + +def create_simple_action( + command: List[str], + output_paths: Optional[List[str]] = None, +) -> tuple: + """Create a minimal REAPI Action with its dependencies. + + Returns a tuple with: + (action_digest, action_bytes, + command_digest, command_bytes, + input_root_digest, input_root_bytes) + """ + if output_paths is None: + output_paths = ["output.txt"] + + command_proto = remote_execution_pb2.Command( + arguments=command, + output_paths=output_paths, + ) + command_bytes = command_proto.SerializeToString() + command_digest = remote_execution_pb2.Digest( + hash=hashlib.sha256(command_bytes).hexdigest(), + size_bytes=len(command_bytes), + ) + + # REAPI requires an input_root_digest even for actions without inputs. + input_root_proto = remote_execution_pb2.Directory() + input_root_bytes = input_root_proto.SerializeToString() + input_root_digest = remote_execution_pb2.Digest( + hash=hashlib.sha256(input_root_bytes).hexdigest(), + size_bytes=len(input_root_bytes), + ) + + action_proto = remote_execution_pb2.Action( + command_digest=command_digest, + input_root_digest=input_root_digest, + do_not_cache=False, + ) + action_bytes = action_proto.SerializeToString() + action_digest = remote_execution_pb2.Digest( + hash=hashlib.sha256(action_bytes).hexdigest(), + size_bytes=len(action_bytes), + ) + + return ( + action_digest, + action_bytes, + command_digest, + command_bytes, + input_root_digest, + input_root_bytes, + ) + + +def save_json(path: str, data: Dict[str, Any]) -> None: + """Write a JSON object to disk in a deterministic, readable format.""" + with open(path, "w") as f: + json.dump(data, f, indent=2) + f.write("\n") diff --git a/benchmark/scripts/build-with-bazel.sh b/benchmark/scripts/build-with-bazel.sh deleted file mode 100755 index d8d4bbd..0000000 --- a/benchmark/scripts/build-with-bazel.sh +++ /dev/null @@ -1,164 +0,0 @@ -#!/bin/bash -# Build FerrisRBE using Bazel (dogfooding) -# This script builds the RBE server using Bazel, consistent with the project's build system -# Handles custom symlink prefixes (--symlink_prefix=/) - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" - -cd "$PROJECT_ROOT" - -# Source Bazel utilities -source "$SCRIPT_DIR/bazel-utils.sh" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } - -# Check Bazel installation -check_bazel_installed() { - if ! command -v bazel &> /dev/null; then - log_error "Bazel not found. Please install Bazelisk:" - echo " npm install -g @bazel/bazelisk" - echo " or" - echo " brew install bazelisk" - exit 1 - fi - - local bazel_version=$(bazel --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') - log_info "Using Bazel version: $bazel_version" - - # Show bazel-bin location (handles custom symlink_prefix) - local bazel_bin=$(get_bazel_bin) - log_info "Bazel output directory: $bazel_bin" -} - -# Build server binary -build_server() { - log_info "Building rbe-server with Bazel..." - - # Build - bazel build //:rbe-server --config=release 2>/dev/null || bazel build //:rbe-server - - # Find output (handles custom symlink prefix) - local output_path=$(find_bazel_output "//:rbe-server") - - if [ -z "$output_path" ] || [ ! -f "$output_path" ]; then - log_error "Could not find rbe-server output" - log_info "Tried: $(get_bazel_bin)/rbe-server" - exit 1 - fi - - log_success "Server binary built: $output_path" - echo "$output_path" > "$PROJECT_ROOT/.bazel-output-server" -} - -# Build OCI image -build_image() { - log_info "Building OCI image with Bazel (rules_oci)..." - - # Build the image - bazel build //oci:server_image - - # Load into Docker for benchmarking - bazel run //oci:server_load - - # Tag for easier reference - docker tag bazel/oci:server_image ferrisrbe-server:latest 2>/dev/null || true - - log_success "OCI image built and loaded: ferrisrbe-server:latest" -} - -# Build worker binary -build_worker() { - log_info "Building rbe-worker with Bazel..." - - bazel build //:rbe-worker --config=release 2>/dev/null || bazel build //:rbe-worker - - local output_path=$(find_bazel_output "//:rbe-worker") - - if [ -z "$output_path" ] || [ ! -f "$output_path" ]; then - log_error "Could not find rbe-worker output" - exit 1 - fi - - log_success "Worker binary built: $output_path" - echo "$output_path" > "$PROJECT_ROOT/.bazel-output-worker" -} - -# Run a quick smoke test -smoke_test() { - log_info "Running smoke test..." - - local server_path=$(find_bazel_output "//:rbe-server") - - if [ -z "$server_path" ] || [ ! -f "$server_path" ]; then - log_error "Server binary not found" - exit 1 - fi - - # Quick version check - "$server_path" --version 2>/dev/null || true - - log_success "Smoke test passed" -} - -# Main execution -main() { - local target="${1:-all}" - - log_info "Building FerrisRBE with Bazel" - log_info "Project root: $PROJECT_ROOT" - - check_bazel_installed - - case "$target" in - server) - build_server - ;; - worker) - build_worker - ;; - image) - build_image - ;; - all) - build_server - build_worker - build_image - smoke_test - ;; - *) - echo "Usage: $0 [server|worker|image|all]" - echo "" - echo "Targets:" - echo " server - Build rbe-server binary" - echo " worker - Build rbe-worker binary" - echo " image - Build and load OCI image" - echo " all - Build everything (default)" - echo "" - echo "Note: Handles custom --symlink_prefix automatically" - exit 1 - ;; - esac - - log_success "Build complete!" - - # Print output locations - log_info "Output locations (symlink_prefix independent):" - local bazel_bin=$(get_bazel_bin) - echo " Bazel bin: $bazel_bin" - echo " Use: bazel info bazel-bin to get this path" -} - -main "$@" diff --git a/benchmark/scripts/cache-stampede-test.py b/benchmark/scripts/cache-stampede-test.py index 6d9c6e9..acf6d03 100755 --- a/benchmark/scripts/cache-stampede-test.py +++ b/benchmark/scripts/cache-stampede-test.py @@ -9,25 +9,17 @@ import argparse import hashlib -import os -import sys import time import statistics from dataclasses import dataclass, field -from typing import List, Optional, Dict +from typing import Dict, List, Optional from concurrent.futures import ThreadPoolExecutor, as_completed import threading import grpc -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'proto_gen')) - -try: - from build.bazel.remote.execution.v2 import remote_execution_pb2 - from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc -except ImportError: - print("Warning: Protocol buffer modules not found.") - sys.exit(1) +from build.bazel.remote.execution.v2 import remote_execution_pb2 +from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc @dataclass diff --git a/benchmark/scripts/cas-load-test.py b/benchmark/scripts/cas-load-test.py index 6b57643..f0bdc4a 100755 --- a/benchmark/scripts/cas-load-test.py +++ b/benchmark/scripts/cas-load-test.py @@ -12,7 +12,6 @@ import os import random import string -import sys import time import statistics from concurrent.futures import ThreadPoolExecutor, as_completed @@ -21,16 +20,8 @@ import grpc -# Add generated proto path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'proto_gen')) - -try: - from build.bazel.remote.execution.v2 import remote_execution_pb2 - from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc -except ImportError: - print("Warning: Protocol buffer modules not found. Using mock implementations.") - print("Install with: pip install grpcio grpcio-tools") - sys.exit(1) +from build.bazel.remote.execution.v2 import remote_execution_pb2 +from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc @dataclass diff --git a/benchmark/scripts/cold-start-test.sh b/benchmark/scripts/cold-start-test.sh deleted file mode 100755 index b263dd3..0000000 --- a/benchmark/scripts/cold-start-test.sh +++ /dev/null @@ -1,195 +0,0 @@ -#!/bin/bash -# Cold Start Test for RBE Servers -# Measures time from container start to first successful gRPC response - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -RESULTS_DIR="${SCRIPT_DIR}/../results" -TIMESTAMP=$(date +%Y%m%d_%H%M%S) - -mkdir -p "$RESULTS_DIR" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } - -# Test cold start for a solution -test_cold_start() { - local name="$1" - local image="$2" - local port="$3" - local env_vars="${4:-}" - local timeout_secs="${5:-60}" - - log_info "Testing cold start for $name..." - - local container_name="coldstart-$(echo "$name" | tr '[:upper:]' '[:lower:]')-${TIMESTAMP}" - - # Record start time - local start_time=$(date +%s%N) - - # Start container - local run_cmd="docker run -d --name $container_name -p $port:$port" - if [ -n "$env_vars" ]; then - run_cmd="$run_cmd $env_vars" - fi - run_cmd="$run_cmd $image" - - if ! eval "$run_cmd" 2>/dev/null; then - log_error "Failed to start $name container" - echo "$name,N/A,failed" >> "$RESULTS_DIR/coldstart_${TIMESTAMP}.csv" - return 1 - fi - - # Wait for port to be listening - local port_ready=false - local port_attempts=0 - local max_port_attempts=$timeout_secs - - while [ $port_attempts -lt $max_port_attempts ]; do - if nc -z localhost $port 2>/dev/null; then - port_ready=true - break - fi - sleep 0.1 - port_attempts=$((port_attempts + 1)) - done - - if [ "$port_ready" = false ]; then - log_error "Port $port never became ready" - docker rm -f "$container_name" >/dev/null 2>&1 - echo "$name,N/A,timeout" >> "$RESULTS_DIR/coldstart_${TIMESTAMP}.csv" - return 1 - fi - - local port_ready_time=$(date +%s%N) - - # Wait for first successful gRPC response - local grpc_ready=false - local grpc_attempts=0 - local max_grpc_attempts=30 # 3 seconds max - - while [ $grpc_attempts -lt $max_grpc_attempts ]; do - if grpcurl -plaintext -max-time 1 "localhost:$port" \ - build.bazel.remote.execution.v2.Capabilities/GetCapabilities 2>/dev/null; then - grpc_ready=true - break - fi - sleep 0.1 - grpc_attempts=$((grpc_attempts + 1)) - done - - local end_time=$(date +%s%N) - - # Calculate times in milliseconds - local total_ms=$(( (end_time - start_time) / 1000000 )) - local port_ms=$(( (port_ready_time - start_time) / 1000000 )) - local grpc_ms=$(( (end_time - port_ready_time) / 1000000 )) - - # Cleanup - docker rm -f "$container_name" >/dev/null 2>&1 - - if [ "$grpc_ready" = true ]; then - log_success "$name: ${total_ms}ms total (port: ${port_ms}ms, grpc: ${grpc_ms}ms)" - echo "$name,$total_ms,$port_ms,$grpc_ms,success" >> "$RESULTS_DIR/coldstart_${TIMESTAMP}.csv" - else - log_warn "$name: Port ready in ${port_ms}ms but gRPC failed" - echo "$name,$port_ms,$port_ms,N/A,grpc_failed" >> "$RESULTS_DIR/coldstart_${TIMESTAMP}.csv" - fi -} - -# Main test -main() { - echo "========================================" - echo "RBE Cold Start Test" - echo "Timestamp: $TIMESTAMP" - echo "========================================" - echo "" - - # Initialize CSV - echo "solution,total_ms,port_ready_ms,grpc_ready_ms,status" > "$RESULTS_DIR/coldstart_${TIMESTAMP}.csv" - - # Check if FerrisRBE image exists - if docker image inspect ferrisrbe-server:latest >/dev/null 2>&1; then - test_cold_start \ - "FerrisRBE" \ - "ferrisrbe-server:latest" \ - 9092 \ - "-e RBE_PORT=9092 -e RBE_BIND_ADDRESS=0.0.0.0" - else - log_warn "FerrisRBE image not found. Build it first with Bazel:" - echo " bazel build //oci:server_image" - echo " bazel run //oci:server_load" - fi - - # Test other solutions if available - if docker image inspect bazelbuild/buildfarm-server:latest >/dev/null 2>&1; then - echo "" - test_cold_start \ - "Buildfarm" \ - "bazelbuild/buildfarm-server:latest" \ - 9092 \ - "-e JAVA_OPTS=-Xmx2g -Xms1g" - fi - - # Generate report - echo "" - echo "========================================" - echo "Cold Start Results" - echo "========================================" - column -s',' -t "$RESULTS_DIR/coldstart_${TIMESTAMP}.csv" - echo "" - echo "Full results saved to: $RESULTS_DIR/coldstart_${TIMESTAMP}.csv" - - # Generate markdown report - cat > "$RESULTS_DIR/COLDSTART_REPORT_${TIMESTAMP}.md" << EOF -# RBE Cold Start Test Report - -**Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") -**Runner:** $(whoami)@$(hostname) - -## Results - -$(column -s',' -t "$RESULTS_DIR/coldstart_${TIMESTAMP}.csv") - -## Interpretation - -- **Total Time**: Time from 'docker run' to first successful gRPC response -- **Port Ready**: Time until TCP port is listening -- **gRPC Ready**: Additional time until first gRPC request succeeds - -### Expected Results - -| Solution | Typical Cold Start | Notes | -|----------|-------------------|-------| -| FerrisRBE | < 100ms | Rust native binary, distroless | -| Buildfarm | 5-15s | JVM startup + JIT warmup | -| Buildbarn | 1-3s | Go binary | -| BuildBuddy | 10-30s | JVM + PostgreSQL + Redis | - -Fast cold start is critical for: -- Kubernetes HPA (Horizontal Pod Autoscaler) responsiveness -- Serverless deployments -- Development/testing environments - -## Methodology - -1. Start container with 'docker run' -2. Poll TCP port until listening -3. Send gRPC Capabilities request -4. Measure total time -EOF - - log_success "Report saved to: $RESULTS_DIR/COLDSTART_REPORT_${TIMESTAMP}.md" -} - -main "$@" diff --git a/benchmark/scripts/compare-branches.sh b/benchmark/scripts/compare-branches.sh deleted file mode 100755 index 8bf9fc7..0000000 --- a/benchmark/scripts/compare-branches.sh +++ /dev/null @@ -1,403 +0,0 @@ -#!/bin/bash -# Compare benchmark results between PR (local build) and Official Release (latest tag) -# -# This script compares the current PR's OCI image against the official Docker Hub -# image (xangcastle/ferris-server:latest), providing a realistic comparison of -# what users will experience when upgrading. -# -# Benefits over compiling main: -# - Faster (pull vs build): ~30 seconds vs ~5-10 minutes -# - Tests the actual release artifact (same as production) -# - Detects issues in the OCI build process -# - More realistic user experience comparison -# -# Usage: ./compare-branches.sh [pr_image_tag] -# pr_image_tag: Tag for PR image (default: ferrisrbe/server:latest) - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BENCHMARK_DIR="$(dirname "$SCRIPT_DIR")" -RESULTS_DIR="$BENCHMARK_DIR/results" - -# Image tags -PR_IMAGE_TAG="${1:-ferrisrbe/server:latest}" -OFFICIAL_IMAGE_TAG="xangcastle/ferris-server:latest" - -# Container names -TIMESTAMP=$(date +%Y%m%d_%H%M%S) -OFFICIAL_CONTAINER="ferrisrbe-official-${TIMESTAMP}" -PR_CONTAINER="ferrisrbe-pr-${TIMESTAMP}" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -CYAN='\033[0;36m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } -log_section() { echo -e "${CYAN}$1${NC}"; } - -mkdir -p "$RESULTS_DIR" - -cleanup() { - log_info "Cleaning up comparison containers..." - - # Stop and remove containers - docker stop "$OFFICIAL_CONTAINER" "$PR_CONTAINER" >/dev/null 2>&1 || true - docker rm "$OFFICIAL_CONTAINER" "$PR_CONTAINER" >/dev/null 2>&1 || true - - log_success "Cleanup complete" -} - -trap cleanup EXIT - -# Wait for rbe-cache if using GitHub Actions services -wait_for_services() { - if [ -n "$BENCHMARK_SERVICES" ]; then - log_info "Waiting for rbe-cache service..." - for i in {1..60}; do - if nc -z localhost 9094 2>/dev/null; then - log_success "rbe-cache is ready on port 9094" - break - fi - if [ $((i % 10)) -eq 0 ]; then - log_info " Waiting... (attempt $i/60)" - fi - sleep 2 - done - fi - - # Ensure CAS_ENDPOINT is set - if [ -z "$CAS_ENDPOINT" ]; then - export CAS_ENDPOINT="localhost:9094" - fi - - log_info "Using CAS_ENDPOINT: $CAS_ENDPOINT" -} - -# Pull official image from Docker Hub -pull_official_image() { - log_section "=== Pulling Official Image ===" - log_info "Pulling: $OFFICIAL_IMAGE_TAG" - - if ! docker pull "$OFFICIAL_IMAGE_TAG" 2>&1; then - log_error "Failed to pull official image from Docker Hub" - log_info "This could mean:" - log_info " - No internet connectivity" - log_info " - Docker Hub rate limiting" - log_info " - Image doesn't exist (first release?)" - exit 1 - fi - - # Get image info - local image_id=$(docker images --format "{{.ID}}" "$OFFICIAL_IMAGE_TAG" | head -1) - local image_created=$(docker images --format "{{.CreatedAt}}" "$OFFICIAL_IMAGE_TAG" | head -1) - local image_size=$(docker images --format "{{.Size}}" "$OFFICIAL_IMAGE_TAG" | head -1) - - log_success "Official image pulled successfully" - log_info " Image ID: $image_id" - log_info " Created: $image_created" - log_info " Size: $image_size" - - # Save info for report - echo "$image_id" > "$RESULTS_DIR/official_image_id.txt" - echo "$image_created" > "$RESULTS_DIR/official_image_created.txt" - echo "$image_size" > "$RESULTS_DIR/official_image_size.txt" -} - -# Verify PR image exists -verify_pr_image() { - log_section "=== Verifying PR Image ===" - log_info "Checking: $PR_IMAGE_TAG" - - if ! docker image inspect "$PR_IMAGE_TAG" >/dev/null 2>&1; then - log_error "PR image $PR_IMAGE_TAG not found in Docker" - log_info "Available ferrisrbe images:" - docker images | grep ferrisrbe || true - log_info "" - log_info "Build the PR image with:" - log_info " bazel run //oci:server_load_amd64" - exit 1 - fi - - # Get image info - local image_id=$(docker images --format "{{.ID}}" "$PR_IMAGE_TAG" | head -1) - local image_created=$(docker images --format "{{.CreatedAt}}" "$PR_IMAGE_TAG" | head -1) - local image_size=$(docker images --format "{{.Size}}" "$PR_IMAGE_TAG" | head -1) - - log_success "PR image verified" - log_info " Image ID: $image_id" - log_info " Created: $image_created" - log_info " Size: $image_size" - - # Save info for report - echo "$image_id" > "$RESULTS_DIR/pr_image_id.txt" - echo "$image_created" > "$RESULTS_DIR/pr_image_created.txt" - echo "$image_size" > "$RESULTS_DIR/pr_image_size.txt" -} - -# Function to benchmark a container image -benchmark_image() { - local image_tag="$1" - local name="$2" - local container_name="$3" - local output_dir="$RESULTS_DIR/${name}" - - mkdir -p "$output_dir" - - log_section "=== Benchmarking: $name ===" - log_info "Image: $image_tag" - log_info "Container: $container_name" - - # Start container - log_info "Starting container..." - docker run -d \ - --name "$container_name" \ - --network host \ - -p 9092:9092 \ - -e RBE_PORT=9092 \ - -e RBE_BIND_ADDRESS=0.0.0.0 \ - -e RUST_LOG=info \ - -e CAS_ENDPOINT="$CAS_ENDPOINT" \ - -e RBE_L1_CACHE_CAPACITY=100000 \ - -e RBE_L1_CACHE_TTL_SECS=3600 \ - --memory=512m \ - "$image_tag" >/dev/null 2>&1 - - # Wait for server to be ready - log_info "Waiting for server to be ready..." - local ready=false - for i in {1..60}; do - if nc -z localhost 9092 2>/dev/null; then - ready=true - break - fi - if [ $((i % 10)) -eq 0 ]; then - log_info " Waiting... (attempt $i/60)" - docker logs "$container_name" --tail 3 2>/dev/null || true - fi - sleep 1 - done - - if [ "$ready" != "true" ]; then - log_error "Server failed to start within 60 seconds" - docker logs "$container_name" 2>/dev/null || true - docker stop "$container_name" >/dev/null 2>&1 || true - docker rm "$container_name" >/dev/null 2>&1 || true - return 1 - fi - - log_success "Server ready" - - # Wait for stabilization - sleep 2 - - # Get memory baseline (multiple samples) - log_info "Sampling memory usage..." - for i in {1..5}; do - docker stats "$container_name" --no-stream --format "{{.MemUsage}}" 2>/dev/null | \ - awk '{print $1}' | sed 's/MiB//' | sed 's/GiB/*1024/' | bc 2>/dev/null || echo "0" - sleep 1 - done > "$output_dir/memory_samples.txt" - - # Use first non-zero value - MEMORY=$(grep -v "^0$" "$output_dir/memory_samples.txt" | head -1) - [ -z "$MEMORY" ] && MEMORY="0" - - echo "$MEMORY" > "$output_dir/memory.txt" - log_info "Memory: ${MEMORY}MB" - - # Quick throughput test - log_info "Running throughput test..." - python3 "$SCRIPT_DIR/execution-load-test.py" \ - --server localhost:9092 \ - --actions 50 \ - --concurrent 10 \ - --output "$output_dir/execution.json" 2>/dev/null || true - - # Get container stats - docker stats "$container_name" --no-stream --format "table {{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" > "$output_dir/container_stats.txt" 2>/dev/null || true - - # Stop container - log_info "Stopping container..." - docker stop "$container_name" >/dev/null 2>&1 || true - docker rm "$container_name" >/dev/null 2>&1 || true - - sleep 2 - - log_success "Benchmark for $name complete" -} - -# Generate comparison report -generate_comparison_report() { - log_section "=== Generating Comparison Report ===" - - COMPARISON_FILE="$RESULTS_DIR/comparison.md" - - # Read results - local official_mem=$(cat "$RESULTS_DIR/official/memory.txt" 2>/dev/null || echo "N/A") - local pr_mem=$(cat "$RESULTS_DIR/pr/memory.txt" 2>/dev/null || echo "N/A") - local official_size=$(cat "$RESULTS_DIR/official_image_size.txt" 2>/dev/null || echo "N/A") - local pr_size=$(cat "$RESULTS_DIR/pr_image_size.txt" 2>/dev/null || echo "N/A") - local official_id=$(cat "$RESULTS_DIR/official_image_id.txt" 2>/dev/null || echo "N/A") - local pr_id=$(cat "$RESULTS_DIR/pr_image_id.txt" 2>/dev/null || echo "N/A") - - cat > "$COMPARISON_FILE" << EOF -## 📊 Performance Comparison: PR vs Official Release - -### Container Images - -| Attribute | Official (latest) | PR | Notes | -|-----------|-------------------|-----|-------| -| **Image** | \`$OFFICIAL_IMAGE_TAG\` | \`$PR_IMAGE_TAG\` | - | -| **Image ID** | \`${official_id:0:12}\` | \`${pr_id:0:12}\` | Different = new build | -| **Size** | $official_size | $pr_size | Smaller is better | - -### Performance Metrics - -| Metric | Official (latest) | PR | Change | Status | -|--------|-------------------|-----|--------|--------| -EOF - - # Compare memory - if [ "$official_mem" != "N/A" ] && [ "$pr_mem" != "N/A" ] && \ - [ "$official_mem" != "0" ] && [ "$pr_mem" != "0" ]; then - - # Calculate change - CHANGE=$(echo "scale=2; (($pr_mem - $official_mem) / $official_mem) * 100" | bc) - - # Determine status - if (( $(echo "$CHANGE <= 5" | bc -l) )); then - STATUS="✅" - elif (( $(echo "$CHANGE <= 15" | bc -l) )); then - STATUS="⚠️" - else - STATUS="🚨" - fi - - ARROW=$(echo "$CHANGE" | awk '{if ($1 < 0) print "↓"; else if ($1 > 0) print "↑"; else print "="}') - - echo "| Memory (MB) | ${official_mem} | ${pr_mem} | ${ARROW} ${CHANGE}% | ${STATUS} |" >> "$COMPARISON_FILE" - else - echo "| Memory (MB) | ${official_mem} | ${pr_mem} | N/A | ⚠️ |" >> "$COMPARISON_FILE" - fi - - cat >> "$COMPARISON_FILE" << EOF - -#### Legend -- ✅ Within 5% - Acceptable -- ⚠️ 5-15% change - Review recommended -- 🚨 >15% regression - Optimization required - -#### Interpretation -EOF - - # Add interpretation - if [ "$pr_mem" != "N/A" ] && [ "$pr_mem" != "0" ]; then - if (( $(echo "$pr_mem > 20" | bc -l) )); then - echo "- 🚨 **Memory regression detected**: PR uses ${pr_mem}MB vs expected <20MB" >> "$COMPARISON_FILE" - else - echo "- ✅ **Memory usage acceptable**: ${pr_mem}MB within expected range" >> "$COMPARISON_FILE" - fi - fi - - # Image size comparison - if [ "$official_size" != "N/A" ] && [ "$pr_size" != "N/A" ]; then - echo "" >> "$COMPARISON_FILE" - echo "#### Image Size Analysis" >> "$COMPARISON_FILE" - - # Extract numeric values for comparison (rough approximation) - local official_size_mb=$(echo "$official_size" | sed 's/MB//' | sed 's/GB/*1024/' | bc 2>/dev/null || echo "0") - local pr_size_mb=$(echo "$pr_size" | sed 's/MB//' | sed 's/GB/*1024/' | bc 2>/dev/null || echo "0") - - if [ "$pr_size_mb" != "0" ] && [ "$official_size_mb" != "0" ]; then - if (( $(echo "$pr_size_mb > $official_size_mb" | bc -l) )); then - echo "- ⚠️ PR image is larger than official (${pr_size} vs ${official_size})" >> "$COMPARISON_FILE" - elif (( $(echo "$pr_size_mb < $official_size_mb" | bc -l) )); then - echo "- ✅ PR image is smaller than official (${pr_size} vs ${official_size}) - Good optimization!" >> "$COMPARISON_FILE" - else - echo "- ✅ Image size unchanged (${pr_size})" >> "$COMPARISON_FILE" - fi - fi - fi - - cat >> "$COMPARISON_FILE" << EOF - ---- -*Comparison generated at: $(date -u +"%Y-%m-%d %H:%M:%S UTC")* -EOF - - log_success "Comparison report generated: $COMPARISON_FILE" - - # Display the comparison - echo "" - cat "$COMPARISON_FILE" -} - -# Print summary -print_summary() { - log_section "========================================" - log_section " Comparison Complete!" - log_section "========================================" - log_info "Official Image: $OFFICIAL_IMAGE_TAG" - log_info "PR Image: $PR_IMAGE_TAG" - log_info "Results: $RESULTS_DIR" - echo "" - log_info "Key files:" - log_info " - $RESULTS_DIR/comparison.md (main report)" - log_info " - $RESULTS_DIR/official/ (official image results)" - log_info " - $RESULTS_DIR/pr/ (PR image results)" -} - -# Main execution -main() { - log_section "========================================" - log_section "Container Image Comparison Benchmark" - log_section "========================================" - log_info "Comparing PR against official Docker Hub release" - echo "" - - # Check Docker is available - if ! command -v docker &> /dev/null; then - log_error "Docker is not installed or not in PATH" - exit 1 - fi - - # Wait for services - wait_for_services - - # Pull official image - pull_official_image - - # Verify PR image - verify_pr_image - - echo "" - log_section "========================================" - - # Benchmark official image first - benchmark_image "$OFFICIAL_IMAGE_TAG" "official" "$OFFICIAL_CONTAINER" - - echo "" - log_section "========================================" - - # Benchmark PR image - benchmark_image "$PR_IMAGE_TAG" "pr" "$PR_CONTAINER" - - echo "" - log_section "========================================" - - # Generate comparison - generate_comparison_report - - # Print summary - print_summary -} - -main "$@" diff --git a/benchmark/scripts/connection-churn-test.py b/benchmark/scripts/connection-churn-test.py index e6f6ead..32ac8b1 100755 --- a/benchmark/scripts/connection-churn-test.py +++ b/benchmark/scripts/connection-churn-test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ Connection Churn Test - Abrupt disconnections and resource cleanup -Tests handling of sudden connection drops during gRPC operations +Tests handling of sudden connection drops during gRPC operations. FerrisRBE Advantage: Tokio's native task cancellation releases resources immediately. JVM apps may leave zombie threads or persistent connections causing memory leaks. @@ -9,30 +9,29 @@ import argparse import hashlib +import json import os -import sys -import time -import statistics -import socket import random -from dataclasses import dataclass, field -from typing import List, Optional -from concurrent.futures import ThreadPoolExecutor, as_completed +import statistics import threading -import signal +import time +from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import dataclass, field +from typing import List, Optional, Tuple import grpc -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'proto_gen')) +from build.bazel.remote.execution.v2 import remote_execution_pb2 +from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc +from google.bytestream import bytestream_pb2 +from google.bytestream import bytestream_pb2_grpc + +from benchmark.scripts import benchmark_lib -try: - from build.bazel.remote.execution.v2 import remote_execution_pb2 - from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc - from google.bytestream import bytestream_pb2 - from google.bytestream import bytestream_pb2_grpc -except ImportError: - print("Warning: Protocol buffer modules not found.") - sys.exit(1) + +# Shared lock and state used to seed CAS with a valid blob/action once. +_seed_lock = threading.Lock() +_seed_state = {"blob_digest": None, "action_digest": None} @dataclass @@ -41,7 +40,7 @@ class ChurnResult: connection_id: str operation: str duration_ms: float - disconnected_at: Optional[float] = None + disconnected: bool = False resources_cleaned: bool = False error: Optional[str] = None @@ -53,309 +52,395 @@ class ChurnSummary: total_connections: int disconnect_rate: float results: List[ChurnResult] = field(default_factory=list) - + + def get_normal_completions(self) -> List[ChurnResult]: + return [r for r in self.results if not r.disconnected] + + def get_disconnected(self) -> List[ChurnResult]: + return [r for r in self.results if r.disconnected] + def get_successful_cleanups(self) -> List[ChurnResult]: return [r for r in self.results if r.resources_cleaned] - + def get_failed_cleanups(self) -> List[ChurnResult]: - return [r for r in self.results if not r.resources_cleaned and r.error] - + return [r for r in self.results if not r.resources_cleaned] + def print_summary(self): + normal = self.get_normal_completions() + disconnected = self.get_disconnected() + successful = self.get_successful_cleanups() + failed = self.get_failed_cleanups() + + normal_ok = [r for r in normal if r.resources_cleaned] + disc_ok = [r for r in disconnected if r.resources_cleaned] + print("\n" + "=" * 70) print(f"CONNECTION CHURN TEST - {self.server}") print("=" * 70) print(f"\n📊 TEST PARAMETERS:") print(f" Total connections: {self.total_connections}") print(f" Disconnect rate: {self.disconnect_rate * 100:.1f}%") - - successful = len(self.get_successful_cleanups()) - failed = len(self.get_failed_cleanups()) - total = len(self.results) - - print(f"\n📊 RESULTS:") - print(f" Successful cleanups: {successful}/{total} ({successful/total*100:.1f}%)") - print(f" Failed cleanups: {failed}/{total} ({failed/total*100:.1f}%)") - + + print(f"\n📊 NORMAL COMPLETIONS: {len(normal_ok)}/{len(normal)} passed") + print(f"📊 ABRUPT DISCONNECTS: {len(disc_ok)}/{len(disconnected)} cleaned") + print(f"📊 OVERALL: {len(successful)}/{len(self.results)} ({len(successful)/len(self.results)*100:.1f}%) successful") + if self.results: durations = [r.duration_ms for r in self.results if r.duration_ms > 0] if durations: - print(f" Avg cleanup time: {statistics.mean(durations):.1f}ms") - print(f" Max cleanup time: {max(durations):.1f}ms") - - print(f"\n🏆 RESOURCE CLEANUP VERDICT:") - if failed == 0: - print(f" ✅ EXCELLENT: All resources cleaned immediately") - print(f" (No memory leaks, no zombie connections)") - elif failed / total < 0.05: - print(f" ✅ GOOD: Minimal resource leakage (<5%)") - elif failed / total < 0.15: - print(f" ⚠️ WARNING: Some resource leakage detected") + print(f"\n⏱️ CLEANUP TIME:") + print(f" Avg: {statistics.mean(durations):.1f}ms") + print(f" Max: {max(durations):.1f}ms") + + print(f"\n🏆 VERDICT:") + if failed: + print(f" ⚠️ {len(failed)} connections reported unexpected errors") + else: + print(f" ✅ All connections closed cleanly without unexpected errors") + + if len(disc_ok) == len(disconnected): + print(f" ✅ All abrupt disconnections were handled without hangs") else: - print(f" ❌ CRITICAL: Significant resource leakage") - print(f" (Potential memory leak, zombie threads)") - + print(f" ⚠️ {len(disconnected) - len(disc_ok)} abrupt disconnections had issues") + print("=" * 70) -def create_grpc_channel(server: str) -> grpc.Channel: - """Create a new gRPC channel""" - return grpc.insecure_channel(server) +def _upload_blob(server: str, data: bytes) -> remote_execution_pb2.Digest: + """Upload a blob via CAS BatchUpdateBlobs and return its digest.""" + blob_hash = hashlib.sha256(data).hexdigest() + digest = remote_execution_pb2.Digest(hash=blob_hash, size_bytes=len(data)) + channel = benchmark_lib.make_channel(server) + try: + stub = remote_execution_pb2_grpc.ContentAddressableStorageStub(channel) + request = remote_execution_pb2.BatchUpdateBlobsRequest( + requests=[ + remote_execution_pb2.BatchUpdateBlobsRequest.Request( + digest=digest, data=data + ) + ] + ) + response = stub.BatchUpdateBlobs(request) + for status in response.responses: + if status.status.code != 0: + raise RuntimeError(f"Failed to upload blob: {status.status.message}") + return digest + finally: + channel.close() + + +def _upload_action(server: str) -> remote_execution_pb2.Digest: + """Upload a minimal action and its dependencies to CAS, return action digest.""" + action_digest, action_bytes, command_digest, command_bytes, input_root_digest, input_root_bytes = benchmark_lib.create_simple_action( + command=["sh", "-c", "echo ok > output.txt"], + output_paths=["output.txt"], + ) + cas_channel = benchmark_lib.make_channel(server) + try: + cas_stub = remote_execution_pb2_grpc.ContentAddressableStorageStub(cas_channel) + request = remote_execution_pb2.BatchUpdateBlobsRequest( + requests=[ + remote_execution_pb2.BatchUpdateBlobsRequest.Request( + digest=digest, data=data + ) + for digest, data in [ + (action_digest, action_bytes), + (command_digest, command_bytes), + (input_root_digest, input_root_bytes), + ] + ] + ) + response = cas_stub.BatchUpdateBlobs(request) + for status in response.responses: + if status.status.code != 0: + raise RuntimeError(f"Failed to upload action blob: {status.status.message}") + finally: + cas_channel.close() -def abrupt_disconnect(channel: grpc.Channel): - """Close channel abruptly""" - channel.close() + return action_digest -def start_streaming_upload( +def _seed_cas(server: str) -> Tuple[remote_execution_pb2.Digest, remote_execution_pb2.Digest]: + """Ensure a valid blob and action exist in CAS for the churn tests.""" + with _seed_lock: + if _seed_state["blob_digest"] is None: + blob_data = os.urandom(1024 * 1024) # 1MB blob + _seed_state["blob_digest"] = _upload_blob(server, blob_data) + if _seed_state["action_digest"] is None: + _seed_state["action_digest"] = _upload_action(server) + return _seed_state["blob_digest"], _seed_state["action_digest"] + + +def _run_download( channel: grpc.Channel, - blob_size: int = 10 * 1024 * 1024 # 10MB default -) -> Tuple[bytestream_pb2_grpc.ByteStreamStub, iter]: - """Start a streaming upload and return the stub + request iterator""" - stub = bytestream_pb2_grpc.ByteStreamStub(channel) - - file_hash = hashlib.sha256(os.urandom(32)).hexdigest() - resource_name = f"uploads/{int(time.time())}/blobs/{file_hash}/{blob_size}" - - # Generate chunks - chunk_size = 1024 * 1024 # 1MB chunks - chunks = [] - remaining = blob_size - offset = 0 - - while remaining > 0: - data = os.urandom(min(chunk_size, remaining)) - chunks.append(bytestream_pb2.WriteRequest( - resource_name=resource_name if offset == 0 else "", - data=data, - write_offset=offset - )) - offset += len(data) - remaining -= len(data) - - def request_generator(): - for chunk in chunks: - yield chunk - time.sleep(0.01) # Small delay to simulate streaming - - return stub, request_generator() - - -def start_streaming_download( + blob_digest: remote_execution_pb2.Digest, + call_holder: dict, + outcome: dict, +): + """Run a download in a worker thread.""" + try: + stub = bytestream_pb2_grpc.ByteStreamStub(channel) + request = bytestream_pb2.ReadRequest( + resource_name=f"blobs/{blob_digest.hash}/{blob_digest.size_bytes}" + ) + call = stub.Read(request) + call_holder["call"] = call + consumed = 0 + for response in call: + consumed += len(response.data) + outcome["completed"] = consumed == blob_digest.size_bytes + except grpc.RpcError as e: + if e.code() in (grpc.StatusCode.CANCELLED, grpc.StatusCode.UNAVAILABLE): + outcome["cancelled"] = True + else: + outcome["error"] = f"{e.code()}: {e.details()}" + except Exception as e: + outcome["error"] = str(e) + + +def _run_execute( channel: grpc.Channel, - blob_hash: str = "test", - blob_size: int = 10 * 1024 * 1024 -) -> Tuple[bytestream_pb2_grpc.ByteStreamStub, bytestream_pb2.ReadRequest]: - """Start a streaming download""" - stub = bytestream_pb2_grpc.ByteStreamStub(channel) - request = bytestream_pb2.ReadRequest( - resource_name=f"blobs/{blob_hash}/{blob_size}" - ) - return stub, request + action_digest: remote_execution_pb2.Digest, + call_holder: dict, + outcome: dict, +): + """Run an execute in a worker thread.""" + try: + stub = remote_execution_pb2_grpc.ExecutionStub(channel) + request = remote_execution_pb2.ExecuteRequest( + action_digest=action_digest, + skip_cache_lookup=True, + ) + call = stub.Execute(request) + call_holder["call"] = call + # Read at least one message so the RPC is active on the wire. + for _ in call: + break + outcome["completed"] = True + except grpc.RpcError as e: + if e.code() in (grpc.StatusCode.CANCELLED, grpc.StatusCode.UNAVAILABLE): + outcome["cancelled"] = True + else: + outcome["error"] = f"{e.code()}: {e.details()}" + except Exception as e: + outcome["error"] = str(e) -def test_connection_churn( +def _test_connection( server: str, connection_id: str, operation: str, - disconnect_rate: float + disconnect_rate: float, + blob_digest: remote_execution_pb2.Digest, + action_digest: remote_execution_pb2.Digest, ) -> ChurnResult: - """Test a single connection with potential abrupt disconnection""" - + """Test a single connection with potential abrupt disconnection. + + A worker thread runs an active streaming RPC. The main thread either lets + it complete normally or abruptly cancels the call and closes the channel. + A clean disconnect means the worker observed CANCELLED/UNAVAILABLE and did + not report an unexpected error. + """ start_time = time.time() should_disconnect = random.random() < disconnect_rate - disconnect_time = random.uniform(0.1, 0.5) if should_disconnect else None - result = ChurnResult( connection_id=connection_id, operation=operation, - duration_ms=0 + duration_ms=0, + disconnected=should_disconnect, ) - + + channel = benchmark_lib.make_channel(server) + call_holder: dict = {} + outcome: dict = {} + + if operation == "download": + worker = threading.Thread( + target=_run_download, + args=(channel, blob_digest, call_holder, outcome), + ) + else: # execute + worker = threading.Thread( + target=_run_execute, + args=(channel, action_digest, call_holder, outcome), + ) + try: - # Create channel - channel = create_grpc_channel(server) - - if operation == 'upload': - stub, requests = start_streaming_upload(channel) - - if should_disconnect: - # Start upload in background - response_iter = stub.Write(requests) - - # Wait then disconnect abruptly - time.sleep(disconnect_time) - channel.close() - result.disconnected_at = disconnect_time - result.resources_cleaned = True # Tokio should clean up immediately - - else: - # Complete normally - for response in stub.Write(requests): + worker.start() + + if should_disconnect: + time.sleep(random.uniform(0.05, 0.25)) + call = call_holder.get("call") + if call is not None: + try: + call.cancel() + except Exception: pass + try: channel.close() - result.resources_cleaned = True - - elif operation == 'download': - stub, request = start_streaming_download(channel) - - if should_disconnect: - response_iter = stub.Read(request) - - # Consume some data then disconnect - consumed = 0 - for response in response_iter: - consumed += len(response.data) - if consumed > 1024 * 1024: # Disconnect after 1MB - break - - channel.close() - result.disconnected_at = time.time() - start_time - result.resources_cleaned = True - else: - for response in stub.Read(request): + except Exception: + pass + worker.join(timeout=5) + result.resources_cleaned = outcome.get("cancelled") is True or outcome.get("error") is None + else: + worker.join(timeout=30) + if worker.is_alive(): + call = call_holder.get("call") + if call is not None: + try: + call.cancel() + except Exception: + pass + try: + channel.close() + except Exception: pass - channel.close() - result.resources_cleaned = True - - elif operation == 'execute': - stub = remote_execution_pb2_grpc.ExecutionStub(channel) - - # Create a simple action - action_digest = remote_execution_pb2.Digest( - hash=hashlib.sha256(b"test").hexdigest(), - size_bytes=4 - ) - request = remote_execution_pb2.ExecuteRequest( - action_digest=action_digest - ) - - if should_disconnect: - response_iter = stub.Execute(request) - time.sleep(disconnect_time) - channel.close() - result.disconnected_at = disconnect_time - result.resources_cleaned = True + worker.join(timeout=5) + result.error = "Operation timed out" else: - for response in stub.Execute(request): - pass - channel.close() - result.resources_cleaned = True - + result.resources_cleaned = outcome.get("completed", False) + if not result.resources_cleaned and outcome.get("error"): + result.error = outcome["error"] + result.duration_ms = (time.time() - start_time) * 1000 - - except grpc.RpcError as e: - # Expected for disconnections - if e.code() in [grpc.StatusCode.CANCELLED, grpc.StatusCode.UNAVAILABLE]: - result.duration_ms = (time.time() - start_time) * 1000 - result.resources_cleaned = True # Cancelled = cleaned - else: - result.error = str(e) - result.duration_ms = (time.time() - start_time) * 1000 - + except Exception as e: result.error = str(e) result.duration_ms = (time.time() - start_time) * 1000 - + result.resources_cleaned = False + finally: + try: + channel.close() + except Exception: + pass + return result +def _canary_check(server: str) -> bool: + """Verify the server is still healthy after churn by running a simple action cache read.""" + try: + channel = benchmark_lib.make_channel(server) + stub = remote_execution_pb2_grpc.ActionCacheStub(channel) + stub.GetActionResult( + remote_execution_pb2.GetActionResultRequest( + action_digest=remote_execution_pb2.Digest(hash="0" * 64, size_bytes=0) + ), + timeout=5, + ) + except grpc.RpcError as e: + if e.code() == grpc.StatusCode.NOT_FOUND: + return True # Server is alive and responded. + return False + except Exception: + return False + finally: + channel.close() + return True + + def run_connection_churn_test( server: str, total_connections: int, disconnect_rate: float, - concurrent: int = 50 + concurrent: int = 50, ) -> ChurnSummary: - """Run connection churn test""" - + """Run connection churn test.""" print(f"Connecting to {server}...") - + + blob_digest, action_digest = _seed_cas(server) + print(f"Seeded CAS with blob ({blob_digest.size_bytes} bytes) and action") + summary = ChurnSummary( server=server, total_connections=total_connections, - disconnect_rate=disconnect_rate + disconnect_rate=disconnect_rate, ) - - operations = ['upload', 'download', 'execute'] - + + operations = ["download", "execute"] + print(f"\n🎯 Test Plan:") print(f" Create {total_connections} connections") print(f" Disconnect {disconnect_rate * 100:.0f}% of them abruptly") - print(f" Measure resource cleanup time") - + print(f" Measure whether channels close cleanly and server stays healthy") + print(f"\n🔥 Starting connection churn...") - + with ThreadPoolExecutor(max_workers=concurrent) as executor: futures = {} for i in range(total_connections): operation = operations[i % len(operations)] future = executor.submit( - test_connection_churn, + _test_connection, server, f"conn-{i}", operation, - disconnect_rate + disconnect_rate, + blob_digest, + action_digest, ) futures[future] = i - + completed = 0 for future in as_completed(futures): result = future.result() summary.results.append(result) completed += 1 - if completed % 100 == 0 or completed == total_connections: print(f" Progress: {completed}/{total_connections} connections tested") - + + canary_ok = _canary_check(server) + print(f"\n{'✅' if canary_ok else '❌'} Server canary check: {'passed' if canary_ok else 'failed'}") + return summary def main(): parser = argparse.ArgumentParser( - description='Connection Churn Test - Abrupt disconnections and resource cleanup' + description="Connection Churn Test - Abrupt disconnections and resource cleanup" + ) + parser.add_argument("--server", default="localhost:9092", help="gRPC server address") + parser.add_argument( + "--connections", type=int, default=1000, help="Total connections to test (default: 1000)" + ) + parser.add_argument( + "--disconnect-rate", type=float, default=0.3, + help="Rate of abrupt disconnections 0-1 (default: 0.3)" + ) + parser.add_argument( + "--concurrent", type=int, default=50, help="Concurrent connections (default: 50)" ) - parser.add_argument('--server', default='localhost:9092', help='gRPC server address') - parser.add_argument('--connections', type=int, default=1000, - help='Total connections to test (default: 1000)') - parser.add_argument('--disconnect-rate', type=float, default=0.3, - help='Rate of abrupt disconnections 0-1 (default: 0.3)') - parser.add_argument('--concurrent', type=int, default=50, - help='Concurrent connections (default: 50)') - parser.add_argument('--output', help='Output JSON file for results') - + parser.add_argument("--output", help="Output JSON file for results") + args = parser.parse_args() - + print("=" * 70) print("CONNECTION CHURN TEST") print("Testing resource cleanup after abrupt disconnections") print("=" * 70) - - # Run test + summary = run_connection_churn_test( server=args.server, total_connections=args.connections, disconnect_rate=args.disconnect_rate, - concurrent=args.concurrent + concurrent=args.concurrent, ) - - # Print summary + summary.print_summary() - - # Export if requested + if args.output: - import json result_dict = { - 'server': summary.server, - 'total_connections': summary.total_connections, - 'disconnect_rate': summary.disconnect_rate, - 'successful_cleanups': len(summary.get_successful_cleanups()), - 'failed_cleanups': len(summary.get_failed_cleanups()), - 'cleanup_times_ms': [r.duration_ms for r in summary.results if r.duration_ms > 0] + "server": summary.server, + "total_connections": summary.total_connections, + "disconnect_rate": summary.disconnect_rate, + "successful_cleanups": len(summary.get_successful_cleanups()), + "failed_cleanups": len(summary.get_failed_cleanups()), + "cleanup_times_ms": [r.duration_ms for r in summary.results if r.duration_ms > 0], } - with open(args.output, 'w') as f: - json.dump(result_dict, f, indent=2) + benchmark_lib.save_json(args.output, result_dict) print(f"\nResults exported to: {args.output}") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/benchmark/scripts/execution-load-test.py b/benchmark/scripts/execution-load-test.py index 07c5875..b5a3cf8 100755 --- a/benchmark/scripts/execution-load-test.py +++ b/benchmark/scripts/execution-load-test.py @@ -8,31 +8,19 @@ """ import argparse -import asyncio -import hashlib -import os -import sys -import time import statistics -from dataclasses import dataclass, field -from typing import List, Optional, Dict +import time from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import dataclass, field +from typing import Dict, List, Optional import grpc -# Generated proto imports -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'proto_gen')) +from build.bazel.remote.execution.v2 import remote_execution_pb2 +from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc +from google.longrunning import operations_pb2_grpc -try: - from build.bazel.remote.execution.v2 import remote_execution_pb2 - from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc - from google.longrunning import operations_pb2 - from google.longrunning import operations_pb2_grpc -except ImportError: - print("Warning: Protocol buffer modules not found.") - print("Install with: pip install grpcio grpcio-tools") - print("Proto files need to be generated from the REAPI definitions.") - sys.exit(1) +from benchmark.scripts import benchmark_lib @dataclass @@ -100,50 +88,7 @@ def print_summary(self): @staticmethod def _percentile(data: List[float], percentile: float) -> float: - if not data: - return 0.0 - sorted_data = sorted(data) - index = int(len(sorted_data) * percentile / 100) - return sorted_data[min(index, len(sorted_data) - 1)] - - -def create_simple_action(command: List[str]) -> tuple: - """Create a simple action for testing""" - # Create command proto - command_proto = remote_execution_pb2.Command( - arguments=command, - output_paths=["output.txt"] - ) - command_bytes = command_proto.SerializeToString() - command_digest = remote_execution_pb2.Digest( - hash=hashlib.sha256(command_bytes).hexdigest(), - size_bytes=len(command_bytes) - ) - - # REAPI requires input_root_digest even for actions with no inputs; it - # must reference a (possibly empty) Directory present in the CAS. Strict - # servers (e.g. NativeLink) reject actions without it. - input_root_proto = remote_execution_pb2.Directory() - input_root_bytes = input_root_proto.SerializeToString() - input_root_digest = remote_execution_pb2.Digest( - hash=hashlib.sha256(input_root_bytes).hexdigest(), - size_bytes=len(input_root_bytes) - ) - - # Create action proto - action_proto = remote_execution_pb2.Action( - command_digest=command_digest, - input_root_digest=input_root_digest, - do_not_cache=False - ) - action_bytes = action_proto.SerializeToString() - action_digest = remote_execution_pb2.Digest( - hash=hashlib.sha256(action_bytes).hexdigest(), - size_bytes=len(action_bytes) - ) - - return (action_digest, action_bytes, command_digest, command_bytes, - input_root_digest, input_root_bytes) + return benchmark_lib.percentile(data, percentile) def execute_action( @@ -229,22 +174,14 @@ def run_execution_load_test( command = ["echo", "hello"] # Simple fast action print(f"Connecting to {server}...") - - # Configure channel with keepalive timeouts - # Using relaxed settings for CI environment to reduce network overhead - channel_options = [ - ('grpc.keepalive_time_ms', 30000), # 30 seconds - ('grpc.keepalive_timeout_ms', 10000), # 10 seconds timeout - ('grpc.http2.max_pings_without_data', 0), - ('grpc.http2.min_time_between_pings_ms', 30000), - ] - channel = grpc.insecure_channel(server, options=channel_options) + + channel = benchmark_lib.make_channel(server) execution_stub = remote_execution_pb2_grpc.ExecutionStub(channel) - + # Create test action print(f"Creating test action: {' '.join(command)}") (action_digest, action_bytes, cmd_digest, cmd_bytes, - input_root_digest, input_root_bytes) = create_simple_action(command) + input_root_digest, input_root_bytes) = benchmark_lib.create_simple_action(command) # Upload action, command and input root to CAS first cas_stub = remote_execution_pb2_grpc.ContentAddressableStorageStub(channel) @@ -373,7 +310,6 @@ def main(): # Export to JSON if requested if args.output: - import json result_dict = { 'server': summary.server, 'total_actions': summary.total_actions, @@ -391,8 +327,7 @@ def main(): 'stddev': statistics.stdev(summary.latencies) if len(summary.latencies) > 1 else 0, } } - with open(args.output, 'w') as f: - json.dump(result_dict, f, indent=2) + benchmark_lib.save_json(args.output, result_dict) print(f"\nResults exported to: {args.output}") diff --git a/benchmark/scripts/generate-report.sh b/benchmark/scripts/generate-report.sh deleted file mode 100755 index b06bad6..0000000 --- a/benchmark/scripts/generate-report.sh +++ /dev/null @@ -1,398 +0,0 @@ -#!/bin/bash -# Generate comprehensive benchmark report from JSON results -# Creates a markdown report with comparisons, thresholds, and trends -# -# Usage: ./generate-report.sh [results_dir] [output_file] -# results_dir: Directory containing JSON results (default: ../results) -# output_file: Output markdown file (default: ../results/BENCHMARK_REPORT_.md) - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BENCHMARK_DIR="$(dirname "$SCRIPT_DIR")" -RESULTS_DIR="${1:-$BENCHMARK_DIR/results}" -TIMESTAMP=$(date +%Y%m%d_%H%M%S) -OUTPUT_FILE="${2:-$RESULTS_DIR/BENCHMARK_REPORT_${TIMESTAMP}.md}" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -CYAN='\033[0;36m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } - -# Thresholds for pass/fail -THRESHOLD_MEMORY_MB=20 -THRESHOLD_COLD_START_MS=500 -THRESHOLD_EXECUTION_P99_MS=100 -THRESHOLD_CACHE_P99_US=1000 -THRESHOLD_CLEANUP_RATE=95 - -# Function to extract value from JSON -get_json_value() { - local file="$1" - local key="$2" - local default="${3:-N/A}" - - if [ -f "$file" ]; then - python3 -c "import json,sys; d=json.load(open('$file')); print(d.get('$key', '$default'))" 2>/dev/null || echo "$default" - else - echo "$default" - fi -} - -# Function to extract nested value from JSON -get_json_nested() { - local file="$1" - local keys="$2" - local default="${3:-N/A}" - - if [ -f "$file" ]; then - python3 -c " -import json,sys -try: - d=json.load(open('$file')) - for k in '$keys'.split('.'): - if isinstance(d, dict) and k in d: - d = d[k] - else: - print('$default') - sys.exit(0) - print(d) -except: - print('$default') -" 2>/dev/null || echo "$default" - else - echo "$default" - fi -} - -# Function to format number -format_number() { - local num="$1" - local decimals="${2:-2}" - printf "%.${decimals}f" "$num" 2>/dev/null || echo "$num" -} - -# Function to check threshold -check_threshold() { - local value="$1" - local threshold="$2" - local operator="${3:-le}" # le = less than or equal (default), ge = greater than or equal - - if [ "$value" = "N/A" ] || [ "$value" = "null" ]; then - echo "⚠️" - return 1 - fi - - if [ "$operator" = "le" ]; then - if (( $(echo "$value <= $threshold" | bc -l 2>/dev/null || echo "0") )); then - echo "✅" - return 0 - else - echo "❌" - return 1 - fi - else - if (( $(echo "$value >= $threshold" | bc -l 2>/dev/null || echo "0") )); then - echo "✅" - return 0 - else - echo "❌" - return 1 - fi - fi -} - -# Find latest result files -find_latest() { - local pattern="$1" - ls -t $RESULTS_DIR/$pattern 2>/dev/null | head -1 -} - -# Main report generation -generate_report() { - log_info "Generating benchmark report..." - log_info "Results directory: $RESULTS_DIR" - log_info "Output file: $OUTPUT_FILE" - - # Find result files - local cache_file=$(find_latest "cache_*.json") - local cas_file=$(find_latest "cas_*.json") - local execution_file=$(find_latest "execution_*.json") - local stampede_file=$(find_latest "stampede_*.json") - local memory_file=$(find_latest "memory_baseline.txt") - local coldstart_file=$(find_latest "coldstart_*.txt") - local churn_file=$(find_latest "churn_*.json") - local benchmark_data=$(find_latest "benchmark_data.json") - - # Extract values - local memory_mb=$(cat "$memory_file" 2>/dev/null || echo "N/A") - local cold_start_ms=$(cat "$coldstart_file" 2>/dev/null || echo "N/A") - - local cache_throughput=$(get_json_nested "$cache_file" "throughput") - local cache_p99_us=$(get_json_nested "$cache_file" "latencies_us.p99") - local cache_ops=$(get_json_nested "$cache_file" "total_operations") - - local cas_upload_p99=$(get_json_nested "$cas_file" "upload_latencies.p99") - local cas_download_p99=$(get_json_nested "$cas_file" "download_latencies.p99") - local cas_blobs=$(get_json_nested "$cas_file" "total_blobs") - - local execution_p99=$(get_json_nested "$execution_file" "latencies.p99") - local execution_throughput=$(get_json_nested "$execution_file" "throughput") - local execution_actions=$(get_json_nested "$execution_file" "total_actions") - - local stampede_p99=$(get_json_nested "$stampede_file" "latencies_ms.p99") - local stampede_ratio=$(get_json_nested "$stampede_file" "latencies_ms.p99" | python3 -c "import sys; p99=float(sys.stdin.read()); print(p99)" 2>/dev/null) - local stampede_mean=$(get_json_nested "$stampede_file" "latencies_ms.mean") - - local cleanup_rate=$(get_json_nested "$churn_file" "cleanup_rate") - - # Convert cache P99 to ms for display - local cache_p99_ms=$(echo "$cache_p99_us" | awk '{print $1/1000}') - - # Calculate stampede ratio - if [ "$stampede_p99" != "N/A" ] && [ "$stampede_mean" != "N/A" ] && [ "$stampede_mean" != "0" ]; then - stampede_ratio=$(echo "scale=2; $stampede_p99 / $stampede_mean" | bc 2>/dev/null || echo "N/A") - fi - - # Get commit info - local commit=$(get_json_value "$benchmark_data" "commit" "N/A") - local mode=$(get_json_value "$benchmark_data" "mode" "unknown") - local image=$(get_json_nested "$benchmark_data" "container.image" "ferrisrbe/server:latest") - - # Check thresholds - local memory_status=$(check_threshold "$memory_mb" "$THRESHOLD_MEMORY_MB" "le") - local coldstart_status=$(check_threshold "$cold_start_ms" "$THRESHOLD_COLD_START_MS" "le") - local cache_status=$(check_threshold "$cache_p99_us" "$THRESHOLD_CACHE_P99_US" "le") - local execution_status=$(check_threshold "$execution_p99" "$THRESHOLD_EXECUTION_P99_MS" "le") - local cleanup_status=$(check_threshold "$cleanup_rate" "$THRESHOLD_CLEANUP_RATE" "ge") - - # Overall status - local overall_status="✅ PASS" - if [ "$memory_status" = "❌" ] || [ "$coldstart_status" = "❌" ] || [ "$cache_status" = "❌" ] || [ "$execution_status" = "❌" ]; then - overall_status="❌ FAIL" - elif [ "$memory_status" = "⚠️" ] || [ "$coldstart_status" = "⚠️" ]; then - overall_status="⚠️ PARTIAL" - fi - - # Generate markdown report - cat > "$OUTPUT_FILE" << EOF -# FerrisRBE Benchmark Report - -**Generated:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") -**Commit:** ${commit} -**Mode:** ${mode} -**Image:** ${image} - -## Executive Summary - -| Metric | Value | Threshold | Status | -|--------|-------|-----------|--------| -| **Memory Footprint** | ${memory_mb} MB | < ${THRESHOLD_MEMORY_MB} MB | ${memory_status} | -| **Cold Start** | ${cold_start_ms} ms | < ${THRESHOLD_COLD_START_MS} ms | ${coldstart_status} | -| **Cache P99 Latency** | $(format_number "$cache_p99_ms") ms | < $(echo "$THRESHOLD_CACHE_P99_US/1000" | bc) ms | ${cache_status} | -| **Execution P99 Latency** | $(format_number "$execution_p99") ms | < ${THRESHOLD_EXECUTION_P99_MS} ms | ${execution_status} | -| **Cleanup Rate** | ${cleanup_rate}% | > ${THRESHOLD_CLEANUP_RATE}% | ${cleanup_status} | - -### Overall Status: ${overall_status} - ---- - -## Detailed Results - -### 1. Memory Footprint - -**Container Memory Usage:** - -\`\`\` -Idle Memory: ${memory_mb} MB -Threshold: ${THRESHOLD_MEMORY_MB} MB -Status: ${memory_status} -\`\`\` - -**Analysis:** -$(if [ "$memory_status" = "✅" ]; then echo "Memory usage is within expected range. FerrisRBE maintains its characteristic low memory footprint."; elif [ "$memory_status" = "❌" ]; then echo "⚠️ Memory usage exceeds threshold. Investigate potential memory leaks or increased baseline usage."; else echo "Memory data not available or inconclusive."; fi) - -### 2. Cold Start Time - -**Container Startup Performance:** - -\`\`\` -Cold Start: ${cold_start_ms} ms -Threshold: ${THRESHOLD_COLD_START_MS} ms -Status: ${coldstart_status} -\`\`\` - -**Analysis:** -$(if [ "$coldstart_status" = "✅" ]; then echo "Fast cold start enables effective autoscaling. Container is ready to serve requests quickly."; elif [ "$coldstart_status" = "❌" ]; then echo "⚠️ Cold start is slower than expected. This may impact autoscaling responsiveness."; else echo "Cold start data not available."; fi) - -### 3. Action Cache Performance - -**Test Configuration:** -- Operations: ${cache_ops} -- Concurrency: $(get_json_nested "$cache_file" "concurrent") -- Type: $(get_json_nested "$cache_file" "operation") - -**Results:** - -| Metric | Value | -|--------|-------| -| Throughput | $(format_number "$cache_throughput") ops/sec | -| P99 Latency | $(format_number "$cache_p99_ms") ms | -| Success Rate | $(get_json_nested "$cache_file" "success_count")/$(get_json_nested "$cache_file" "total_operations") ($(python3 -c "print(f\"{$(get_json_nested "$cache_file" "success_count")/$(get_json_nested "$cache_file" "total_operations")*100:.1f}\")" 2>/dev/null || echo "N/A")%) | -| Cache Hits | $(get_json_nested "$cache_file" "hit_count") | - -**Analysis:** -$(if [ "$cache_status" = "✅" ]; then echo "Excellent cache performance. DashMap lock-free architecture delivers microsecond-level responses."; elif [ "$cache_status" = "❌" ]; then echo "⚠️ Cache latency higher than expected. Check for contention or backend issues."; else echo "Cache performance data incomplete."; fi) - -### 4. CAS Operations - -**Test Configuration:** -- Blobs: ${cas_blobs} -- Size per blob: $(get_json_nested "$cas_file" "blob_size" | awk '{print $1/1024/1024 " MB"}') -- Total data: $(get_json_nested "$cas_file" "total_bytes" | awk '{print $1/1024/1024 " MB"}') - -**Results:** - -| Operation | P50 | P95 | P99 | -|-----------|-----|-----|-----| -| Upload | $(format_number "$(get_json_nested "$cas_file" "upload_latencies.p50")") ms | $(format_number "$(get_json_nested "$cas_file" "upload_latencies.p95")") ms | $(format_number "$cas_upload_p99") ms | -| Download | $(format_number "$(get_json_nested "$cas_file" "download_latencies.p50")") ms | $(format_number "$(get_json_nested "$cas_file" "download_latencies.p95")") ms | $(format_number "$cas_download_p99") ms | - -**Analysis:** -$(if [ -n "$cas_upload_p99" ] && [ "$cas_upload_p99" != "N/A" ]; then echo "CAS operations completing successfully. Upload and download latencies are within acceptable ranges for blob storage operations."; else echo "CAS test data incomplete."; fi) - -### 5. Execution Throughput - -**Test Configuration:** -- Actions: ${execution_actions} -- Concurrency: $(get_json_nested "$execution_file" "concurrent") - -**Results:** - -| Metric | Value | -|--------|-------| -| Throughput | $(format_number "$execution_throughput") actions/sec | -| Success | $(get_json_nested "$execution_file" "success_count")/$(get_json_nested "$execution_file" "total_actions") | -| P50 Latency | $(format_number "$(get_json_nested "$execution_file" "latencies.p50")") ms | -| P99 Latency | $(format_number "$execution_p99") ms | -| Jitter (StdDev) | $(format_number "$(get_json_nested "$execution_file" "latencies.stddev")") ms | - -**Analysis:** -$(if [ "$execution_status" = "✅" ]; then echo "Execution API performing well. Zero-GC runtime provides consistent latencies without spikes."; elif [ "$execution_status" = "❌" ]; then echo "⚠️ Execution latency exceeds threshold. Check scheduler and worker pool health."; else echo "Execution data incomplete."; fi) - -### 6. Cache Stampede Protection - -**Test Configuration:** -- Total Requests: $(get_json_nested "$stampede_file" "total_requests") -- Concurrency: $(get_json_nested "$stampede_file" "concurrent") -- Target: Same uncached action digest - -**Results:** - -| Metric | Value | -|--------|-------| -| Cache Hits | $(get_json_nested "$stampede_file" "cache_hits") | -| Cache Misses | $(get_json_nested "$stampede_file" "cache_misses") | -| Errors | $(get_json_nested "$stampede_file" "errors") | -| P99/Mean Ratio | $(format_number "$stampede_ratio")x | - -**Analysis:** -$(if [ -n "$stampede_ratio" ] && [ "$stampede_ratio" != "N/A" ] && (( $(echo "$stampede_ratio < 3" | bc -l 2>/dev/null || echo "0") )); then echo "✅ Good stampede protection. Request coalescing or fast backend prevents thundering herd issues."; else echo "⚠️ High P99/Mean ratio may indicate backend contention under load."; fi) - -### 7. Connection Churn - -**Results:** - -| Metric | Value | -|--------|-------| -| Connections Tested | $(get_json_nested "$churn_file" "connections_tested") | -| Abrupt Disconnects | $(get_json_nested "$churn_file" "abrupt_disconnects") | -| Cleanup Rate | ${cleanup_rate}% | -| Zombie Resources | $(get_json_nested "$churn_file" "zombie_resources") | - -**Analysis:** -$(if [ "$cleanup_status" = "✅" ]; then echo "Excellent resource cleanup. Tokio's task cancellation releases resources immediately on connection drops."; elif [ "$cleanup_status" = "❌" ]; then echo "⚠️ Cleanup rate below threshold. Potential resource leaks detected."; else echo "Connection churn data incomplete."; fi) - ---- - -## Comparison with Official Release - -$(if [ -f "$RESULTS_DIR/comparison.md" ]; then cat "$RESULTS_DIR/comparison.md"; else echo "*No comparison data available. Run \`./scripts/compare-branches.sh\` to compare against official release.*"; fi) - ---- - -## Recommendations - -$(if [ "$overall_status" = "✅ PASS" ]; then echo "All benchmarks passing. No action required."; elif [ "$overall_status" = "❌ FAIL" ]; then echo "### ⚠️ Performance Regressions Detected - -1. Review failed metrics above -2. Compare against baseline (\`xangcastle/ferris-server:latest\`) -3. Profile the application to identify bottlenecks -4. Re-run benchmarks after optimizations"; else echo "### Partial Results - -Some benchmarks did not complete or data is missing. Check: -- Container logs for errors -- Test connectivity to localhost:9092 -- Required services (rbe-cache) are running"; fi) - ---- - -## Raw Data Files - -| Test | File | -|------|------| -| Action Cache | \`$(basename "$cache_file")\` | -| CAS Load | \`$(basename "$cas_file")\` | -| Execution | \`$(basename "$execution_file")\` | -| Cache Stampede | \`$(basename "$stampede_file")\` | -| Connection Churn | \`$(basename "$churn_file")\` | -| Benchmark Data | \`$(basename "$benchmark_data")\` | - ---- - -*Generated by FerrisRBE Benchmark Suite v2.0 - Container Native* -EOF - - log_success "Report generated: $OUTPUT_FILE" - - # Also create/update LATEST_REPORT.md symlink - local latest_link="$RESULTS_DIR/LATEST_REPORT.md" - ln -sf "$(basename "$OUTPUT_FILE")" "$latest_link" - log_info "Latest report link updated: $latest_link" - - # Display summary - echo "" - echo "========================================" - echo " BENCHMARK REPORT SUMMARY" - echo "========================================" - echo "" - echo "Overall Status: $overall_status" - echo "" - echo "Key Metrics:" - echo " Memory: ${memory_mb} MB ${memory_status}" - echo " Cold Start: ${cold_start_ms} ms ${coldstart_status}" - echo " Cache P99: $(format_number "$cache_p99_ms") ms ${cache_status}" - echo " Execution P99: $(format_number "$execution_p99") ms ${execution_status}" - echo "" - echo "Report: $OUTPUT_FILE" - echo "========================================" -} - -# Check if results directory exists -if [ ! -d "$RESULTS_DIR" ]; then - log_error "Results directory not found: $RESULTS_DIR" - exit 1 -fi - -# Generate report -generate_report - -exit 0 diff --git a/benchmark/scripts/memory-footprint.sh b/benchmark/scripts/memory-footprint.sh deleted file mode 100755 index a31f03f..0000000 --- a/benchmark/scripts/memory-footprint.sh +++ /dev/null @@ -1,92 +0,0 @@ -#!/bin/bash -# Simple memory footprint test for RBE servers -# Uses Bazel to build FerrisRBE (dogfooding) -# Works with any Bazel configuration (--symlink_prefix) - -echo "========================================" -echo "RBE Memory Footprint Comparison" -echo "========================================" -echo "" - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Source Bazel utilities -source "$SCRIPT_DIR/bazel-utils.sh" - -PROJECT_ROOT="$(get_workspace_root "$SCRIPT_DIR/../..")" - -# Test FerrisRBE (using Bazel - dogfooding) -echo "🦀 FerrisRBE (Rust + Bazel)..." - -cd "$PROJECT_ROOT" - -# Find server binary -server_output=$(find_bazel_output "//:rbe-server" "$PROJECT_ROOT") - -if [ -z "$server_output" ] || [ ! -f "$server_output" ]; then - echo " Building with Bazel..." - server_output=$(bazel_build_and_get_output "//:rbe-server" "$PROJECT_ROOT" "release") -fi - -# Run native binary -if [ -n "$server_output" ] && [ -f "$server_output" ]; then - echo " Binary location: $server_output" - - # Check if binary is executable and compatible - if [ ! -x "$server_output" ]; then - chmod +x "$server_output" 2>/dev/null || true - fi - - # Try to execute - if "$server_output" --version &>/dev/null || "$server_output" --help &>/dev/null; then - "$server_output" & - SERVER_PID=$! - sleep 5 - - echo " Memory (idle):" - mem=$(ps -o rss= -p $SERVER_PID 2>/dev/null | awk '{print $1/1024}') - if [ -n "$mem" ] && [ "$mem" != "0" ]; then - printf " %.1fMiB\n" "$mem" - else - echo " Unable to measure (process may have exited)" - fi - - kill $SERVER_PID 2>/dev/null || true - wait $SERVER_PID 2>/dev/null || true - else - echo " ⚠️ Binary not compatible with this architecture (expected in CI/Linux)" - echo " Binary type: $(file "$server_output" 2>/dev/null | cut -d: -f2 | xargs)" - echo " Using sample data: ~6.7MiB" - fi -else - echo " Binary not available, using sample data: ~6.7MiB" -fi -echo "" - -# Sample data for other RBE solutions (based on typical measurements) -echo "☕ Buildfarm (Java/OpenJDK - Docker)..." -echo " Memory (idle): ~800-1200MiB (typical JVM footprint)" -echo " GC Pauses: Yes (G1GC, ~50-100ms)" -echo "" - -echo "🔧 Buildbarn (Go - Docker)..." -echo " Memory (idle): ~120-200MiB" -echo " GC Pauses: Minimal (Go GC)" -echo "" - -echo "🏢 BuildBuddy (Java/Go - Docker)..." -echo " Memory (idle): ~1.2-2GB (includes PostgreSQL, Redis)" -echo " GC Pauses: Yes (JVM components)" -echo "" - -echo "========================================" -echo "Summary (Idle Memory)" -echo "========================================" -echo "FerrisRBE: ~6.7 MB ⭐ (Rust/Bazel/distroless)" -echo "Buildbarn: ~120-200 MB" -echo "Buildfarm: ~800-1200 MB" -echo "BuildBuddy: ~1200-2000 MB" -echo "========================================" -echo "" -echo "Note: FerrisRBE is built with Bazel (bazel build //:rbe-server)" -echo " Other solutions use upstream Docker images" diff --git a/benchmark/scripts/metrics-collector.py b/benchmark/scripts/metrics-collector.py deleted file mode 100755 index 331aead..0000000 --- a/benchmark/scripts/metrics-collector.py +++ /dev/null @@ -1,333 +0,0 @@ -#!/usr/bin/env python3 -""" -Metrics Collector for RBE Benchmarking -Collects container metrics from Docker and Prometheus during benchmark runs -""" - -import argparse -import json -import subprocess -import sys -import time -from dataclasses import dataclass, field, asdict -from typing import Dict, List, Optional -from datetime import datetime - - -@dataclass -class ContainerMetrics: - """Container resource metrics snapshot""" - timestamp: float - container_name: str - cpu_percent: float - memory_usage_mb: float - memory_limit_mb: float - memory_percent: float - net_io_read_mb: float - net_io_write_mb: float - block_io_read_mb: float - block_io_write_mb: float - pids: int - - -@dataclass -class BenchmarkMetrics: - """Complete benchmark metrics collection""" - benchmark_name: str - start_time: str - end_time: Optional[str] = None - container_snapshots: List[ContainerMetrics] = field(default_factory=list) - - def get_memory_stats(self, container_name: str) -> Dict: - """Get memory statistics for a specific container""" - snapshots = [s for s in self.container_snapshots if s.container_name == container_name] - if not snapshots: - return {} - - memory_values = [s.memory_usage_mb for s in snapshots] - return { - 'container': container_name, - 'min_mb': min(memory_values), - 'max_mb': max(memory_values), - 'avg_mb': sum(memory_values) / len(memory_values), - 'samples': len(memory_values) - } - - def get_cpu_stats(self, container_name: str) -> Dict: - """Get CPU statistics for a specific container""" - snapshots = [s for s in self.container_snapshots if s.container_name == container_name] - if not snapshots: - return {} - - cpu_values = [s.cpu_percent for s in snapshots] - return { - 'container': container_name, - 'min_percent': min(cpu_values), - 'max_percent': max(cpu_values), - 'avg_percent': sum(cpu_values) / len(cpu_values), - 'samples': len(cpu_values) - } - - -class DockerMetricsCollector: - """Collect metrics using Docker CLI""" - - def __init__(self): - self._check_docker() - - def _check_docker(self): - """Verify Docker is available""" - try: - subprocess.run(['docker', 'version'], capture_output=True, check=True) - except (subprocess.CalledProcessError, FileNotFoundError): - raise RuntimeError("Docker is not available. Please install Docker.") - - def get_container_stats(self, container_name: str) -> Optional[ContainerMetrics]: - """Get stats for a single container""" - try: - result = subprocess.run( - ['docker', 'stats', container_name, '--no-stream', '--format', - '{{.CPUPerc}}|{{.MemUsage}}|{{.MemPerc}}|{{.NetIO}}|{{.BlockIO}}|{{.PIDs}}'], - capture_output=True, - text=True, - timeout=10 - ) - - if result.returncode != 0: - return None - - output = result.stdout.strip() - if not output: - return None - - # Parse: 0.15%|50MiB / 512MiB|9.77%|1.2MB / 800kB|0B / 0B|15 - parts = output.split('|') - if len(parts) < 6: - return None - - cpu_percent = float(parts[0].replace('%', '')) - - # Memory: "50MiB / 512MiB" - mem_parts = parts[1].split('/') - memory_usage = self._parse_size(mem_parts[0].strip()) - memory_limit = self._parse_size(mem_parts[1].strip()) - memory_percent = float(parts[2].replace('%', '')) - - # Network I/O: "1.2MB / 800kB" - net_parts = parts[3].split('/') - net_read = self._parse_size(net_parts[0].strip()) - net_write = self._parse_size(net_parts[1].strip()) - - # Block I/O: "0B / 0B" - block_parts = parts[4].split('/') - block_read = self._parse_size(block_parts[0].strip()) - block_write = self._parse_size(block_parts[1].strip()) - - pids = int(parts[5]) - - return ContainerMetrics( - timestamp=time.time(), - container_name=container_name, - cpu_percent=cpu_percent, - memory_usage_mb=memory_usage, - memory_limit_mb=memory_limit, - memory_percent=memory_percent, - net_io_read_mb=net_read, - net_io_write_mb=net_write, - block_io_read_mb=block_read, - block_io_write_mb=block_write, - pids=pids - ) - - except Exception as e: - print(f"Error collecting metrics for {container_name}: {e}", file=sys.stderr) - return None - - def _parse_size(self, size_str: str) -> float: - """Parse size string to MB""" - size_str = size_str.strip().upper() - if size_str == '0B': - return 0.0 - - units = { - 'B': 1 / (1024 * 1024), - 'KB': 1 / 1024, - 'KIB': 1 / 1024, - 'MB': 1, - 'MIB': 1, - 'GB': 1024, - 'GIB': 1024, - 'TB': 1024 * 1024, - 'TIB': 1024 * 1024, - } - - # Longest suffix first: every MiB/GiB/KiB value also ends with "B", - # so insertion-order matching would hit 'B' and fail the float parse. - for unit, multiplier in sorted(units.items(), key=lambda kv: -len(kv[0])): - if size_str.endswith(unit): - try: - value = float(size_str[:-len(unit)]) - return value * multiplier - except ValueError: - return 0.0 - - # Try to parse as plain number - try: - return float(size_str) / (1024 * 1024) # Assume bytes - except ValueError: - return 0.0 - - def list_containers(self, prefix: str = '') -> List[str]: - """List running containers with optional prefix filter""" - try: - result = subprocess.run( - ['docker', 'ps', '--format', '{{.Names}}'], - capture_output=True, - text=True, - check=True - ) - containers = result.stdout.strip().split('\n') - if prefix: - containers = [c for c in containers if c.startswith(prefix)] - return [c for c in containers if c] - except subprocess.CalledProcessError: - return [] - - -def collect_metrics( - duration_seconds: int, - interval_seconds: int, - containers: List[str], - benchmark_name: str -) -> BenchmarkMetrics: - """Collect metrics for specified duration""" - - collector = DockerMetricsCollector() - metrics = BenchmarkMetrics( - benchmark_name=benchmark_name, - start_time=datetime.now().isoformat() - ) - - print(f"Collecting metrics for {duration_seconds}s (interval: {interval_seconds}s)") - print(f"Monitoring containers: {', '.join(containers)}") - print("-" * 60) - - start_time = time.time() - iterations = 0 - - try: - while time.time() - start_time < duration_seconds: - iteration_start = time.time() - - for container in containers: - snapshot = collector.get_container_stats(container) - if snapshot: - metrics.container_snapshots.append(snapshot) - # Print current status - print(f"[{iterations:4d}] {container:30s} | " - f"CPU: {snapshot.cpu_percent:6.2f}% | " - f"Mem: {snapshot.memory_usage_mb:8.1f}MB ({snapshot.memory_percent:5.2f}%)") - - iterations += 1 - - # Sleep until next interval - elapsed = time.time() - iteration_start - sleep_time = max(0, interval_seconds - elapsed) - if sleep_time > 0: - time.sleep(sleep_time) - - except KeyboardInterrupt: - print("\nMetrics collection interrupted by user") - - metrics.end_time = datetime.now().isoformat() - return metrics - - -def print_summary(metrics: BenchmarkMetrics): - """Print summary statistics""" - print("\n" + "=" * 60) - print(f"METRICS SUMMARY: {metrics.benchmark_name}") - print("=" * 60) - print(f"Duration: {metrics.start_time} to {metrics.end_time}") - print(f"Total snapshots: {len(metrics.container_snapshots)}") - - # Get unique container names - containers = set(s.container_name for s in metrics.container_snapshots) - - print("\nMEMORY STATISTICS:") - print("-" * 60) - print(f"{'Container':<30} {'Min (MB)':<12} {'Max (MB)':<12} {'Avg (MB)':<12}") - print("-" * 60) - for container in sorted(containers): - stats = metrics.get_memory_stats(container) - if stats: - print(f"{container:<30} {stats['min_mb']:<12.1f} {stats['max_mb']:<12.1f} {stats['avg_mb']:<12.1f}") - - print("\nCPU STATISTICS:") - print("-" * 60) - print(f"{'Container':<30} {'Min (%)':<12} {'Max (%)':<12} {'Avg (%)':<12}") - print("-" * 60) - for container in sorted(containers): - stats = metrics.get_cpu_stats(container) - if stats: - print(f"{container:<30} {stats['min_percent']:<12.2f} {stats['max_percent']:<12.2f} {stats['avg_percent']:<12.2f}") - - print("=" * 60) - - -def main(): - parser = argparse.ArgumentParser(description='Collect container metrics during benchmarking') - parser.add_argument('--duration', type=int, default=300, help='Collection duration in seconds') - parser.add_argument('--interval', type=int, default=5, help='Sampling interval in seconds') - parser.add_argument('--containers', nargs='+', default=[], - help='Container names to monitor (default: auto-detect)') - parser.add_argument('--prefix', default='', help='Container name prefix filter') - parser.add_argument('--benchmark', default='rbe-benchmark', help='Benchmark name') - parser.add_argument('--output', required=True, help='Output JSON file') - - args = parser.parse_args() - - # Auto-detect containers if not specified - containers = args.containers - if not containers: - collector = DockerMetricsCollector() - containers = collector.list_containers(args.prefix) - if not containers: - print("No containers found to monitor", file=sys.stderr) - sys.exit(1) - - # Collect metrics - metrics = collect_metrics( - duration_seconds=args.duration, - interval_seconds=args.interval, - containers=containers, - benchmark_name=args.benchmark - ) - - # Print summary - print_summary(metrics) - - # Export to JSON - output_data = { - 'benchmark_name': metrics.benchmark_name, - 'start_time': metrics.start_time, - 'end_time': metrics.end_time, - 'containers': list(set(s.container_name for s in metrics.container_snapshots)), - 'snapshots': [asdict(s) for s in metrics.container_snapshots], - 'summary': { - container: { - 'memory': metrics.get_memory_stats(container), - 'cpu': metrics.get_cpu_stats(container) - } - for container in set(s.container_name for s in metrics.container_snapshots) - } - } - - with open(args.output, 'w') as f: - json.dump(output_data, f, indent=2) - - print(f"\nMetrics exported to: {args.output}") - - -if __name__ == '__main__': - main() diff --git a/benchmark/scripts/noisy-neighbor-test.py b/benchmark/scripts/noisy-neighbor-test.py index 5e41406..8b1a14a 100755 --- a/benchmark/scripts/noisy-neighbor-test.py +++ b/benchmark/scripts/noisy-neighbor-test.py @@ -13,25 +13,17 @@ import argparse import hashlib -import os -import sys -import time import statistics -from dataclasses import dataclass, field -from typing import List, Optional, Dict +import time from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import dataclass, field from datetime import datetime +from typing import Dict, List, Optional import grpc -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'proto_gen')) - -try: - from build.bazel.remote.execution.v2 import remote_execution_pb2 - from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc -except ImportError: - print("Warning: Protocol buffer modules not found.") - sys.exit(1) +from build.bazel.remote.execution.v2 import remote_execution_pb2 +from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc @dataclass diff --git a/benchmark/scripts/o1-streaming-test.py b/benchmark/scripts/o1-streaming-test.py index bf15f53..26077a6 100755 --- a/benchmark/scripts/o1-streaming-test.py +++ b/benchmark/scripts/o1-streaming-test.py @@ -9,28 +9,20 @@ import argparse import hashlib -import os -import sys -import time import statistics import tempfile import threading +import time +from concurrent.futures import ThreadPoolExecutor, as_completed from dataclasses import dataclass, field from typing import List, Optional, Tuple -from concurrent.futures import ThreadPoolExecutor, as_completed import grpc -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'proto_gen')) - -try: - from build.bazel.remote.execution.v2 import remote_execution_pb2 - from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc - from google.bytestream import bytestream_pb2 - from google.bytestream import bytestream_pb2_grpc -except ImportError: - print("Warning: Protocol buffer modules not found.") - sys.exit(1) +from build.bazel.remote.execution.v2 import remote_execution_pb2 +from build.bazel.remote.execution.v2 import remote_execution_pb2_grpc +from google.bytestream import bytestream_pb2 +from google.bytestream import bytestream_pb2_grpc @dataclass @@ -127,7 +119,7 @@ def get_container_memory(container_name: str) -> float: import subprocess try: result = subprocess.run( - ['docker', 'stats', container_name, '--no-stream', '--format', '{{.MemUsage}}'], + ['podman', 'stats', container_name, '--no-stream', '--format', '{{.MemUsage}}'], capture_output=True, text=True, timeout=5 ) mem_str = result.stdout.strip().split('/')[0].strip() diff --git a/benchmark/scripts/run-benchmark.sh b/benchmark/scripts/run-benchmark.sh deleted file mode 100755 index 37b912f..0000000 --- a/benchmark/scripts/run-benchmark.sh +++ /dev/null @@ -1,471 +0,0 @@ -#!/bin/bash -# RBE Benchmark Runner -# Runs comparative benchmarks between FerrisRBE and other solutions - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BENCHMARK_DIR="$(dirname "$SCRIPT_DIR")" -PROJECT_DIR="$(dirname "$BENCHMARK_DIR")" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Default values -BLOBS=100 -BLOB_SIZE=1048576 # 1MB -CONCURRENT=10 -DURATION=300 # 5 minutes -RESULTS_DIR="$BENCHMARK_DIR/results" - -# Print usage -usage() { - cat </dev/null 2>&1 || { log_error "Docker is required but not installed"; exit 1; } - command -v docker-compose >/dev/null 2>&1 || { log_error "Docker Compose is required"; exit 1; } - - # Create benchmark network if it doesn't exist - docker network create benchmark-network 2>/dev/null || true - - log_success "Setup complete" -} - -# Cleanup function -cleanup() { - log_info "Cleaning up..." - - # Stop and remove containers for the current target - case "$TARGET" in - ferrisrbe) - docker-compose -f "$BENCHMARK_DIR/docker-compose.ferrisrbe.yml" down -v 2>/dev/null || true - ;; - buildfarm) - docker-compose -f "$BENCHMARK_DIR/docker-compose.buildfarm.yml" down -v 2>/dev/null || true - ;; - buildbarn) - docker-compose -f "$BENCHMARK_DIR/docker-compose.buildbarn.yml" down -v 2>/dev/null || true - ;; - buildbuddy) - docker-compose -f "$BENCHMARK_DIR/docker-compose.buildbuddy.yml" down -v 2>/dev/null || true - ;; - nativelink) - docker-compose -f "$BENCHMARK_DIR/docker-compose.nativelink.yml" down -v 2>/dev/null || true - ;; - esac - - log_success "Cleanup complete" -} - -# Extract BuildBuddy API key from its in-process SQLite DB -# BuildBuddy Enterprise requires an API key even for anonymous usage; the key -# is generated on first startup and stored at /tmp/buildbuddy.db inside the -# container. -extract_buildbuddy_api_key() { - local tmpdb - tmpdb=$(mktemp /tmp/buildbuddy_bench_XXXXXX.db) - - docker cp buildbuddy-server:/tmp/buildbuddy.db "${tmpdb}" 2>/dev/null || { rm -f "${tmpdb}"; echo ""; return; } - docker cp buildbuddy-server:/tmp/buildbuddy.db-wal "${tmpdb}-wal" 2>/dev/null || true - docker cp buildbuddy-server:/tmp/buildbuddy.db-shm "${tmpdb}-shm" 2>/dev/null || true - - local api_key - api_key=$(python3 - </dev/null; then - log_success "$name is ready" - return 0 - fi - echo -n "." - sleep 2 - attempt=$((attempt + 1)) - done - - log_error "$name failed to start after $max_attempts attempts" - return 1 -} - -# Run benchmark for a specific target -run_benchmark() { - local target="$1" - local compose_file="$BENCHMARK_DIR/docker-compose.$target.yml" - local timestamp=$(date +%Y%m%d_%H%M%S) - local result_dir="$RESULTS_DIR/${target}_${timestamp}" - - log_info "Starting benchmark for: $target" - log_info "Results will be saved to: $result_dir" - - mkdir -p "$result_dir" - - # Save benchmark configuration - cat > "$result_dir/config.json" < "$result_dir/docker_stats.txt" 2>/dev/null || true - docker inspect "$server_container" > "$result_dir/container_inspect.json" 2>/dev/null || true - - # Generate quick summary - log_info "Generating summary..." - if [ -f "$result_dir/metrics.json" ]; then - python3 </dev/null | tee -a "$RESULT_FILE" || echo "Container not running" | tee -a "$RESULT_FILE" - -echo "" | tee -a "$RESULT_FILE" -echo "🚀 Starting load generation for ${DURATION}s..." | tee -a "$RESULT_FILE" - -# Collect metrics in background -METRICS_FILE=$(mktemp) -echo "timestamp,container,memory_mb,memory_percent,cpu_percent" > "$METRICS_FILE" - -# Function to collect metrics -collect_metrics() { - local start_time=$(date +%s) - while true; do - local current_time=$(date +%s) - local elapsed=$((current_time - start_time)) - if [ $elapsed -ge $DURATION ]; then - break - fi - - # Get stats - STATS=$(docker stats "$CONTAINER" --no-stream --format "{{.MemUsage}},{{.MemPerc}},{{.CPUPerc}}" 2>/dev/null) - if [ -n "$STATS" ]; then - # Parse memory usage (e.g., "50MiB / 512MiB" -> 50) - MEM_RAW=$(echo "$STATS" | cut -d',' -f1 | awk '{print $1}') - MEM_MB=$(echo "$MEM_RAW" | sed 's/MiB//' | sed 's/GiB/*1024/' | sed 's/KiB\/1024/' | bc 2>/dev/null || echo "0") - MEM_PCT=$(echo "$STATS" | cut -d',' -f2 | sed 's/%//') - CPU_PCT=$(echo "$STATS" | cut -d',' -f3 | sed 's/%//') - - echo "$(date +%s),$CONTAINER,$MEM_MB,$MEM_PCT,$CPU_PCT" >> "$METRICS_FILE" - fi - - # Generate load using grpcurl if available - if command -v grpcurl &> /dev/null; then - # Small gRPC request - grpcurl -plaintext -max-time 2 "localhost:$PORT" build.bazel.remote.execution.v2.Capabilities/GetCapabilities 2>/dev/null > /dev/null || true - fi - - sleep 1 - done -} - -# Start collecting metrics in background -collect_metrics & -METRICS_PID=$! - -# Wait for completion -wait $METRICS_PID - -echo "" | tee -a "$RESULT_FILE" -echo "📊 Results Summary:" | tee -a "$RESULT_FILE" -echo "-------------------" | tee -a "$RESULT_FILE" - -# Calculate statistics from collected metrics -if [ -f "$METRICS_FILE" ] && [ $(wc -l < "$METRICS_FILE") -gt 1 ]; then - # Skip header and calculate stats - echo "Memory (MB) Statistics:" | tee -a "$RESULT_FILE" - echo " Min: $(tail -n +2 "$METRICS_FILE" | cut -d',' -f3 | sort -n | head -1)" | tee -a "$RESULT_FILE" - echo " Max: $(tail -n +2 "$METRICS_FILE" | cut -d',' -f3 | sort -n | tail -1)" | tee -a "$RESULT_FILE" - echo " Avg: $(tail -n +2 "$METRICS_FILE" | cut -d',' -f3 | awk '{sum+=$1; count++} END {printf "%.1f", sum/count}')" | tee -a "$RESULT_FILE" - - echo "" | tee -a "$RESULT_FILE" - echo "CPU (%) Statistics:" | tee -a "$RESULT_FILE" - echo " Min: $(tail -n +2 "$METRICS_FILE" | cut -d',' -f5 | sort -n | head -1)" | tee -a "$RESULT_FILE" - echo " Max: $(tail -n +2 "$METRICS_FILE" | cut -d',' -f5 | sort -n | tail -1)" | tee -a "$RESULT_FILE" - echo " Avg: $(tail -n +2 "$METRICS_FILE" | cut -d',' -f5 | awk '{sum+=$1; count++} END {printf "%.1f", sum/count}')" | tee -a "$RESULT_FILE" - - # Save metrics to result dir - cp "$METRICS_FILE" "$RESULTS_DIR/${TARGET}_${TIMESTAMP}_metrics.csv" - echo "" | tee -a "$RESULT_FILE" - echo "📁 Raw metrics saved to: $RESULTS_DIR/${TARGET}_${TIMESTAMP}_metrics.csv" | tee -a "$RESULT_FILE" -fi - -# Cleanup -rm -f "$METRICS_FILE" - -echo "" | tee -a "$RESULT_FILE" -echo "✅ Benchmark complete!" | tee -a "$RESULT_FILE" diff --git a/benchmark/scripts/verify-setup.sh b/benchmark/scripts/verify-setup.sh deleted file mode 100755 index e647a53..0000000 --- a/benchmark/scripts/verify-setup.sh +++ /dev/null @@ -1,286 +0,0 @@ -#!/bin/bash -# Verify Benchmark Setup -# Comprehensive check that all components are ready for benchmarking - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BENCHMARK_DIR="$(dirname "$SCRIPT_DIR")" -PROJECT_ROOT="$(dirname "$BENCHMARK_DIR")" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -PASS=0 -FAIL=0 -WARN=0 - -pass() { - echo -e "${GREEN}✓${NC} $1" - ((PASS++)) -} - -fail() { - echo -e "${RED}✗${NC} $1" - ((FAIL++)) -} - -warn() { - echo -e "${YELLOW}⚠${NC} $1" - ((WARN++)) -} - -info() { - echo -e "${BLUE}ℹ${NC} $1" -} - -header() { - echo "" - echo "========================================" - echo "$1" - echo "========================================" -} - -# Check if command exists -check_command() { - local cmd="$1" - local required="${2:-true}" - - if command -v "$cmd" &> /dev/null; then - local version=$("$cmd" --version 2>/dev/null | head -1 || echo "unknown") - pass "$cmd: $version" - return 0 - else - if [ "$required" = "true" ]; then - fail "$cmd: not found (required)" - return 1 - else - warn "$cmd: not found (optional)" - return 1 - fi - fi -} - -# Check file exists and is executable -check_executable() { - local file="$1" - local desc="$2" - - if [ -f "$file" ]; then - if [ -x "$file" ]; then - pass "$desc" - return 0 - else - fail "$desc: not executable" - return 1 - fi - else - fail "$desc: file not found" - return 1 - fi -} - -# Check directory exists -check_directory() { - local dir="$1" - local desc="$2" - - if [ -d "$dir" ]; then - pass "$desc" - return 0 - else - fail "$desc: directory not found" - return 1 - fi -} - -# Main verification -main() { - echo "========================================" - echo "FerrisRBE Benchmark Setup Verification" - echo "========================================" - echo "" - info "Project root: $PROJECT_ROOT" - info "Benchmark dir: $BENCHMARK_DIR" - - # Section 1: Required Commands - header "1. Required Commands" - - check_command "bazel" true || true - check_command "python3" true || true - check_command "docker" false || true - check_command "bc" false || true - - # Section 2: Python Dependencies - header "2. Python Dependencies" - - python3 -c "import grpc" 2>/dev/null && pass "grpc (Python)" || fail "grpc (Python): not installed" - - # Section 3: Directory Structure - header "3. Directory Structure" - - check_directory "$BENCHMARK_DIR/scripts" "scripts/ directory" - check_directory "$BENCHMARK_DIR/results" "results/ directory" - check_directory "$BENCHMARK_DIR/config" "config/ directory" - - # Section 4: Core Scripts - header "4. Core Benchmark Scripts" - - check_executable "$SCRIPT_DIR/bazel-utils.sh" "bazel-utils.sh" - check_executable "$SCRIPT_DIR/build-with-bazel.sh" "build-with-bazel.sh" - check_executable "$SCRIPT_DIR/benchmark.sh" "benchmark.sh" - check_executable "$SCRIPT_DIR/benchmark-local.sh" "benchmark-local.sh (local testing)" - check_executable "$SCRIPT_DIR/benchmark-ci.sh" "benchmark-ci.sh (CI/CD)" - check_executable "$SCRIPT_DIR/check-regression.py" "check-regression.py" - - # Section 5: Test Scripts - header "5. Test Scripts" - - check_executable "$SCRIPT_DIR/execution-load-test.py" "execution-load-test.py" - check_executable "$SCRIPT_DIR/action-cache-test.py" "action-cache-test.py" - check_executable "$SCRIPT_DIR/noisy-neighbor-test.py" "noisy-neighbor-test.py" - check_executable "$SCRIPT_DIR/o1-streaming-test.py" "o1-streaming-test.py" - check_executable "$SCRIPT_DIR/connection-churn-test.py" "connection-churn-test.py" - check_executable "$SCRIPT_DIR/cache-stampede-test.py" "cache-stampede-test.py" - check_executable "$SCRIPT_DIR/cold-start-test.sh" "cold-start-test.sh" - - # Section 6: Docker Compose Files - header "6. Docker Compose Configurations" - - if [ -f "$BENCHMARK_DIR/docker-compose.ferrisrbe.yml" ]; then - pass "docker-compose.ferrisrbe.yml" - else - fail "docker-compose.ferrisrbe.yml" - fi - - if [ -f "$BENCHMARK_DIR/docker-compose.buildfarm.yml" ]; then - pass "docker-compose.buildfarm.yml" - else - warn "docker-compose.buildfarm.yml (optional)" - fi - - # Section 7: Bazel Configuration - header "7. Bazel Configuration" - - if [ -f "$PROJECT_ROOT/.bazelrc" ]; then - pass ".bazelrc exists" - - # Check for symlink_prefix configuration - if grep -q "symlink_prefix" "$PROJECT_ROOT/.bazelrc" 2>/dev/null; then - local prefix=$(grep "symlink_prefix" "$PROJECT_ROOT/.bazelrc" | head -1) - info "Custom symlink_prefix detected: $prefix" - info "Scripts will handle this automatically" - fi - else - warn ".bazelrc: not found (using defaults)" - fi - - if [ -f "$PROJECT_ROOT/MODULE.bazel" ]; then - pass "MODULE.bazel (bzlmod)" - elif [ -f "$PROJECT_ROOT/WORKSPACE" ]; then - pass "WORKSPACE (legacy)" - else - fail "No Bazel workspace found" - fi - - # Section 8: Test Bazel Functions - header "8. Bazel Integration" - - if [ -f "$SCRIPT_DIR/bazel-utils.sh" ]; then - source "$SCRIPT_DIR/bazel-utils.sh" - - # Test get_bazel_bin - if command -v bazel &> /dev/null; then - local bazel_bin=$(get_bazel_bin "$PROJECT_ROOT") - if [ -n "$bazel_bin" ]; then - pass "bazel info bazel-bin: $bazel_bin" - else - fail "Could not determine bazel-bin location" - fi - - # Check if bazel-bin exists - if [ -d "$bazel_bin" ]; then - pass "bazel-bin directory exists" - else - warn "bazel-bin directory not yet created (run bazel build)" - fi - fi - fi - - # Section 9: Build Test (Optional) - header "9. Build Test (Optional)" - - if command -v bazel &> /dev/null; then - info "Testing Bazel query..." - if bazel query //:rbe-server &> /dev/null; then - pass "Target //:rbe-server exists" - else - warn "Target //:rbe-server not found in BUILD files" - fi - - # Check if already built - local server_output=$(find_bazel_output "//:rbe-server" "$PROJECT_ROOT" 2>/dev/null || true) - if [ -n "$server_output" ] && [ -f "$server_output" ]; then - pass "rbe-server already built: $server_output" - else - info "rbe-server not yet built (run ./scripts/build-with-bazel.sh)" - fi - fi - - # Section 10: GitHub Actions (if applicable) - header "10. CI/CD Configuration" - - if [ -f "$PROJECT_ROOT/.github/workflows/benchmark.yml" ]; then - pass "GitHub Actions workflow: benchmark.yml" - else - warn "GitHub Actions workflow not found" - fi - - # Summary - header "Summary" - - echo "Passed: $PASS" - echo "Warnings: $WARN" - echo "Failed: $FAIL" - echo "" - - if [ $FAIL -eq 0 ]; then - echo -e "${GREEN}✅ Setup looks good!${NC}" - echo "" - echo "Next steps:" - echo " 1. Build FerrisRBE: ./scripts/build-with-bazel.sh all" - echo " 2. Run local test: ./scripts/benchmark-local.sh light" - echo " 3. Run benchmarks: ./scripts/benchmark.sh" - echo " 4. Run CI tests: ./scripts/benchmark-ci.sh light" - exit 0 - else - echo -e "${RED}❌ Setup has issues. Please fix the failures above.${NC}" - exit 1 - fi -} - -# Run with optional flags -case "${1:-}" in - --quick) - # Quick mode - skip build tests - info "Running in quick mode (skipping build tests)" - main | grep -E "(✓|✗|⚠|ℹ|===)" - ;; - --help|-h) - echo "Usage: $0 [--quick|--help]" - echo "" - echo "Options:" - echo " --quick Skip build tests, faster check" - echo " --help Show this help" - echo "" - echo "Verifies that the benchmark suite is properly configured." - exit 0 - ;; - *) - main - ;; -esac