diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3496c20..8b274f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,13 @@ jobs: with: {go-version: "1.25.x", cache: true} - run: test -z "$(gofmt -l cmd internal sdk/go gen tests/integration)" - run: go vet ./... - - run: go test -race -coverprofile=coverage.out ./cmd/... ./internal/... ./sdk/go/... + - run: go test -race -covermode=atomic -coverprofile=coverage.out ./cmd/... ./internal/... ./sdk/go/... + - run: scripts/check-go-coverage.sh coverage.out 10 + - run: go test -coverprofile=workflow-coverage.out ./internal/workflow && scripts/check-go-coverage.sh workflow-coverage.out 70 + - run: go test -coverprofile=auth-coverage.out ./internal/auth && scripts/check-go-coverage.sh auth-coverage.out 40 + - run: go test -coverprofile=live-coverage.out ./internal/live && scripts/check-go-coverage.sh live-coverage.out 70 + - uses: actions/upload-artifact@v4 + with: {name: go-coverage, path: "*-coverage.out"} python: runs-on: ubuntu-latest steps: @@ -22,6 +28,8 @@ jobs: - run: pip install -e 'sdk/python[dev]' - run: ruff check sdk/python examples - run: cd sdk/python && mypy runmesh && pytest --junitxml=../../pytest.xml + - uses: actions/upload-artifact@v4 + with: {name: python-coverage, path: sdk/python/coverage.xml} web: runs-on: ubuntu-latest defaults: {run: {working-directory: web}} @@ -32,8 +40,10 @@ jobs: - run: npm ci - run: npm run lint - run: npm run build - - run: npm test -- --run + - run: npm run test:coverage - run: npm audit --audit-level=high + - uses: actions/upload-artifact@v4 + with: {name: web-coverage, path: web/coverage/coverage-summary.json} protobuf: runs-on: ubuntu-latest steps: @@ -68,16 +78,29 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: {go-version: "1.25.x", cache: true} - - run: go test -tags=integration -count=1 -timeout=10m ./tests/integration + - run: go test -tags=integration -count=1 -timeout=15m ./tests/integration containers: runs-on: ubuntu-latest needs: [go, python, web, protobuf, integration] - strategy: {matrix: {service: [control-plane, scheduler, worker-go]}} + strategy: + fail-fast: false + matrix: + include: + - {service: control-plane, dockerfile: Dockerfile, build-args: "SERVICE=control-plane"} + - {service: scheduler, dockerfile: Dockerfile, build-args: "SERVICE=scheduler"} + - {service: worker-go, dockerfile: Dockerfile, build-args: "SERVICE=worker-go"} + - {service: worker-python, dockerfile: sdk/python/Dockerfile, build-args: ""} + - {service: web, dockerfile: web/Dockerfile, build-args: "WEB_DEVELOPMENT_MODE=false"} steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/build-push-action@v6 - with: {context: ., build-args: "SERVICE=${{ matrix.service }}", tags: "runmesh/${{ matrix.service }}:${{ github.sha }}", load: true} + with: + context: . + file: ${{ matrix.dockerfile }} + build-args: ${{ matrix.build-args }} + tags: runmesh/${{ matrix.service }}:${{ github.sha }} + load: true - uses: aquasecurity/trivy-action@v0.36.0 with: {image-ref: "runmesh/${{ matrix.service }}:${{ github.sha }}", severity: CRITICAL, exit-code: "1", ignore-unfixed: true} kind-upgrade: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3005e01..99b4d24 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: build-args: "" - component: web dockerfile: web/Dockerfile - build-args: VITE_DEV_AUTH=false + build-args: WEB_DEVELOPMENT_MODE=false steps: - uses: actions/checkout@v6 - uses: docker/setup-qemu-action@v4 diff --git a/.gitignore b/.gitignore index 38743ef..bc75d6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,17 @@ .env +.env.* +!.env.example +.secrets/ +*.pem +*.key +*.p12 +credentials.json .DS_Store bin/ coverage/ +.coverage +coverage.xml +htmlcov/ dist/ node_modules/ __pycache__/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2f08b8b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +All notable changes are documented here. This project follows Semantic Versioning. + +## [0.1.0] - Unreleased + +### Added + +- Multi-tenant workflow and DAG lifecycle APIs with OIDC and scoped API-key authentication. +- Go and Python workers using Kafka delivery, expiring leases, fenced mutations, retries, cancellation, and dead-letter replay. +- S3-compatible input, output, and log artifacts with presigned transfer URLs. +- Per-tenant Redis rate limiting and live WebSocket notifications with a polling fallback. +- Prometheus metrics, OpenTelemetry traces, structured logs, and six Grafana dashboards. +- Docker Compose, Helm, validated AWS Terraform, browser tests, chaos tests, and reproducible local benchmark tooling. + +### Reliability + +- Transactional outbox publication now claims work in short PostgreSQL transactions and publishes outside row locks. +- Workflow-run Kafka keys preserve ordering across multiple partitions and concurrent publishers. +- Worker operations verify tenant ownership, worker identity, active state, and unexpired leases. +- Fixed Kafka/Redpanda topic initialization on a completely fresh stack, where `rpk topic describe` could report success before the topic actually existed. +- Fixed heterogeneous Go/Python worker capability routing on a shared consumer group. + +### Known limitations + +- Benchmark evidence is local and does not establish production capacity. +- The Terraform configuration is validated with mock providers but has not been applied to AWS. +- Kafka is externally supplied; the Terraform configuration does not create MSK. +- At-least-once execution cannot make arbitrary handler side effects exactly-once. +- `v0.1.0` images and release artifacts have not been published. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..db8568e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,120 @@ +# Contributing to RunMesh + +Thank you for helping improve RunMesh. Changes should preserve tenant isolation, +at-least-once delivery, per-workflow ordering, and backward-compatible protobuf +evolution. + +## Local setup + +The shortest path to a complete development environment is Docker Engine or +Docker Desktop with Docker Compose v2: + +```bash +git clone https://github.com/samarth1412/RunMesh.git +cd RunMesh +docker compose up --build -d +docker compose ps +bash tests/e2e/smoke.sh +``` + +For development outside containers, install Go 1.25, Python 3.11 or newer, and +Node.js 22. Then install the Python and web dependencies: + +```bash +python3 -m pip install -e 'sdk/python[dev]' +npm --prefix web ci +``` + +## Testing + +Run focused tests while developing and the relevant full suite before opening a +pull request: + +```bash +gofmt -w cmd internal sdk/go gen tests/integration +go vet ./... +go test -race ./cmd/... ./internal/... ./sdk/go/... +go test -coverprofile=workflow-coverage.out ./internal/workflow +scripts/check-go-coverage.sh workflow-coverage.out 70 + +ruff check sdk/python examples +(cd sdk/python && mypy runmesh && pytest) # includes a 40% worker-SDK floor + +npm --prefix web run lint +npm --prefix web run build +npm --prefix web run test:coverage + +buf lint +go test -tags=integration -count=1 -timeout=20m ./tests/integration +``` + +Integration tests use Docker through Testcontainers. The browser suite expects +the Compose stack: + +```bash +docker compose up -d --build +npm --prefix web run test:e2e +``` + +CI additionally checks protobuf compatibility, container vulnerabilities, +Terraform, Helm, rendered Kubernetes resources, the Compose smoke path, and a +kind rolling upgrade. + +## Protobuf changes + +Only make additive, backward-compatible changes to `proto/`. Regenerate both Go +and Python clients and commit them in the same pull request: + +```bash +buf generate +buf lint +buf breaking --against '.git#branch=origin/main' +``` + +If `buf` is not installed locally, use the pinned container: + +```bash +docker run --rm -v "$PWD:/workspace" -w /workspace \ + bufbuild/buf:1.47.2 generate +``` + +## Database migrations + +Schema changes require paired, reversible migrations. Create the next numbered +`.up.sql` and `.down.sql` files under `migrations/`, use idempotent DDL where it +is safe, and test both directions against disposable data. Apply pending local +migrations with: + +```bash +make migrate +``` + +Never edit a migration that may already have been applied; add a new migration. + +## Benchmarks and failure tests + +Performance claims require raw, machine-readable evidence tied to a source +commit. Start from a disposable Compose environment and run: + +```bash +tests/benchmarks/run-local.sh tests/benchmarks/results/YYYY-MM-DDTHHMMSSZ +``` + +The benchmark driver starts the local stack, stops schedulers, scales and terminates workers, and writes +into a new result directory. It refuses to overwrite existing evidence. Do not point it at shared or production +infrastructure. Do not hand-edit raw output. Record failed runs honestly, and +distinguish API throughput, scheduling throughput, and end-to-end completion +throughput. See [`docs/benchmarks.md`](docs/benchmarks.md). + +## Pull requests + +- Keep changes focused and explain the behavior or failure mode being changed. +- Add tests for new state transitions, authorization boundaries, and recovery + behavior. +- Update OpenAPI, protobuf clients, deployment configuration, and documentation + with the behavior they describe. +- Include exact validation commands and results. Do not describe a partial test + run as a full validation. +- Do not include secrets, local environment files, generated credentials, or + unreviewed benchmark output. +- Preserve existing commit attribution and do not rewrite shared history. diff --git a/Dockerfile b/Dockerfile index 0d88768..3dc5803 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM golang:1.25-alpine AS build WORKDIR /src RUN apk add --no-cache ca-certificates git -COPY go.mod ./ +COPY go.mod go.sum ./ RUN go mod download COPY . . ARG SERVICE=control-plane diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..edb8674 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Samarth Vinayaka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 475a635..c54e522 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,18 @@ RunMesh stores versioned DAGs, schedules dependency-aware tasks, and delivers work with at-least-once semantics. PostgreSQL is the source of truth, a transactional outbox bridges state to Kafka/Redpanda, and expiring leases let work recover safely after worker or infrastructure failures. +| See it | Run it | Verify it | +| --- | --- | --- | +| [Dashboard demo](#demo) | `docker compose up --build -d` | [Raw benchmark evidence](tests/benchmarks/results/) | + It ships as a complete local platform: Go control plane and scheduler, Go and Python workers, a React operations dashboard, OIDC authentication, tenant-scoped API keys, S3-compatible artifacts, live updates, metrics, logs, traces, Helm charts, and validated AWS Terraform. +Latest local evidence on an Apple M4 / 16 GiB machine: **407.403 scheduler +dispatches/second across 10,000 tasks**, **100.0 authenticated reads/second +at 3.307 ms p95**, and **10,000/10,000 tasks completed with zero permanent +loss after all 20 workers were terminated**. These are reproducible local +Docker results, not production-capacity claims. + ## Highlights - **Durable execution** — transactional run creation, dependency-aware scheduling, fenced state transitions, retries, cancellation, and dead-letter replay. @@ -49,6 +59,18 @@ PostgreSQL remains authoritative throughout the lifecycle. Kafka delivery can be Read the deeper [architecture](docs/architecture.md), [delivery semantics](docs/delivery-semantics.md), and [failure-mode analysis](docs/failure-modes.md). +## Demo + +The dashboard is exercised against the real Compose stack in Playwright; it does not use a mock API. The [one-minute recovery video](docs/demo/runmesh-recovery.webm) creates and runs a DAG, terminates both bundled workers during an active lease, and shows the successful recovered attempt. + +| Operations overview | Completed dependency graph | +| --- | --- | +| ![RunMesh operations overview](docs/demo/dashboard-overview.png) | ![RunMesh completed run detail](docs/demo/run-detail.png) | + +![RunMesh lease recovery with immutable attempt history](docs/demo/lease-recovery.png) + +The exact capture procedure is documented with the media in [`docs/demo`](docs/demo/README.md); no mock API or generated screenshot is used. + ## Quick start You need Docker Engine/Desktop with Docker Compose v2, `curl`, and Python 3. @@ -137,6 +159,15 @@ worker.run() See the complete [Python worker example](examples/python_worker.py) and the [artifact guide](docs/artifacts.md). +The canonical Go SDK import is: + +```go +import runmesh "github.com/samarth1412/RunMesh/sdk/go" + +client := runmesh.NewClient("localhost:7001", "rm__", "worker-1") +defer client.Close() +``` + ## Reliability model RunMesh deliberately promises **at-least-once**, not exactly-once, execution. Task handlers that perform side effects should deduplicate using the stable `context.idempotency_key`. @@ -169,13 +200,14 @@ The repository includes unit, race, integration, browser, security, chaos, deplo | Scenario | Recorded result | | --- | ---: | -| Authenticated API load | 100.017 req/s, 4.316 ms p95, 0 failures | -| Simultaneously active workflows | 1,000 | -| Scheduler dispatch | 196.078 tasks/s across 10,000 tasks | -| Worker termination and recovery | 10,000/10,000 succeeded, 0 lost | +| Authenticated API reads | 100.0 req/s; 1.701/3.307/4.657 ms p50/p95/p99; 0 failures | +| Workflow submissions | 50.028 req/s; 1.587/7.012/220.024 ms p50/p95/p99; 0 failures | +| Simultaneously active workflows | 1,000 created; 0 failures; 57.608 tasks/s drain | +| Scheduler dispatch | 407.403 tasks/s across 10,000 tasks | +| Worker termination and recovery | 20 workers terminated; 10,000/10,000 succeeded; 7 recovered attempts; 0 lost | | Duplicate submission and delivery | Passed | -These numbers are transparent local evidence, not a universal capacity claim. Hardware details, commands, image digests, source SHA, and raw machine-readable output are committed in the [2026-07-20 benchmark results](tests/benchmarks/results/2026-07-20/README.md). +These numbers are transparent local evidence, not a universal capacity claim. Hardware details, commands, image digests, source SHA, and raw machine-readable output are committed in the [2026-07-21T003600Z benchmark results](tests/benchmarks/results/2026-07-21T003600Z/README.md), the canonical run against the current `HEAD`. The complete [local validation ledger](docs/validation.md) separates executed checks from remote or cloud work that remains unverified. ## Development @@ -205,6 +237,22 @@ make lint # format/check Go, Python, and TypeScript CI also validates protobuf compatibility, container vulnerability scans, Terraform, Helm, rendered Kubernetes manifests, Playwright flows, and a live kind rolling upgrade. +Coverage gates intentionally target risk rather than 100%: the workflow state machine, authentication, live event broker, Python worker, and dashboard each have documented minimums in CI. Docker-backed integration tests cover PostgreSQL scheduler and storage behavior. + +## Limitations and future work + +- Published performance evidence is from one local Docker Desktop machine, not a production capacity guarantee. +- The AWS Terraform is formatted, validated, and tested with mock providers; it has not been applied and no AWS deployment is claimed. +- Useful worker parallelism is bounded by Kafka partition count, and a single workflow run remains intentionally confined to one partition. +- Workers in the same Kafka consumer group must expose the same handler set; heterogeneous capability pools require separate group IDs. +- The outbox removes broker I/O from database transactions, but PostgreSQL claim and acknowledgement writes still bound dispatch throughput. +- The recorded 10,000-task crash run recovered all interrupted leases, but the + recovered-attempt delay was 132.797 seconds p50 under backlog; prioritizing + expired leases is future work. +- At-least-once execution cannot make arbitrary handler side effects exactly-once. Handlers must use the stable task idempotency key. +- Redis Pub/Sub notifications are best-effort; the dashboard re-reads durable state and falls back to polling. +- The repository prepares `v0.1.0`, but no release or versioned image is published until a maintainer explicitly creates the tag. + ## Deployment The Helm chart deploys independently scalable control-plane, scheduler, dashboard, and worker pools with probes, resource limits, autoscaling, disruption budgets, restricted security contexts, and default-deny NetworkPolicies. @@ -236,4 +284,7 @@ Pushing a `v*` tag builds five multi-architecture GHCR images with semantic-vers - [Security model](docs/security.md) - [Artifact handling](docs/artifacts.md) - [Benchmarks](docs/benchmarks.md) +- [Local validation report](docs/validation.md) - [Deployment and releases](docs/deployment.md) +- [Architecture decisions](docs/adr/) +- [Draft v0.1.0 release notes](docs/release-v0.1.0.md) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1d7e4ad --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,45 @@ +# Security policy + +## Supported versions + +RunMesh has not published a stable release. Security fixes are currently made +on the `main` branch. A version support table will be added when the first +release is published. + +## Reporting a vulnerability + +Please do not disclose a suspected vulnerability in a public issue, discussion, +or pull request. + +GitHub private vulnerability reporting is not currently enabled for this +repository. To request a private reporting channel, open a minimal issue that +contains only a request to contact the maintainer and no vulnerability details. +The maintainer will arrange a private channel and acknowledge the report. This +file will be updated with a direct private-reporting link when that repository +feature is enabled. + +Include the affected component and version or commit, reproduction conditions, +security impact, and any suggested mitigation in the private report. Please +allow reasonable time for investigation and remediation before disclosure. + +## Local development credentials + +The credentials committed in `compose.yaml`, `deploy/docker/local.env`, the +Keycloak realm fixture, examples, and test fixtures are intentionally public, +development-only values. They secure nothing outside the disposable local +Compose environment and must never be reused in a shared or production system. + +Production mode rejects development authentication. Deployments must provide +unique secrets outside the repository for the database, Redis, S3, worker API +keys, and the API-key pepper, and must configure a trusted OIDC issuer and +audience. See [`docs/security.md`](docs/security.md) and +[`docs/deployment.md`](docs/deployment.md). + +## Scope + +Useful reports include authentication or authorization bypasses, cross-tenant +data access, worker lease-fencing failures, secret exposure, unsafe artifact +access, injection, denial-of-service paths, and vulnerable deployment defaults. +Reports about the documented public local-development credentials are not +vulnerabilities unless those credentials are accepted when development +authentication is disabled. diff --git a/buf.yaml b/buf.yaml index 614f921..09069f0 100644 --- a/buf.yaml +++ b/buf.yaml @@ -2,4 +2,8 @@ version: v2 modules: - path: proto lint: {use: [STANDARD]} -breaking: {use: [FILE]} +# FILE_SAME_GO_PACKAGE is excepted for the one-time go_package rename to the +# canonical module path; there is no released version or external consumer yet. +breaking: + use: [FILE] + except: [FILE_SAME_GO_PACKAGE] diff --git a/cmd/control-plane/main.go b/cmd/control-plane/main.go index 68e4a6b..4b11ac2 100644 --- a/cmd/control-plane/main.go +++ b/cmd/control-plane/main.go @@ -11,16 +11,16 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" - runmeshv1 "github.com/runmesh/runmesh/gen/runmesh/v1" - "github.com/runmesh/runmesh/internal/api" - "github.com/runmesh/runmesh/internal/artifact" - "github.com/runmesh/runmesh/internal/auth" - "github.com/runmesh/runmesh/internal/config" - "github.com/runmesh/runmesh/internal/live" - "github.com/runmesh/runmesh/internal/ratelimit" - workerrpc "github.com/runmesh/runmesh/internal/rpc" - "github.com/runmesh/runmesh/internal/storage" - "github.com/runmesh/runmesh/internal/telemetry" + runmeshv1 "github.com/samarth1412/RunMesh/gen/runmesh/v1" + "github.com/samarth1412/RunMesh/internal/api" + "github.com/samarth1412/RunMesh/internal/artifact" + "github.com/samarth1412/RunMesh/internal/auth" + "github.com/samarth1412/RunMesh/internal/config" + "github.com/samarth1412/RunMesh/internal/live" + "github.com/samarth1412/RunMesh/internal/ratelimit" + workerrpc "github.com/samarth1412/RunMesh/internal/rpc" + "github.com/samarth1412/RunMesh/internal/storage" + "github.com/samarth1412/RunMesh/internal/telemetry" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "google.golang.org/grpc" diff --git a/cmd/scheduler/main.go b/cmd/scheduler/main.go index 545bf8e..665557f 100644 --- a/cmd/scheduler/main.go +++ b/cmd/scheduler/main.go @@ -12,12 +12,12 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/runmesh/runmesh/internal/config" - "github.com/runmesh/runmesh/internal/live" - "github.com/runmesh/runmesh/internal/messaging" - "github.com/runmesh/runmesh/internal/scheduler" - "github.com/runmesh/runmesh/internal/storage" - "github.com/runmesh/runmesh/internal/telemetry" + "github.com/samarth1412/RunMesh/internal/config" + "github.com/samarth1412/RunMesh/internal/live" + "github.com/samarth1412/RunMesh/internal/messaging" + "github.com/samarth1412/RunMesh/internal/scheduler" + "github.com/samarth1412/RunMesh/internal/storage" + "github.com/samarth1412/RunMesh/internal/telemetry" ) func main() { @@ -51,7 +51,7 @@ func main() { defer liveBroker.Close() published := prometheus.NewCounterVec(prometheus.CounterOpts{Name: "scheduler_outbox_published_total", Help: "Outbox events published to Kafka by tenant and event type."}, []string{"tenant_id", "event_type"}) prometheus.MustRegister(published) - service := scheduler.Service{Store: store, Publisher: publisher, Live: liveBroker, Published: published, ScheduleInterval: cfg.SchedulerInterval, OutboxInterval: cfg.OutboxInterval} + service := scheduler.Service{Store: store, Publisher: publisher, Live: liveBroker, Published: published, ScheduleInterval: cfg.SchedulerInterval, OutboxInterval: cfg.OutboxInterval, OutboxBatchSize: cfg.OutboxBatchSize, OutboxClaimTTL: cfg.OutboxClaimTTL, OutboxPublishTimeout: cfg.OutboxPublishTimeout} metricsServer := &http.Server{Addr: cfg.SchedulerMetricsAddr, Handler: promhttp.Handler(), ReadHeaderTimeout: 3 * time.Second} go func() { if serveErr := metricsServer.ListenAndServe(); serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) { diff --git a/cmd/worker-go/main.go b/cmd/worker-go/main.go index 1db74d9..53522b7 100644 --- a/cmd/worker-go/main.go +++ b/cmd/worker-go/main.go @@ -12,9 +12,9 @@ import ( "syscall" "time" - "github.com/runmesh/runmesh/internal/telemetry" - "github.com/runmesh/runmesh/internal/tracecontext" - runmesh "github.com/runmesh/runmesh/sdk/go" + "github.com/samarth1412/RunMesh/internal/telemetry" + "github.com/samarth1412/RunMesh/internal/tracecontext" + runmesh "github.com/samarth1412/RunMesh/sdk/go" "github.com/segmentio/kafka-go" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -43,7 +43,7 @@ func main() { workerID := env("RUNMESH_WORKER_ID", defaultWorkerID()) client := runmesh.NewClient(env("RUNMESH_ENDPOINT", "localhost:7001"), env("RUNMESH_INTERNAL_TOKEN", "local-development-token"), workerID) defer client.Close() - reader := kafka.NewReader(kafka.ReaderConfig{Brokers: brokers, Topic: env("RUNMESH_KAFKA_TOPIC", "runmesh.tasks"), GroupID: "runmesh-workers", MinBytes: 1, MaxBytes: 10e6}) + reader := kafka.NewReader(kafka.ReaderConfig{Brokers: brokers, Topic: env("RUNMESH_KAFKA_TOPIC", "runmesh.tasks"), GroupID: env("RUNMESH_KAFKA_GROUP_ID", "runmesh-workers"), MinBytes: 1, MaxBytes: 10e6}) defer reader.Close() go report(ctx, client) slog.Info("go worker started", "worker_id", workerID) @@ -66,6 +66,12 @@ func main() { _ = reader.CommitMessages(ctx, message) continue } + if !supported(d.Handler) { + if err = reader.CommitMessages(ctx, message); err != nil { + slog.Error("commit unsupported dispatch", "handler", d.Handler, "error", err) + } + continue + } taskContext := tracecontext.IntoContext(ctx, header(message.Headers, "traceparent")) if err = execute(taskContext, client, d.TaskRunID); err != nil { slog.Error("execute task", "task_run_id", d.TaskRunID, "error", err) @@ -75,6 +81,15 @@ func main() { } } } + +func supported(handler string) bool { + switch handler { + case "examples.greet", "examples.upper", "examples.notify", "examples.slow", "validation.dead": + return true + default: + return false + } +} func execute(parent context.Context, client *runmesh.Client, id string) error { parent, span := otel.Tracer("runmesh/worker").Start(parent, "task.execute", trace.WithAttributes(attribute.String("task_run_id", id))) defer span.End() @@ -120,6 +135,8 @@ func execute(parent context.Context, client *runmesh.Client, id string) error { output = map[string]any{"message": fmt.Sprintf("Hello, %v!", input["name"]), "worker": "go"} case "examples.upper": output = map[string]any{"text": strings.ToUpper(fmt.Sprint(input["text"])), "worker": "go"} + case "examples.notify": + output = map[string]any{"delivered": true, "idempotency_key": id, "worker": "go"} case "examples.slow": delay := 200 * time.Millisecond if value, ok := input["delay_ms"].(float64); ok && value >= 0 && value <= 30_000 { @@ -131,6 +148,12 @@ func execute(parent context.Context, client *runmesh.Client, id string) error { case <-ctx.Done(): return ctx.Err() } + case "validation.dead": + err = errors.New("intentional permanent failure for validation") + _, _ = client.Fail(parent, id, false, err) + cancel() + <-heartbeatsDone + return err default: err = fmt.Errorf("unsupported handler %q", task.Handler) _, _ = client.Fail(parent, id, true, err) @@ -147,7 +170,7 @@ func report(ctx context.Context, client *runmesh.Client) { ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() for { - if err := client.Report(ctx, []string{"examples.greet", "examples.upper", "examples.slow"}, 0); err != nil { + if err := client.Report(ctx, []string{"examples.greet", "examples.upper", "examples.notify", "examples.slow", "validation.dead"}, 0); err != nil { slog.Warn("worker heartbeat failed", "error", err) } select { diff --git a/cmd/worker-go/main_test.go b/cmd/worker-go/main_test.go new file mode 100644 index 0000000..e661c03 --- /dev/null +++ b/cmd/worker-go/main_test.go @@ -0,0 +1,14 @@ +package main + +import "testing" + +func TestSupportedHandlers(t *testing.T) { + for _, handler := range []string{"examples.greet", "examples.upper", "examples.notify", "examples.slow", "validation.dead"} { + if !supported(handler) { + t.Errorf("supported(%q)=false", handler) + } + } + if supported("documents.unknown") { + t.Fatal("unknown handler must not be claimed by this worker pool") + } +} diff --git a/compose.yaml b/compose.yaml index 8ad4ea1..780bd56 100644 --- a/compose.yaml +++ b/compose.yaml @@ -29,6 +29,22 @@ services: ports: ["8081:8080"] depends_on: {redpanda: {condition: service_healthy}} + redpanda-init: + image: redpandadata/redpanda:v24.3.15 + entrypoint: ["/bin/sh", "-ec"] + environment: {RUNMESH_KAFKA_PARTITIONS: "6"} + command: + - | + current="$$(rpk topic list -X brokers=redpanda:9092 | awk '$$1 == "runmesh.tasks" {print $$2}')" + if [ -n "$$current" ]; then + if [ "$$current" -lt "$$RUNMESH_KAFKA_PARTITIONS" ]; then + rpk topic add-partitions runmesh.tasks --num "$$((RUNMESH_KAFKA_PARTITIONS-current))" -X brokers=redpanda:9092 + fi + else + rpk topic create runmesh.tasks --partitions "$$RUNMESH_KAFKA_PARTITIONS" --replicas 1 -X brokers=redpanda:9092 + fi + depends_on: {redpanda: {condition: service_healthy}} + redis: image: redis:7.4-alpine command: ["redis-server", "--appendonly", "yes"] @@ -72,8 +88,9 @@ services: env_file: [deploy/docker/local.env] depends_on: migrate: {condition: service_completed_successfully} - redpanda: {condition: service_healthy} + redpanda-init: {condition: service_completed_successfully} redis: {condition: service_healthy} + deploy: {replicas: 2} restart: unless-stopped worker-python: @@ -83,7 +100,8 @@ services: RUNMESH_HTTP_ENDPOINT: control-plane:8080 RUNMESH_INTERNAL_TOKEN: rm_00000000-0000-0000-0000-000000000002_local-development-worker-key RUNMESH_KAFKA_BROKERS: redpanda:9092 - depends_on: {control-plane: {condition: service_started}, redpanda: {condition: service_healthy}} + RUNMESH_KAFKA_GROUP_ID: runmesh-workers + depends_on: {control-plane: {condition: service_started}, redpanda-init: {condition: service_completed_successfully}} restart: unless-stopped worker-go: @@ -92,14 +110,15 @@ services: RUNMESH_ENDPOINT: control-plane:7001 RUNMESH_INTERNAL_TOKEN: rm_00000000-0000-0000-0000-000000000002_local-development-worker-key RUNMESH_KAFKA_BROKERS: redpanda:9092 - depends_on: {control-plane: {condition: service_started}, redpanda: {condition: service_healthy}} + RUNMESH_KAFKA_GROUP_ID: runmesh-workers + depends_on: {control-plane: {condition: service_started}, redpanda-init: {condition: service_completed_successfully}} restart: unless-stopped web: build: context: . dockerfile: web/Dockerfile - args: {VITE_DEV_AUTH: "false", VITE_OIDC_AUTHORITY: "http://localhost:8180/realms/runmesh", VITE_OIDC_CLIENT_ID: "runmesh-web"} + args: {WEB_DEVELOPMENT_MODE: "false", VITE_OIDC_AUTHORITY: "http://localhost:8180/realms/runmesh", VITE_OIDC_CLIENT_ID: "runmesh-web"} ports: ["3000:3000"] depends_on: [control-plane] restart: unless-stopped diff --git a/deploy/docker/local.env b/deploy/docker/local.env index ed47577..06b7d6f 100644 --- a/deploy/docker/local.env +++ b/deploy/docker/local.env @@ -8,6 +8,9 @@ RUNMESH_INTERNAL_TOKEN=local-development-token RUNMESH_LEASE_DURATION=30s RUNMESH_SCHEDULER_INTERVAL=500ms RUNMESH_OUTBOX_INTERVAL=250ms +RUNMESH_OUTBOX_BATCH_SIZE=100 +RUNMESH_OUTBOX_CLAIM_TTL=30s +RUNMESH_OUTBOX_PUBLISH_TIMEOUT=10s RUNMESH_DEV_AUTH=false RUNMESH_DEV_TENANT_ID=00000000-0000-0000-0000-000000000001 RUNMESH_DEV_USER_ID=00000000-0000-0000-0000-000000000001 diff --git a/deploy/helm/runmesh/Chart.yaml b/deploy/helm/runmesh/Chart.yaml index 31c3ecf..e3030b5 100644 --- a/deploy/helm/runmesh/Chart.yaml +++ b/deploy/helm/runmesh/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: runmesh description: Failure-tolerant workflow orchestration control plane type: application -version: 1.0.0 -appVersion: "1.0.0" +version: 0.1.0 +appVersion: "0.1.0" diff --git a/deploy/helm/runmesh/templates/config.yaml b/deploy/helm/runmesh/templates/config.yaml index 7fa3eb4..c5968cf 100644 --- a/deploy/helm/runmesh/templates/config.yaml +++ b/deploy/helm/runmesh/templates/config.yaml @@ -12,6 +12,9 @@ data: RUNMESH_HTTP_ENDPOINT: "runmesh-control-plane:8080" RUNMESH_KAFKA_BROKERS: {{ required "config.kafkaBrokers is required; Kafka is externally supplied" .Values.config.kafkaBrokers | quote }} RUNMESH_KAFKA_TOPIC: {{ .Values.config.kafkaTopic | quote }} + RUNMESH_OUTBOX_BATCH_SIZE: {{ .Values.config.outboxBatchSize | quote }} + RUNMESH_OUTBOX_CLAIM_TTL: {{ .Values.config.outboxClaimTTL | quote }} + RUNMESH_OUTBOX_PUBLISH_TIMEOUT: {{ .Values.config.outboxPublishTimeout | quote }} RUNMESH_DEV_AUTH: {{ .Values.config.devAuth | quote }} RUNMESH_OIDC_ISSUER: {{ .Values.config.oidcIssuer | quote }} RUNMESH_OIDC_AUDIENCE: {{ .Values.config.oidcAudience | quote }} diff --git a/deploy/helm/runmesh/templates/deployments.yaml b/deploy/helm/runmesh/templates/deployments.yaml index 0bcd8ae..1a953eb 100644 --- a/deploy/helm/runmesh/templates/deployments.yaml +++ b/deploy/helm/runmesh/templates/deployments.yaml @@ -148,6 +148,8 @@ spec: {{- toYaml $.Values.containerSecurityContext | nindent 12 }} envFrom: [{configMapRef: {name: {{ include "runmesh.name" $ }}}}] env: + - name: RUNMESH_KAFKA_GROUP_ID + value: {{ $pool.groupID | quote }} - name: RUNMESH_INTERNAL_TOKEN valueFrom: secretKeyRef: diff --git a/deploy/helm/runmesh/values.schema.json b/deploy/helm/runmesh/values.schema.json index 73e017b..132d310 100644 --- a/deploy/helm/runmesh/values.schema.json +++ b/deploy/helm/runmesh/values.schema.json @@ -15,6 +15,9 @@ "webApiURL": {"type": "string"}, "kafkaBrokers": {"type": "string", "minLength": 1}, "kafkaTopic": {"type": "string", "minLength": 1}, + "outboxBatchSize": {"type": "integer", "minimum": 1}, + "outboxClaimTTL": {"type": "string", "pattern": "^[0-9]+(ms|s|m)$"}, + "outboxPublishTimeout": {"type": "string", "pattern": "^[0-9]+(ms|s|m)$"}, "rateLimitRate": {"type": "integer", "minimum": 1}, "rateLimitBurst": {"type": "integer", "minimum": 1}, "artifactPathStyle": {"type": "boolean"}, @@ -34,11 +37,12 @@ "type": "array", "items": { "type": "object", - "required": ["name", "enabled", "type", "replicas", "credentialSecretKey", "resources", "autoscaling"], + "required": ["name", "enabled", "type", "groupID", "replicas", "credentialSecretKey", "resources", "autoscaling"], "properties": { "name": {"type": "string", "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"}, "enabled": {"type": "boolean"}, "type": {"type": "string", "enum": ["go", "python"]}, + "groupID": {"type": "string", "minLength": 1}, "replicas": {"type": "integer", "minimum": 0} } } diff --git a/deploy/helm/runmesh/values.yaml b/deploy/helm/runmesh/values.yaml index 6572cd8..1ba6ebb 100644 --- a/deploy/helm/runmesh/values.yaml +++ b/deploy/helm/runmesh/values.yaml @@ -1,9 +1,11 @@ images: - controlPlane: {repository: ghcr.io/example/runmesh-control-plane, tag: latest, pullPolicy: IfNotPresent} - scheduler: {repository: ghcr.io/example/runmesh-scheduler, tag: latest, pullPolicy: IfNotPresent} - web: {repository: ghcr.io/example/runmesh-web, tag: latest, pullPolicy: IfNotPresent} - workerGo: {repository: ghcr.io/example/runmesh-worker-go, tag: latest, pullPolicy: IfNotPresent} - workerPython: {repository: ghcr.io/example/runmesh-worker-python, tag: latest, pullPolicy: IfNotPresent} + # v0.1.0 is prepared but not published. Override these tags until the release + # workflow has published the corresponding immutable images. + controlPlane: {repository: ghcr.io/samarth1412/runmesh-control-plane, tag: 0.1.0, pullPolicy: IfNotPresent} + scheduler: {repository: ghcr.io/samarth1412/runmesh-scheduler, tag: 0.1.0, pullPolicy: IfNotPresent} + web: {repository: ghcr.io/samarth1412/runmesh-web, tag: 0.1.0, pullPolicy: IfNotPresent} + workerGo: {repository: ghcr.io/samarth1412/runmesh-worker-go, tag: 0.1.0, pullPolicy: IfNotPresent} + workerPython: {repository: ghcr.io/samarth1412/runmesh-worker-python, tag: 0.1.0, pullPolicy: IfNotPresent} imagePullSecrets: [] replicas: {controlPlane: 2, scheduler: 2, web: 2} @@ -12,6 +14,9 @@ config: devAuth: false kafkaBrokers: "kafka.example.internal:9092" kafkaTopic: runmesh.tasks + outboxBatchSize: 100 + outboxClaimTTL: 30s + outboxPublishTimeout: 10s oidcIssuer: "https://identity.example.com/realms/runmesh" oidcAudience: runmesh-web oidcJwksURL: "https://identity.example.com/realms/runmesh/protocol/openid-connect/certs" @@ -49,6 +54,7 @@ workerPools: - name: default-go enabled: true type: go + groupID: runmesh-workers replicas: 2 image: {repository: "", tag: "", pullPolicy: ""} credentialSecretName: "" diff --git a/docs/adr/0001-postgresql-source-of-truth.md b/docs/adr/0001-postgresql-source-of-truth.md new file mode 100644 index 0000000..9bdf357 --- /dev/null +++ b/docs/adr/0001-postgresql-source-of-truth.md @@ -0,0 +1,15 @@ +# ADR 0001: PostgreSQL is the execution source of truth + +Status: Accepted + +## Context + +Workflow state must remain queryable and recoverable when brokers, workers, or optional services are unavailable. + +## Decision + +Persist definitions, runs, tasks, attempts, leases, dependencies, and outbox events transactionally in PostgreSQL. Kafka transports work but never decides the durable task state. Redis carries rate-limit state and live hints only. + +## Consequences + +Correctness can be reasoned about through database constraints and conditional updates. PostgreSQL availability and write throughput bound the control plane, and schema migrations require operational care. diff --git a/docs/adr/0002-at-least-once-delivery.md b/docs/adr/0002-at-least-once-delivery.md new file mode 100644 index 0000000..9cede33 --- /dev/null +++ b/docs/adr/0002-at-least-once-delivery.md @@ -0,0 +1,15 @@ +# ADR 0002: At-least-once delivery + +Status: Accepted + +## Context + +A process can fail after a broker publish or handler side effect but before recording the corresponding acknowledgement. + +## Decision + +Allow duplicate messages and attempts, and prevent duplicate successful leases with compare-and-swap state transitions. Expose the stable task-run ID as the handler idempotency key. + +## Consequences + +RunMesh does not lose durable work merely to suppress duplicates. Handlers with external side effects must deduplicate those effects; the orchestrator cannot promise exactly-once behavior for arbitrary code. diff --git a/docs/adr/0003-kafka-durable-delivery.md b/docs/adr/0003-kafka-durable-delivery.md new file mode 100644 index 0000000..225ab14 --- /dev/null +++ b/docs/adr/0003-kafka-durable-delivery.md @@ -0,0 +1,15 @@ +# ADR 0003: Kafka for durable work delivery + +Status: Accepted + +## Context + +Workers need resumable, partitioned delivery with consumer-group coordination and explicit offset commits. + +## Decision + +Publish dispatch events to Kafka or Redpanda through the transactional outbox and commit consumer offsets only after a lease conflict or completed processing path. + +## Consequences + +Workers scale through partitions and consumer groups. Kafka remains an operational dependency for new dispatch, but broker outages do not erase work because unpublished events remain in PostgreSQL. diff --git a/docs/adr/0004-redis-outside-durable-path.md b/docs/adr/0004-redis-outside-durable-path.md new file mode 100644 index 0000000..0c6d099 --- /dev/null +++ b/docs/adr/0004-redis-outside-durable-path.md @@ -0,0 +1,15 @@ +# ADR 0004: Redis stays outside durable execution + +Status: Accepted + +## Context + +Rate limiting and low-latency UI notifications benefit from Redis, but workflow correctness must not depend on ephemeral state. + +## Decision + +Use Redis for atomic tenant token buckets and Pub/Sub hints. Public production requests fail closed if limits cannot be enforced; schedulers, Kafka consumers, and durable state transitions do not call Redis. + +## Consequences + +A Redis outage can reject public API traffic and degrade the UI to polling without losing or stopping already durable work. diff --git a/docs/adr/0005-transactional-outbox-recovery.md b/docs/adr/0005-transactional-outbox-recovery.md new file mode 100644 index 0000000..59300df --- /dev/null +++ b/docs/adr/0005-transactional-outbox-recovery.md @@ -0,0 +1,15 @@ +# ADR 0005: Claim-based transactional outbox recovery + +Status: Accepted + +## Context + +Publishing to Kafka inside a PostgreSQL transaction holds row locks across network I/O. Publishing first without a durable claim allows competing publishers to reorder a workflow. + +## Decision + +Claim only the oldest unpublished event for each workflow ordering key in a short `SKIP LOCKED` statement. Publish outside the transaction with a timeout, then conditionally acknowledge the claim. Claims expire and are explicitly released on known errors. + +## Consequences + +Database lock time no longer includes broker latency and publishers can process different workflows concurrently. A crash after publish but before database acknowledgement causes a duplicate, never a lost event. A claim timeout must remain longer than the publish timeout. diff --git a/docs/adr/0006-worker-leases-and-fencing.md b/docs/adr/0006-worker-leases-and-fencing.md new file mode 100644 index 0000000..b6d4a78 --- /dev/null +++ b/docs/adr/0006-worker-leases-and-fencing.md @@ -0,0 +1,15 @@ +# ADR 0006: Worker leases and fencing + +Status: Accepted + +## Context + +Workers can pause, partition, or restart while holding tasks, and an old process may resume after recovery reassigned its work. + +## Decision + +Lease through a conditional state transition and require the tenant, task, worker ID, active state, and unexpired lease for worker mutations. Record each attempt separately. Recover expired leases to retry wait or dead letter based on the attempt budget. + +## Consequences + +Only the current lease holder can mutate a task. Long tasks must heartbeat, and recovery begins only after lease expiry; therefore the configured lease bounds failover latency. diff --git a/docs/adr/0007-per-workflow-ordering.md b/docs/adr/0007-per-workflow-ordering.md new file mode 100644 index 0000000..06262c6 --- /dev/null +++ b/docs/adr/0007-per-workflow-ordering.md @@ -0,0 +1,15 @@ +# ADR 0007: Preserve per-workflow ordering + +Status: Accepted + +## Context + +Global ordering limits throughput, while events within one workflow must not overtake their predecessors. + +## Decision + +Derive each outbox ordering key from the workflow-run ID, claim only that key's head event, and use the same ID as the Kafka message key. Different workflow runs may publish and execute concurrently. + +## Consequences + +Kafka partition count limits useful consumer parallelism. Ordering is guaranteed within a run, not across tenants or workflow runs, and changing partition counts can remap future keys without reordering records already in a partition. diff --git a/docs/architecture.md b/docs/architecture.md index 34cf05b..5213600 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -15,7 +15,7 @@ React web -> REST control plane -> PostgreSQL <- scheduler PostgreSQL ``` -The control plane validates and versions DAGs, creates runs transactionally, handles tenant-scoped reads and mutations, and exposes worker lease/completion callbacks. The scheduler uses `FOR UPDATE SKIP LOCKED` so replicas can claim distinct tasks. The outbox publisher may republish after a crash; task IDs and compare-and-swap transitions make consumers safe under duplicate delivery. +The control plane validates and versions DAGs, creates runs transactionally, handles tenant-scoped reads and mutations, and exposes worker lease/completion callbacks. The scheduler uses `FOR UPDATE SKIP LOCKED` so replicas can claim distinct tasks. Outbox publishers claim a workflow's head event in a short database operation, release database locks before Kafka I/O, and acknowledge the claim afterward. A crash in the acknowledgement gap can republish an event; task IDs and compare-and-swap transitions make consumers safe under duplicate delivery. Redis enforces the public API's per-tenant token bucket and carries tenant-scoped live UI hints after the transactional outbox commit. Those hints only trigger durable API reads; they never replace PostgreSQL or Kafka. Losing Redis fails public requests closed in production, while the dashboard polls and scheduling/execution continue independently. MinIO stores oversized input/output/log artifacts. PostgreSQL contains references to those artifacts. @@ -36,3 +36,13 @@ No API performs an unqualified state change. Worker mutations include the task I ## Tenant boundary The authenticated principal supplies tenant and role. Resource IDs from paths never establish tenancy. Every public SQL query also filters by the principal tenant ID. + +## Decision records + +- [PostgreSQL as source of truth](adr/0001-postgresql-source-of-truth.md) +- [At-least-once delivery](adr/0002-at-least-once-delivery.md) +- [Kafka for durable delivery](adr/0003-kafka-durable-delivery.md) +- [Redis outside the durable path](adr/0004-redis-outside-durable-path.md) +- [Transactional outbox recovery](adr/0005-transactional-outbox-recovery.md) +- [Worker leases and fencing](adr/0006-worker-leases-and-fencing.md) +- [Per-workflow ordering](adr/0007-per-workflow-ordering.md) diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 901780f..0603cbd 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -5,22 +5,41 @@ reported with their source commit, hardware, image digests, commands, and raw machine-readable output; they are evidence from that environment rather than a universal capacity claim. -## Latest recorded run +## Latest recorded run (canonical) -The [2026-07-20 local run](../tests/benchmarks/results/2026-07-20/README.md) -used an Apple M4 MacBook Air with 16 GiB RAM and Docker Desktop 4.82.0. +The [2026-07-21T003600Z local run](../tests/benchmarks/results/2026-07-21T003600Z/README.md) +used an Apple M4 MacBook Air with 16 GiB RAM and Docker Desktop 4.82.0, against +source commit `c4b248638f2ae36c4a7bbaf48df37ee59ac1bf74`. It is the canonical +run: it postdates a fresh-stack Kafka/Redpanda topic-initialization fix in +`compose.yaml` that earlier runs (including 2026-07-21T002100Z) predate. | Scenario | Result | | --- | ---: | -| Authenticated API load | 100.017 requests/second, 4.316 ms p95, 0 failures | -| Simultaneously active workflows | 1,000 | -| Scheduler dispatch | 196.078 tasks/second across 10,000 tasks | -| Worker termination and lease recovery | 10,000/10,000 succeeded, 0 lost | +| Authenticated API reads | 100.0 requests/s; 1.701/3.307/4.657 ms p50/p95/p99; 0 failures | +| Workflow submissions | 50.028 requests/s; 1.587/7.012/220.024 ms p50/p95/p99; 0 failures | +| Simultaneously active workflows | 1,000 created in 18.224 seconds; 0 failures | +| Queued end-to-end completion | 1,000 tasks at 57.608 tasks/s | +| Scheduler dispatch | 407.403 tasks/s across 10,000 tasks | +| Worker termination and lease recovery | 20 workers terminated; 10,000/10,000 succeeded; 7 recovered; 0 lost | | Duplicate submission and delivery | Passed | -The local Redpanda topology used one partition, so worker consumption was serial. -The dispatch result met the target; the worker-termination run is reported as -correctness and recovery evidence, not as a throughput claim. +The run used two scheduler replicas, a six-partition local Redpanda topic, and +12 workers for the queued completion phase. The termination scenario killed +all 20 Go-worker containers while seven tasks were actively running (see the +[run's README](../tests/benchmarks/results/2026-07-21T003600Z/README.md) for a +note on a benign 6-vs-7 recording-timing discrepancy between two evidence +files). Its complete drain took 185.971 seconds. The seven +lease-expiration-to-next-attempt samples were +132.797/140.148/142.414 seconds p50/p95/p99; the backlog makes this a +correctness result and exposes a recovery-latency optimization opportunity. + +All 15 blocking verification checks passed. New records appeared on every +Kafka partition, duplicate submission returned the same run while preserving +the original input, and the duplicate-delivery integration test permitted only +one successful lease. + +Superseded raw runs remain in [`tests/benchmarks/results`](../tests/benchmarks/results/) +for history, each labelled with why it was superseded. ## Reproduce locally diff --git a/docs/delivery-semantics.md b/docs/delivery-semantics.md index 0143e94..022eeaf 100644 --- a/docs/delivery-semantics.md +++ b/docs/delivery-semantics.md @@ -3,11 +3,15 @@ RunMesh promises **at-least-once**, not exactly-once, task execution. 1. A state transaction moves a task from `READY` to `DISPATCHING` and appends an outbox event. -2. The publisher sends the event to Kafka using the workflow-run ID as the partition key. -3. A crash after publish but before recording `published_at` can publish a duplicate. +2. A publisher atomically claims the oldest unpublished event for that workflow, commits the short claim, and sends it to Kafka outside a database transaction using the workflow-run ID as the partition key. +3. It conditionally records `published_at`. A crash after publish but before that acknowledgement can publish a duplicate. 4. A worker leases with a conditional transition. A duplicate cannot acquire an already owned, running, or terminal task. 5. If a worker runs user code and dies before completion is recorded, its lease expires and the task is delivered again. Handlers should use `context.idempotency_key`, normally the task-run ID, for side effects. The database retains each attempt. A stale worker cannot complete a task after its lease has been reassigned because completion is fenced by `lease_owner` and state. Events are ordered within a workflow run because all of its messages share a Kafka partition key. There is no global order across runs. + +Claims expire so another publisher can recover abandoned work. The publish timeout must remain shorter than the claim TTL; otherwise two publishers could concurrently send the same ordering key while the first call is still running. + +Workers in one Kafka consumer group must advertise the same handler set because Kafka assigns partitions, not individual handlers. Use a separate group ID for a heterogeneous capability pool; each group sees the dispatch, unsupported pools leave it untouched, and the database lease selects the single executor. The bundled Go and Python workers intentionally implement the same example handlers and share `runmesh-workers`. diff --git a/docs/demo/README.md b/docs/demo/README.md new file mode 100644 index 0000000..dc77e1f --- /dev/null +++ b/docs/demo/README.md @@ -0,0 +1,23 @@ +# Demo media + +These files are captured from the real local Compose stack, never a mocked dashboard. + +To regenerate them from the repository root, start RunMesh and run the pinned +Playwright image. The small TCP forwarder makes the host-published services +available on Docker Desktop while the Docker socket lets the test terminate and +restart only the two local worker containers: + +```bash +docker compose up --build -d +docker run --rm --ipc=host \ + -v "$PWD/web:/app" \ + -v "$PWD/docs:/docs" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -w /app mcr.microsoft.com/playwright:v1.56.1-noble \ + sh -lc 'node scripts/forward-host.mjs & proxy_pid=$!; trap "kill $proxy_pid 2>/dev/null || true" EXIT; npm ci >/dev/null && npx playwright test --config=playwright.demo.config.ts' +``` + +The capture creates a workflow, executes a DAG, terminates both bundled worker +processes during a leased task, restarts the worker containers, waits for lease +recovery, and records the successful second attempt. Review the resulting media +before committing it; never substitute mock data or generated screenshots. diff --git a/docs/demo/dashboard-overview.png b/docs/demo/dashboard-overview.png new file mode 100644 index 0000000..540c7ce Binary files /dev/null and b/docs/demo/dashboard-overview.png differ diff --git a/docs/demo/lease-recovery.png b/docs/demo/lease-recovery.png new file mode 100644 index 0000000..3f07b8c Binary files /dev/null and b/docs/demo/lease-recovery.png differ diff --git a/docs/demo/run-detail.png b/docs/demo/run-detail.png new file mode 100644 index 0000000..bdb1b9d Binary files /dev/null and b/docs/demo/run-detail.png differ diff --git a/docs/demo/runmesh-recovery.webm b/docs/demo/runmesh-recovery.webm new file mode 100644 index 0000000..e42e434 Binary files /dev/null and b/docs/demo/runmesh-recovery.webm differ diff --git a/docs/deployment.md b/docs/deployment.md index 91e150f..f65e1d1 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -34,10 +34,10 @@ helm upgrade --install runmesh deploy/helm/runmesh \ --set config.corsAllowlist=https://runmesh.example.com \ --set config.artifactRegion=us-east-1 \ --set config.artifactBucket=runmesh-production-artifacts \ - --set images.controlPlane.tag=1.0.0 \ - --set images.scheduler.tag=1.0.0 \ - --set images.web.tag=1.0.0 \ - --set images.workerGo.tag=1.0.0 + --set images.controlPlane.tag=0.1.0 \ + --set images.scheduler.tag=0.1.0 \ + --set images.web.tag=0.1.0 \ + --set images.workerGo.tag=0.1.0 ``` The dashboard reads OIDC and API configuration from a ConfigMap-mounted `config.js`, so the same immutable web image can be promoted between environments. Credentials are rendered only into a Secret, never into the ConfigMap. Probes, resources, HPAs, disruption budgets, restricted security contexts, rolling strategies, and default-deny NetworkPolicies are enabled by default. @@ -65,7 +65,7 @@ Annotate the chart service account with the `artifact_role_arn` output. Install ## Release and upgrade validation -Tags matching `v*` publish control-plane, scheduler, Go worker, Python worker, and web images to GHCR. Every image receives semantic-version and commit-SHA tags, a multi-architecture manifest, SBOM, provenance attestation, and a Trivy scan. +Tags matching `v*` are configured to publish control-plane, scheduler, Go worker, Python worker, and web images to GHCR. Every image receives semantic-version and commit-SHA tags, a multi-architecture manifest, SBOM, provenance attestation, and a Trivy scan. No `v0.1.0` tag or image is published as part of repository validation; a maintainer must explicitly create the tag after reviewing the release checklist. CI lints every workflow with actionlint, validates Terraform and its mock plan, lints/templates the Helm chart, checks manifests with kubeconform, and runs a kind rolling-upgrade scenario while a workflow is active. Run that scenario locally with: diff --git a/docs/failure-modes.md b/docs/failure-modes.md index 62cb1a3..bdd5132 100644 --- a/docs/failure-modes.md +++ b/docs/failure-modes.md @@ -5,7 +5,7 @@ | Broker unavailable | State and outbox commit; publisher retries later | | Duplicate broker message | Lease compare-and-swap rejects the duplicate | | Worker crash | Expired lease becomes retryable or dead after attempt exhaustion | -| Completion response lost | Repeated completion sees the terminal task and is idempotent | +| Completion response lost | The durable completion remains committed; a redelivered dispatch cannot lease the terminal task, so user code is not rerun for that message | | Scheduler crash | Database locks are released; another replica claims work | | Redis unavailable | Rate limiting fails closed in production and live WebSocket hints reconnect with polling fallback; durable scheduling and execution remain unaffected | | Artifact store unavailable | Worker reports a retryable failure | diff --git a/docs/release-v0.1.0.md b/docs/release-v0.1.0.md new file mode 100644 index 0000000..fca42cd --- /dev/null +++ b/docs/release-v0.1.0.md @@ -0,0 +1,31 @@ +# v0.1.0 release notes (draft) + +This draft prepares the first RunMesh release. It is not evidence that a tag, GitHub release, container image, or cloud deployment exists. + +## What it demonstrates + +- Durable, dependency-aware DAG execution backed by PostgreSQL. +- At-least-once Kafka delivery with a transactional outbox and lease-fenced workers. +- Tenant-scoped OIDC and API-key authentication, rate limiting, and artifacts. +- Go and Python worker interoperability and a live React operations dashboard. +- Reproducible integration, chaos, deployment, and local benchmark checks. + +## Before publishing + +1. Complete a clean-clone Compose run and the validation checklist in `CONTRIBUTING.md`. +2. Run benchmarks against the release candidate and commit their untouched raw output. +3. Confirm CI is green for that commit. +4. Review image names and permissions, then create the `v0.1.0` tag manually. +5. Verify multi-architecture manifests, SBOMs, provenance attestations, and Trivy results. + +## Known limitations + +See `CHANGELOG.md` and the README limitations section. In particular, AWS has not been applied and benchmark results are local evidence only. + +## Suggested GitHub metadata + +- Description: `Multi-tenant workflow orchestration with durable DAG execution, Kafka delivery, and lease-based failure recovery.` +- Topics: `distributed-systems`, `workflow-engine`, `golang`, `kafka`, + `postgresql`, `kubernetes`, `observability`, `react`, `python` +- Social preview: use the real operations-overview screenshot in `docs/demo` + with a short RunMesh title treatment; do not imply hosted production usage. diff --git a/docs/validation.md b/docs/validation.md new file mode 100644 index 0000000..8893be8 --- /dev/null +++ b/docs/validation.md @@ -0,0 +1,69 @@ +# Local validation report + +This report records the checks run while preparing the `v0.1.0` candidate on +2026-07-20/21. They ran on the Apple M4 / 16 GiB Docker Desktop environment +captured in the latest benchmark directory. GitHub-hosted Actions, CodeQL, a +published release, and real AWS infrastructure were **not** run or created. + +## Test and static-analysis ledger + +| Area | Command | Result | +| --- | --- | --- | +| Go formatting | `test -z "$(gofmt -l cmd internal sdk/go gen tests/integration)"` | Passed | +| Go analysis | `go vet ./...` (Go 1.25 container) | Passed | +| Go race suite | `go test -race -covermode=atomic -coverprofile=coverage.out ./cmd/... ./internal/... ./sdk/go/...` | Passed; 11.8% repository coverage | +| Workflow coverage | `go test -coverprofile=workflow-coverage.out ./internal/workflow` | Passed; 73.6% (70% gate) | +| Authentication coverage | `go test -coverprofile=auth-coverage.out ./internal/auth` | Passed; 40.6% (40% gate) | +| Live-event coverage | `go test -coverprofile=live-coverage.out ./internal/live` | Passed; 73.5% (70% gate) | +| Go integration/chaos | `go test -tags=integration -count=1 -timeout=15m ./tests/integration` with Docker socket and Testcontainers host override | Passed in 93.547 seconds | +| Python lint | `ruff check sdk/python examples` | Passed | +| Python types/tests | `cd sdk/python && mypy runmesh && pytest` | Passed; 6 tests, 40.56% coverage | +| Dashboard lint/build | `npm --prefix web run lint && npm --prefix web run build` | Passed | +| Dashboard tests | `npm --prefix web run test:coverage` | Passed; 5 tests; 67.94% statements, 52.14% branches, 58.24% functions, 81.37% lines | +| Dashboard dependencies | `npm --prefix web audit --audit-level=high` | Passed; 0 vulnerabilities | +| Browser E2E | `npm --prefix web run test:e2e` against the real Compose services | Passed; 4 Playwright tests in 11.4 seconds | +| Compose smoke | `bash tests/e2e/smoke.sh` | Passed | +| Protobuf | `buf lint` | Passed | +| GitHub workflow syntax | `actionlint` | Passed | +| Helm | `helm lint deploy/helm/runmesh ...` | Passed | +| Rendered manifests | `helm template ... \| kubeconform -strict -summary -kubernetes-version 1.31.0` | Passed; 24 resources valid | +| Terraform | `terraform -chdir=infra/terraform fmt -check -recursive`, `init -backend=false`, `validate`, and `test` using Terraform 1.10.5 | Passed; 1 mock-provider test, 0 failed; no apply | +| Rolling upgrade | `tests/deployment/kind-rolling-upgrade.sh` | Passed; workflow `93bf8f7f-af7e-4135-9f0c-85090b1ad949` completed through the local kind rollout | +| Secret scan | `gitleaks git --staged --redact --no-banner` | Passed before the implementation commit; repeated before the evidence commit | +| Container builds | Compose builds for control plane, scheduler, Go worker, Python worker, and web | Passed | +| Container vulnerabilities | `trivy image --scanners vuln --severity CRITICAL --ignore-unfixed --exit-code 1` for all five images | Passed after patching the dashboard runtime; 0 applicable critical findings | +| Local benchmark | `tests/benchmarks/run-local.sh ` | Blocking verifier passed; see [benchmark evidence](benchmarks.md) | + +The initial dashboard vulnerability scan failed on two fixed OpenSSL findings in +the Nginx/Alpine runtime. The runtime was upgraded and its OpenSSL packages were +patched during the image build; the rebuilt image then passed. An initial +benchmark attempt also stopped during dependency readiness, and an earlier +complete run omitted p99 from its k6 summaries. Both issues were corrected +before the headline benchmark was recorded. Superseded raw runs are retained +and labelled instead of being represented as final evidence. + +## What the automated suites prove + +- Scheduler replicas use PostgreSQL `FOR UPDATE SKIP LOCKED` claims without + double-claiming work. +- Kafka publication occurs outside the database claim transaction, recovers + expired publisher claims, and tolerates duplicates around uncertain broker + acknowledgement without losing outbox events. +- Per-run Kafka keys preserve ordering, and the local six-partition topology is + exercised by multiple scheduler and worker replicas. +- Active lease, worker identity, tenant ownership, retry, cancellation, and + stale-worker fencing conditions guard worker mutations. +- OIDC/API-key authentication, scopes, revocation, expiry, rate-limit isolation, + artifact ownership/limits, and cross-tenant denial paths are covered. +- Redis, PostgreSQL, Kafka, duplicate-delivery, slow-artifact, and multi-worker + termination recovery paths have Docker-backed coverage. + +## Deliberately unverified here + +- GitHub-hosted Actions and CodeQL must run after the branch is pushed. Local + equivalents passed, but this report does not call remote CI green. +- AWS Terraform was validated and mock-tested only. It was not applied. +- Multi-architecture images, SBOMs, provenance, GHCR publication, and the + `v0.1.0` release remain tag-triggered release work and were not published. +- Performance evidence is local Docker Desktop evidence, not production + capacity or a service-level objective. diff --git a/examples/python_worker.py b/examples/python_worker.py index 12b7d50..7aeba38 100644 --- a/examples/python_worker.py +++ b/examples/python_worker.py @@ -8,6 +8,7 @@ http_endpoint=os.getenv("RUNMESH_HTTP_ENDPOINT", "control-plane:8080"), api_key=os.getenv("RUNMESH_INTERNAL_TOKEN", "local-development-token"), brokers=os.getenv("RUNMESH_KAFKA_BROKERS", "redpanda:9092"), + group_id=os.getenv("RUNMESH_KAFKA_GROUP_ID", "runmesh-workers"), ) @@ -28,5 +29,18 @@ async def notify(payload: dict, context: TaskContext) -> dict: return {"delivered": True, "idempotency_key": context.idempotency_key} +@worker.task("examples.slow") +async def slow(payload: dict, context: TaskContext) -> dict: + delay = max(0, min(int(payload.get("delay_ms", 200)), 30_000)) / 1000 + await asyncio.sleep(delay) + context.raise_if_cancelled() + return {"slept_ms": int(delay * 1000), "worker": "python"} + + +@worker.task("validation.dead") +def validation_dead(payload: dict, context: TaskContext) -> dict: + raise RuntimeError("intentional permanent failure for validation") + + if __name__ == "__main__": worker.run() diff --git a/gen/runmesh/v1/worker.pb.go b/gen/runmesh/v1/worker.pb.go index 1a1268d..781b71b 100644 --- a/gen/runmesh/v1/worker.pb.go +++ b/gen/runmesh/v1/worker.pb.go @@ -1144,7 +1144,7 @@ const file_runmesh_v1_worker_proto_rawDesc = "" + "\x04Fail\x12\x17.runmesh.v1.FailRequest\x1a\x18.runmesh.v1.FailResponse\x12i\n" + "\x14CreateArtifactUpload\x12'.runmesh.v1.CreateArtifactUploadRequest\x1a(.runmesh.v1.CreateArtifactUploadResponse\x12o\n" + "\x16CompleteArtifactUpload\x12).runmesh.v1.CompleteArtifactUploadRequest\x1a*.runmesh.v1.CompleteArtifactUploadResponse\x12f\n" + - "\x13GetArtifactDownload\x12&.runmesh.v1.GetArtifactDownloadRequest\x1a'.runmesh.v1.GetArtifactDownloadResponseB5Z3github.com/runmesh/runmesh/gen/runmesh/v1;runmeshv1b\x06proto3" + "\x13GetArtifactDownload\x12&.runmesh.v1.GetArtifactDownloadRequest\x1a'.runmesh.v1.GetArtifactDownloadResponseB9Z7github.com/samarth1412/RunMesh/gen/runmesh/v1;runmeshv1b\x06proto3" var ( file_runmesh_v1_worker_proto_rawDescOnce sync.Once diff --git a/go.mod b/go.mod index 9cb3b20..c7201b7 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/runmesh/runmesh +module github.com/samarth1412/RunMesh go 1.25.0 diff --git a/internal/api/server.go b/internal/api/server.go index 81682bd..6586a3c 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -18,13 +18,13 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/robfig/cron/v3" - "github.com/runmesh/runmesh/internal/artifact" - "github.com/runmesh/runmesh/internal/auth" - "github.com/runmesh/runmesh/internal/live" - "github.com/runmesh/runmesh/internal/storage" - "github.com/runmesh/runmesh/internal/telemetry" - "github.com/runmesh/runmesh/internal/workflow" - openapispec "github.com/runmesh/runmesh/openapi" + "github.com/samarth1412/RunMesh/internal/artifact" + "github.com/samarth1412/RunMesh/internal/auth" + "github.com/samarth1412/RunMesh/internal/live" + "github.com/samarth1412/RunMesh/internal/storage" + "github.com/samarth1412/RunMesh/internal/telemetry" + "github.com/samarth1412/RunMesh/internal/workflow" + openapispec "github.com/samarth1412/RunMesh/openapi" ) const maxBodyBytes = 1 << 20 @@ -230,6 +230,7 @@ func (s *Server) createArtifactUpload(w http.ResponseWriter, r *http.Request) { SizeBytes int64 `json:"size_bytes"` ChecksumSHA256 string `json:"checksum_sha256,omitempty"` TaskRunID *string `json:"task_run_id,omitempty"` + WorkerID string `json:"worker_id,omitempty"` } if !decode(w, r, &req) { return @@ -238,6 +239,13 @@ func (s *Server) createArtifactUpload(w http.ResponseWriter, r *http.Request) { var upload artifact.Upload var err error if strings.HasPrefix(r.URL.Path, "/internal/") { + if req.TaskRunID == nil || req.WorkerID == "" { + writeError(w, http.StatusBadRequest, "task_run_id and worker_id are required") + return + } + if !s.authorizeActiveTask(w, r, *req.TaskRunID, req.WorkerID) { + return + } upload, err = s.Artifacts.CreateInternalUpload(r.Context(), p.TenantID, p.UserID, req.Kind, req.ContentType, req.SizeBytes, req.ChecksumSHA256, req.TaskRunID) } else { upload, err = s.Artifacts.CreateUpload(r.Context(), p.TenantID, p.UserID, req.Kind, req.ContentType, req.SizeBytes, req.ChecksumSHA256, req.TaskRunID) @@ -259,6 +267,24 @@ func (s *Server) completeArtifactUpload(w http.ResponseWriter, r *http.Request) return } p := auth.PrincipalFrom(r.Context()) + if strings.HasPrefix(r.URL.Path, "/internal/") { + var req workerRequest + if !decode(w, r, &req) { + return + } + if req.WorkerID == "" || req.TaskRunID == "" { + writeError(w, http.StatusBadRequest, "task_run_id and worker_id are required") + return + } + if !s.authorizeActiveTask(w, r, req.TaskRunID, req.WorkerID) { + return + } + pending, pendingErr := s.Store.GetArtifact(r.Context(), p.TenantID, r.PathValue("id")) + if pendingErr != nil || pending.TaskRunID == nil || *pending.TaskRunID != req.TaskRunID { + writeError(w, http.StatusNotFound, "artifact not found for active task") + return + } + } a, err := s.Artifacts.Complete(r.Context(), p.TenantID, r.PathValue("id")) if handleErr(w, err) { return @@ -275,6 +301,15 @@ func (s *Server) downloadArtifact(w http.ResponseWriter, r *http.Request) { var download artifact.Download var err error if strings.HasPrefix(r.URL.Path, "/internal/") { + taskRunID := r.URL.Query().Get("task_run_id") + workerID := r.URL.Query().Get("worker_id") + if taskRunID == "" || workerID == "" { + writeError(w, http.StatusBadRequest, "task_run_id and worker_id are required") + return + } + if !s.authorizeActiveTask(w, r, taskRunID, workerID) { + return + } download, err = s.Artifacts.DownloadInternal(r.Context(), p.TenantID, r.PathValue("id")) } else { download, err = s.Artifacts.Download(r.Context(), p.TenantID, r.PathValue("id")) @@ -307,6 +342,19 @@ func (s *Server) getRun(w http.ResponseWriter, r *http.Request) { run.Tasks[index].LogArtifactDownloadURL = signed.URL } } + for attemptIndex := range run.Tasks[index].Attempts { + attempt := &run.Tasks[index].Attempts[attemptIndex] + if attempt.ArtifactURI != "" { + if signed, signErr := s.Artifacts.Download(r.Context(), tenantID, attempt.ArtifactURI); signErr == nil { + attempt.ArtifactDownloadURL = signed.URL + } + } + if attempt.LogArtifactURI != "" { + if signed, signErr := s.Artifacts.Download(r.Context(), tenantID, attempt.LogArtifactURI); signErr == nil { + attempt.LogArtifactDownloadURL = signed.URL + } + } + } } } writeJSON(w, 200, run) @@ -457,12 +505,17 @@ func (s *Server) revokeAPIKey(w http.ResponseWriter, r *http.Request) { } type workerRequest struct { - WorkerID string `json:"worker_id"` + WorkerID string `json:"worker_id"` + TaskRunID string `json:"task_run_id,omitempty"` } func (s *Server) lease(w http.ResponseWriter, r *http.Request) { var req workerRequest - if !decode(w, r, &req) || req.WorkerID == "" { + if !decode(w, r, &req) { + return + } + if req.WorkerID == "" { + writeError(w, http.StatusBadRequest, "worker_id is required") return } if !s.authorizeTask(w, r) { @@ -479,6 +532,10 @@ func (s *Server) start(w http.ResponseWriter, r *http.Request) { if !decode(w, r, &req) { return } + if req.WorkerID == "" { + writeError(w, http.StatusBadRequest, "worker_id is required") + return + } if !s.authorizeTask(w, r) { return } @@ -493,6 +550,10 @@ func (s *Server) heartbeat(w http.ResponseWriter, r *http.Request) { if !decode(w, r, &req) { return } + if req.WorkerID == "" { + writeError(w, http.StatusBadRequest, "worker_id is required") + return + } if !s.authorizeTask(w, r) { return } @@ -511,9 +572,25 @@ func (s *Server) complete(w http.ResponseWriter, r *http.Request) { if !decode(w, r, &req) { return } + if req.WorkerID == "" { + writeError(w, http.StatusBadRequest, "worker_id is required") + return + } if !s.authorizeTask(w, r) { return } + if req.OutputArtifactURI != "" { + if s.Artifacts == nil { + writeError(w, http.StatusServiceUnavailable, "artifact storage is not configured") + return + } + principal := auth.PrincipalFrom(r.Context()) + a, artifactErr := s.Store.GetArtifactByURI(r.Context(), principal.TenantID, req.OutputArtifactURI) + if artifactErr != nil || a.Status != "READY" || a.Kind != "output" || a.TaskRunID == nil || *a.TaskRunID != r.PathValue("id") { + writeError(w, http.StatusForbidden, "invalid output artifact") + return + } + } t, err := s.Store.CompleteTask(r.Context(), r.PathValue("id"), req.WorkerID, req.Output, req.OutputArtifactURI) if handleErr(w, err) { return @@ -528,6 +605,10 @@ func (s *Server) fail(w http.ResponseWriter, r *http.Request) { if !decode(w, r, &req) { return } + if req.WorkerID == "" { + writeError(w, http.StatusBadRequest, "worker_id is required") + return + } if !s.authorizeTask(w, r) { return } @@ -560,6 +641,14 @@ func (s *Server) authorizeTask(w http.ResponseWriter, r *http.Request) bool { return true } +func (s *Server) authorizeActiveTask(w http.ResponseWriter, r *http.Request, taskID, workerID string) bool { + p := auth.PrincipalFrom(r.Context()) + if handleErr(w, s.Store.ActiveTaskLeaseBelongsToWorker(r.Context(), taskID, p.TenantID, workerID)) { + return false + } + return true +} + func (s *Server) createSchedule(w http.ResponseWriter, r *http.Request) { var req struct { WorkflowDefinitionID string `json:"workflow_definition_id"` diff --git a/internal/api/stream.go b/internal/api/stream.go index 301734a..72cb03f 100644 --- a/internal/api/stream.go +++ b/internal/api/stream.go @@ -7,7 +7,7 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/runmesh/runmesh/internal/auth" + "github.com/samarth1412/RunMesh/internal/auth" ) var streamUpgrader = websocket.Upgrader{ diff --git a/internal/api/stream_test.go b/internal/api/stream_test.go index cdf5b86..904a9dd 100644 --- a/internal/api/stream_test.go +++ b/internal/api/stream_test.go @@ -11,8 +11,8 @@ import ( "github.com/alicebob/miniredis/v2" "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus" - "github.com/runmesh/runmesh/internal/auth" - "github.com/runmesh/runmesh/internal/live" + "github.com/samarth1412/RunMesh/internal/auth" + "github.com/samarth1412/RunMesh/internal/live" ) func TestAuthenticatedTenantStream(t *testing.T) { diff --git a/internal/artifact/manager.go b/internal/artifact/manager.go index 3f07a20..10ddf70 100644 --- a/internal/artifact/manager.go +++ b/internal/artifact/manager.go @@ -16,7 +16,7 @@ import ( "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/google/uuid" - "github.com/runmesh/runmesh/internal/storage" + "github.com/samarth1412/RunMesh/internal/storage" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" diff --git a/internal/config/config.go b/internal/config/config.go index d8fd3f5..3889f60 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -19,6 +19,9 @@ type Config struct { LeaseDuration time.Duration SchedulerInterval time.Duration OutboxInterval time.Duration + OutboxBatchSize int + OutboxClaimTTL time.Duration + OutboxPublishTimeout time.Duration DevAuth bool DevTenantID string DevUserID string @@ -90,6 +93,15 @@ func Load() (Config, error) { if c.OutboxInterval, err = envDuration("RUNMESH_OUTBOX_INTERVAL", 250*time.Millisecond); err != nil { return Config{}, err } + if c.OutboxBatchSize, err = envInt("RUNMESH_OUTBOX_BATCH_SIZE", 100); err != nil { + return Config{}, err + } + if c.OutboxClaimTTL, err = envDuration("RUNMESH_OUTBOX_CLAIM_TTL", 30*time.Second); err != nil { + return Config{}, err + } + if c.OutboxPublishTimeout, err = envDuration("RUNMESH_OUTBOX_PUBLISH_TIMEOUT", 10*time.Second); err != nil { + return Config{}, err + } if c.ArtifactPresignExpiry, err = envDuration("RUNMESH_ARTIFACT_PRESIGN_EXPIRY", 15*time.Minute); err != nil { return Config{}, err } @@ -105,6 +117,9 @@ func Load() (Config, error) { if c.RateLimitRate <= 0 || c.RateLimitBurst <= 0 { return Config{}, fmt.Errorf("rate limit rate and burst must be positive") } + if c.OutboxBatchSize <= 0 || c.OutboxClaimTTL <= 0 || c.OutboxPublishTimeout <= 0 || c.OutboxPublishTimeout >= c.OutboxClaimTTL { + return Config{}, fmt.Errorf("outbox batch size must be positive and publish timeout must be shorter than claim TTL") + } if !c.DevAuth && c.RateLimitFailOpen { return Config{}, fmt.Errorf("RUNMESH_RATE_LIMIT_FAIL_OPEN cannot be enabled outside development") } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 79d7d83..2933375 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -2,6 +2,7 @@ package config import ( "reflect" + "strings" "testing" ) @@ -12,3 +13,12 @@ func TestSplitNonEmpty(t *testing.T) { t.Fatalf("splitNonEmpty()=%v, want %v", got, want) } } + +func TestOutboxClaimConfiguration(t *testing.T) { + t.Setenv("RUNMESH_DEV_AUTH", "true") + t.Setenv("RUNMESH_OUTBOX_CLAIM_TTL", "5s") + t.Setenv("RUNMESH_OUTBOX_PUBLISH_TIMEOUT", "5s") + if _, err := Load(); err == nil || !strings.Contains(err.Error(), "publish timeout") { + t.Fatalf("Load() error=%v, want outbox timeout validation", err) + } +} diff --git a/internal/ratelimit/limiter.go b/internal/ratelimit/limiter.go index dcd12c8..4dbd516 100644 --- a/internal/ratelimit/limiter.go +++ b/internal/ratelimit/limiter.go @@ -10,7 +10,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/redis/go-redis/v9" - "github.com/runmesh/runmesh/internal/auth" + "github.com/samarth1412/RunMesh/internal/auth" ) const tokenBucketScript = ` diff --git a/internal/rpc/worker.go b/internal/rpc/worker.go index 7a08be6..5631676 100644 --- a/internal/rpc/worker.go +++ b/internal/rpc/worker.go @@ -7,11 +7,11 @@ import ( "strings" "time" - runmeshv1 "github.com/runmesh/runmesh/gen/runmesh/v1" - "github.com/runmesh/runmesh/internal/artifact" - "github.com/runmesh/runmesh/internal/auth" - "github.com/runmesh/runmesh/internal/storage" - "github.com/runmesh/runmesh/internal/workflow" + runmeshv1 "github.com/samarth1412/RunMesh/gen/runmesh/v1" + "github.com/samarth1412/RunMesh/internal/artifact" + "github.com/samarth1412/RunMesh/internal/auth" + "github.com/samarth1412/RunMesh/internal/storage" + "github.com/samarth1412/RunMesh/internal/workflow" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" @@ -29,10 +29,14 @@ type WorkerServer struct { func (s *WorkerServer) authorize(ctx context.Context) (auth.Principal, error) { values := metadata.ValueFromIncomingContext(ctx, "authorization") - if len(values) != 1 { + if len(values) != 1 || !strings.HasPrefix(values[0], "Bearer ") { return auth.Principal{}, status.Error(codes.Unauthenticated, "invalid worker credential") } - principal, err := s.Authenticator.AuthenticateWorker(ctx, strings.TrimPrefix(values[0], "Bearer ")) + token := strings.TrimSpace(strings.TrimPrefix(values[0], "Bearer ")) + if token == "" { + return auth.Principal{}, status.Error(codes.Unauthenticated, "invalid worker credential") + } + principal, err := s.Authenticator.AuthenticateWorker(ctx, token) if err != nil { return auth.Principal{}, status.Error(codes.Unauthenticated, "invalid worker credential") } @@ -49,12 +53,12 @@ func (s *WorkerServer) authorizeTask(ctx context.Context, taskID string) error { return nil } -func (s *WorkerServer) principalForTask(ctx context.Context, taskID string) (auth.Principal, error) { +func (s *WorkerServer) principalForActiveTask(ctx context.Context, taskID, workerID string) (auth.Principal, error) { principal, err := s.authorize(ctx) if err != nil { return principal, err } - if err = s.Store.TaskBelongsToTenant(ctx, taskID, principal.TenantID); err != nil { + if err = s.Store.ActiveTaskLeaseBelongsToWorker(ctx, taskID, principal.TenantID, workerID); err != nil { return principal, rpcError(err) } return principal, nil @@ -90,7 +94,7 @@ func (s *WorkerServer) Heartbeat(ctx context.Context, request *runmeshv1.Heartbe return &runmeshv1.HeartbeatResponse{LeaseExpiresAt: timestamppb.New(expires), Cancelled: cancelled}, nil } func (s *WorkerServer) Complete(ctx context.Context, request *runmeshv1.CompleteRequest) (*runmeshv1.CompleteResponse, error) { - principal, err := s.principalForTask(ctx, request.TaskRunId) + principal, err := s.principalForActiveTask(ctx, request.TaskRunId, request.WorkerId) if err != nil { return nil, err } @@ -125,7 +129,7 @@ func (s *WorkerServer) CreateArtifactUpload(ctx context.Context, request *runmes if s.Artifacts == nil { return nil, status.Error(codes.Unavailable, "artifact storage is not configured") } - principal, err := s.principalForTask(ctx, request.TaskRunId) + principal, err := s.principalForActiveTask(ctx, request.TaskRunId, request.WorkerId) if err != nil { return nil, err } @@ -140,7 +144,7 @@ func (s *WorkerServer) CompleteArtifactUpload(ctx context.Context, request *runm if s.Artifacts == nil { return nil, status.Error(codes.Unavailable, "artifact storage is not configured") } - principal, err := s.principalForTask(ctx, request.TaskRunId) + principal, err := s.principalForActiveTask(ctx, request.TaskRunId, request.WorkerId) if err != nil { return nil, err } @@ -159,7 +163,7 @@ func (s *WorkerServer) GetArtifactDownload(ctx context.Context, request *runmesh if s.Artifacts == nil { return nil, status.Error(codes.Unavailable, "artifact storage is not configured") } - principal, err := s.principalForTask(ctx, request.TaskRunId) + principal, err := s.principalForActiveTask(ctx, request.TaskRunId, request.WorkerId) if err != nil { return nil, err } diff --git a/internal/rpc/worker_test.go b/internal/rpc/worker_test.go index 181c27a..d6ece88 100644 --- a/internal/rpc/worker_test.go +++ b/internal/rpc/worker_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/runmesh/runmesh/internal/workflow" + "github.com/samarth1412/RunMesh/internal/workflow" ) func TestTaskSnapshotContract(t *testing.T) { diff --git a/internal/scheduler/scheduler.go b/internal/scheduler/scheduler.go index 460ae77..bcb5d48 100644 --- a/internal/scheduler/scheduler.go +++ b/internal/scheduler/scheduler.go @@ -4,17 +4,18 @@ import ( "context" "encoding/json" "errors" + "fmt" "log/slog" "time" + "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/prometheus/client_golang/prometheus" "github.com/robfig/cron/v3" - "github.com/runmesh/runmesh/internal/live" - "github.com/runmesh/runmesh/internal/messaging" - "github.com/runmesh/runmesh/internal/storage" - "github.com/runmesh/runmesh/internal/tracecontext" - "github.com/runmesh/runmesh/internal/workflow" + "github.com/samarth1412/RunMesh/internal/live" + "github.com/samarth1412/RunMesh/internal/storage" + "github.com/samarth1412/RunMesh/internal/tracecontext" + "github.com/samarth1412/RunMesh/internal/workflow" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -22,12 +23,25 @@ import ( type Service struct { Store *storage.Store - Publisher *messaging.Publisher + Publisher EventPublisher Live *live.Broker Published *prometheus.CounterVec ScheduleInterval, OutboxInterval time.Duration + OutboxBatchSize int + OutboxClaimTTL time.Duration + OutboxPublishTimeout time.Duration } +type EventPublisher interface { + Publish(context.Context, []byte, []byte, map[string]string) error +} + +const ( + defaultOutboxBatchSize = 100 + defaultOutboxClaimTTL = 30 * time.Second + defaultOutboxPublishTimeout = 10 * time.Second +) + func (s *Service) Run(ctx context.Context) error { errc := make(chan error, 2) go func() { errc <- s.runScheduler(ctx) }() @@ -243,75 +257,122 @@ func (s *Service) runOutbox(ctx context.Context) error { } } func (s *Service) publishBatch(ctx context.Context) error { - tx, err := s.Store.Pool.Begin(ctx) - if err != nil { - return err + for { + published, err := s.publishClaimedBatch(ctx) + if err != nil { + return err + } + if published == 0 { + return nil + } } - defer tx.Rollback(ctx) - rows, err := tx.Query(ctx, `SELECT o.id,o.aggregate_id,o.event_type,o.payload,COALESCE(o.trace_parent,''),o.created_at, +} + +func (s *Service) publishClaimedBatch(ctx context.Context) (int, error) { + batchSize := s.OutboxBatchSize + if batchSize <= 0 { + batchSize = defaultOutboxBatchSize + } + claimTTL := s.OutboxClaimTTL + if claimTTL <= 0 { + claimTTL = defaultOutboxClaimTTL + } + publishTimeout := s.OutboxPublishTimeout + if publishTimeout <= 0 { + publishTimeout = defaultOutboxPublishTimeout + } + if publishTimeout >= claimTTL { + return 0, fmt.Errorf("outbox publish timeout %s must be shorter than claim TTL %s", publishTimeout, claimTTL) + } + + claimToken := uuid.NewString() + rows, err := s.Store.Pool.Query(ctx, `WITH candidates AS ( + SELECT o.id FROM outbox_events o + WHERE o.published_at IS NULL + AND (o.claim_token IS NULL OR o.claim_expires_at<=now()) + AND NOT EXISTS ( + SELECT 1 FROM outbox_events earlier + WHERE earlier.ordering_key=o.ordering_key + AND earlier.published_at IS NULL + AND (earlier.created_at,earlier.id)<(o.created_at,o.id) + ) + ORDER BY o.created_at,o.id + FOR UPDATE OF o SKIP LOCKED + LIMIT $1 + ) + UPDATE outbox_events o + SET claim_token=$2,claim_expires_at=now()+make_interval(secs => $3),publish_attempts=publish_attempts+1 + FROM candidates c + WHERE o.id=c.id + RETURNING o.id,o.aggregate_id,o.ordering_key,o.event_type,o.payload,COALESCE(o.trace_parent,''),o.created_at, COALESCE( (SELECT tenant_id::text FROM workflow_runs WHERE id=o.aggregate_id AND o.aggregate_type='workflow_run'), (SELECT r.tenant_id::text FROM task_runs t JOIN workflow_runs r ON r.id=t.workflow_run_id WHERE t.id=o.aggregate_id AND o.aggregate_type='task_run'), (SELECT tenant_id::text FROM workflow_definitions WHERE id=o.aggregate_id AND o.aggregate_type='workflow_definition'), '' - ) - FROM outbox_events o WHERE o.published_at IS NULL ORDER BY o.created_at FOR UPDATE OF o SKIP LOCKED LIMIT 100`) + )`, batchSize, claimToken, claimTTL.Seconds()) if err != nil { - return err + return 0, err } type event struct { - id, aggregateID, eventType, traceParent, tenantID string - payload []byte - createdAt time.Time + id, aggregateID, orderingKey, eventType, traceParent, tenantID string + payload []byte + createdAt time.Time } var events []event for rows.Next() { var e event - if err = rows.Scan(&e.id, &e.aggregateID, &e.eventType, &e.payload, &e.traceParent, &e.createdAt, &e.tenantID); err != nil { + if err = rows.Scan(&e.id, &e.aggregateID, &e.orderingKey, &e.eventType, &e.payload, &e.traceParent, &e.createdAt, &e.tenantID); err != nil { rows.Close() - return err + return 0, err } events = append(events, e) } + if err = rows.Err(); err != nil { + rows.Close() + return 0, err + } rows.Close() - for _, e := range events { - var envelope struct { - WorkflowRunID string `json:"workflow_run_id"` - } - _ = json.Unmarshal(e.payload, &envelope) - key := e.aggregateID - if envelope.WorkflowRunID != "" { - key = envelope.WorkflowRunID + + releaseClaims := func() { + releaseContext, cancel := context.WithTimeout(context.WithoutCancel(ctx), time.Second) + defer cancel() + if _, releaseErr := s.Store.Pool.Exec(releaseContext, `UPDATE outbox_events SET claim_token=NULL,claim_expires_at=NULL WHERE claim_token=$1 AND published_at IS NULL`, claimToken); releaseErr != nil { + slog.Warn("outbox claim release failed", "claim_token", claimToken, "error", releaseErr) } + } + for _, e := range events { publishContext := tracecontext.IntoContext(ctx, e.traceParent) + publishContext, cancel := context.WithTimeout(publishContext, publishTimeout) publishContext, span := otel.Tracer("runmesh/scheduler").Start(publishContext, "outbox.publish", trace.WithAttributes(attribute.String("tenant_id", e.tenantID), attribute.String("event.id", e.id), attribute.String("event.type", e.eventType))) - if err = s.Publisher.Publish(publishContext, []byte(key), e.payload, map[string]string{"event_type": e.eventType, "event_id": e.id}); err != nil { + if err = s.Publisher.Publish(publishContext, []byte(e.orderingKey), e.payload, map[string]string{"event_type": e.eventType, "event_id": e.id}); err != nil { span.RecordError(err) span.End() - return err + cancel() + releaseClaims() + return 0, err } span.End() - if _, err = tx.Exec(ctx, `UPDATE outbox_events SET published_at=now() WHERE id=$1 AND published_at IS NULL`, e.id); err != nil { - return err + cancel() + command, ackErr := s.Store.Pool.Exec(ctx, `UPDATE outbox_events SET published_at=now(),claim_token=NULL,claim_expires_at=NULL WHERE id=$1 AND published_at IS NULL AND claim_token=$2`, e.id, claimToken) + if ackErr != nil { + releaseClaims() + return 0, ackErr } - } - if err = tx.Commit(ctx); err != nil { - return err - } - if s.Published != nil { - for _, e := range events { + if command.RowsAffected() != 1 { + releaseClaims() + return 0, fmt.Errorf("outbox claim lost before acknowledgement: event %s", e.id) + } + if s.Published != nil { s.Published.WithLabelValues(e.tenantID, e.eventType).Inc() } - } - if s.Live != nil { - for _, e := range events { + if s.Live != nil { if liveErr := s.Live.Publish(ctx, e.tenantID, live.Event{ID: e.id, Type: e.eventType, AggregateID: e.aggregateID, Payload: e.payload, OccurredAt: e.createdAt.UTC().Format(time.RFC3339Nano)}); liveErr != nil { slog.Warn("live notification unavailable", "tenant_id", e.tenantID, "event_id", e.id, "event_type", e.eventType, "error", liveErr) - break } } } - return nil + return len(events), nil } func mustJSON(v any) []byte { b, err := json.Marshal(v) diff --git a/internal/storage/postgres.go b/internal/storage/postgres.go index e92e6ca..e5d9f16 100644 --- a/internal/storage/postgres.go +++ b/internal/storage/postgres.go @@ -10,8 +10,8 @@ import ( "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" - "github.com/runmesh/runmesh/internal/tracecontext" - "github.com/runmesh/runmesh/internal/workflow" + "github.com/samarth1412/RunMesh/internal/tracecontext" + "github.com/samarth1412/RunMesh/internal/workflow" ) var ErrNotFound = errors.New("not found") @@ -235,19 +235,46 @@ func (s *Store) GetRun(ctx context.Context, tenantID, id string) (workflow.Run, if err != nil { return r, err } - rows, err := s.Pool.Query(ctx, `SELECT id,workflow_run_id,task_key,handler,status,priority,available_at,attempt_count,maximum_attempts,timeout_seconds,lease_owner,lease_expires_at,input,output,output_artifact_uri,(SELECT log_artifact_uri FROM task_attempts WHERE task_run_id=task_runs.id AND log_artifact_uri IS NOT NULL ORDER BY attempt_number DESC LIMIT 1) FROM task_runs WHERE workflow_run_id=$1 ORDER BY created_at,task_key`, id) + rows, err := s.Pool.Query(ctx, `SELECT id,workflow_run_id,task_key,handler,status,priority,available_at,attempt_count,maximum_attempts,timeout_seconds,lease_owner,lease_expires_at,input,output,output_artifact_uri, + (SELECT log_artifact_uri FROM task_attempts WHERE task_run_id=task_runs.id AND log_artifact_uri IS NOT NULL ORDER BY attempt_number DESC LIMIT 1), + ARRAY(SELECT upstream.task_key FROM task_dependencies d JOIN task_runs upstream ON upstream.id=d.depends_on_task_run_id WHERE d.task_run_id=task_runs.id ORDER BY upstream.task_key) + FROM task_runs WHERE workflow_run_id=$1 ORDER BY created_at,task_key`, id) if err != nil { return r, err } - defer rows.Close() + taskIndexes := make(map[string]int) for rows.Next() { var t workflow.TaskRun - if err = rows.Scan(&t.ID, &t.WorkflowRunID, &t.TaskKey, &t.Handler, &t.Status, &t.Priority, &t.AvailableAt, &t.AttemptCount, &t.MaximumAttempts, &t.TimeoutSeconds, &t.LeaseOwner, &t.LeaseExpiresAt, &t.Input, &t.Output, &t.OutputArtifactURI, &t.LogArtifactURI); err != nil { + if err = rows.Scan(&t.ID, &t.WorkflowRunID, &t.TaskKey, &t.Handler, &t.Status, &t.Priority, &t.AvailableAt, &t.AttemptCount, &t.MaximumAttempts, &t.TimeoutSeconds, &t.LeaseOwner, &t.LeaseExpiresAt, &t.Input, &t.Output, &t.OutputArtifactURI, &t.LogArtifactURI, &t.DependsOn); err != nil { + rows.Close() return r, err } + taskIndexes[t.ID] = len(r.Tasks) r.Tasks = append(r.Tasks, t) } - return r, rows.Err() + if err = rows.Err(); err != nil { + rows.Close() + return r, err + } + rows.Close() + attemptRows, err := s.Pool.Query(ctx, `SELECT a.task_run_id,a.attempt_number,a.worker_id,a.scheduled_at,a.executing_at,a.started_at,a.ended_at, + COALESCE(a.exit_status,''),COALESCE(a.error_type,''),COALESCE(a.error_message,''),COALESCE(a.trace_id,''),COALESCE(a.artifact_uri,''),COALESCE(a.log_artifact_uri,'') + FROM task_attempts a JOIN task_runs t ON t.id=a.task_run_id WHERE t.workflow_run_id=$1 ORDER BY t.created_at,t.task_key,a.attempt_number`, id) + if err != nil { + return r, err + } + defer attemptRows.Close() + for attemptRows.Next() { + var taskID string + var attempt workflow.TaskAttempt + if err = attemptRows.Scan(&taskID, &attempt.AttemptNumber, &attempt.WorkerID, &attempt.ScheduledAt, &attempt.ExecutingAt, &attempt.StartedAt, &attempt.EndedAt, &attempt.ExitStatus, &attempt.ErrorType, &attempt.ErrorMessage, &attempt.TraceID, &attempt.ArtifactURI, &attempt.LogArtifactURI); err != nil { + return r, err + } + if index, ok := taskIndexes[taskID]; ok { + r.Tasks[index].Attempts = append(r.Tasks[index].Attempts, attempt) + } + } + return r, attemptRows.Err() } func (s *Store) ListRuns(ctx context.Context, tenantID string, limit int) ([]workflow.Run, error) { diff --git a/internal/storage/worker.go b/internal/storage/worker.go index 1f8ae64..576cdf6 100644 --- a/internal/storage/worker.go +++ b/internal/storage/worker.go @@ -8,8 +8,8 @@ import ( "time" "github.com/jackc/pgx/v5" - "github.com/runmesh/runmesh/internal/tracecontext" - "github.com/runmesh/runmesh/internal/workflow" + "github.com/samarth1412/RunMesh/internal/tracecontext" + "github.com/samarth1412/RunMesh/internal/workflow" ) type Failure struct { @@ -47,9 +47,6 @@ func (s *Store) LeaseTask(ctx context.Context, taskID, workerID string, lease ti if err != nil { return t, err } - if _, err = tx.Exec(ctx, `UPDATE task_attempts SET executing_at=now() WHERE task_run_id=$1 AND attempt_number=$2 AND executing_at IS NULL`, t.ID, t.AttemptCount); err != nil { - return t, err - } return t, tx.Commit(ctx) } @@ -78,6 +75,9 @@ func (s *Store) StartTask(ctx context.Context, taskID, workerID string) (workflo if err != nil { return t, err } + if _, err = tx.Exec(ctx, `UPDATE task_attempts SET executing_at=now() WHERE task_run_id=$1 AND attempt_number=$2 AND executing_at IS NULL`, t.ID, t.AttemptCount); err != nil { + return t, err + } return t, tx.Commit(ctx) } @@ -266,6 +266,24 @@ func (s *Store) TaskBelongsToTenant(ctx context.Context, taskID, tenantID string return nil } +func (s *Store) ActiveTaskLeaseBelongsToWorker(ctx context.Context, taskID, tenantID, workerID string) error { + var taskTenantID string + var active bool + err := s.Pool.QueryRow(ctx, `SELECT r.tenant_id::text, + (t.status IN ('LEASED','RUNNING') AND t.lease_owner=$2 AND t.lease_expires_at>now() AND r.status='RUNNING') + FROM task_runs t JOIN workflow_runs r ON r.id=t.workflow_run_id WHERE t.id=$1`, taskID, workerID).Scan(&taskTenantID, &active) + if errors.Is(err, pgx.ErrNoRows) || (err == nil && taskTenantID != tenantID) { + return ErrNotFound + } + if err != nil { + return err + } + if !active { + return ErrLeaseLost + } + return nil +} + func (s *Store) DeadLetter(ctx context.Context, tenantID string) ([]workflow.TaskRun, error) { rows, err := s.Pool.Query(ctx, `SELECT t.id,t.workflow_run_id,t.task_key,t.handler,t.status,t.priority,t.available_at,t.attempt_count,t.maximum_attempts,t.timeout_seconds,t.lease_owner,t.lease_expires_at,t.input,t.output,t.output_artifact_uri FROM task_runs t JOIN workflow_runs r ON r.id=t.workflow_run_id WHERE r.tenant_id=$1 AND t.status='DEAD' ORDER BY t.updated_at DESC`, tenantID) if err != nil { diff --git a/internal/telemetry/dbmetrics.go b/internal/telemetry/dbmetrics.go index 3ee9315..2a74a12 100644 --- a/internal/telemetry/dbmetrics.go +++ b/internal/telemetry/dbmetrics.go @@ -6,7 +6,7 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" - "github.com/runmesh/runmesh/internal/storage" + "github.com/samarth1412/RunMesh/internal/storage" ) var durationBuckets = []float64{0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60, 300, 900, 3600} diff --git a/internal/workflow/model.go b/internal/workflow/model.go index ba24341..4ab344f 100644 --- a/internal/workflow/model.go +++ b/internal/workflow/model.go @@ -62,6 +62,25 @@ type TaskRun struct { LogArtifactURI *string `json:"log_artifact_uri,omitempty"` OutputArtifactDownloadURL string `json:"output_artifact_download_url,omitempty"` LogArtifactDownloadURL string `json:"log_artifact_download_url,omitempty"` + DependsOn []string `json:"depends_on,omitempty"` + Attempts []TaskAttempt `json:"attempts,omitempty"` +} + +type TaskAttempt struct { + AttemptNumber int `json:"attempt_number"` + WorkerID string `json:"worker_id"` + ScheduledAt time.Time `json:"scheduled_at"` + ExecutingAt *time.Time `json:"executing_at,omitempty"` + StartedAt time.Time `json:"started_at"` + EndedAt *time.Time `json:"ended_at,omitempty"` + ExitStatus string `json:"exit_status,omitempty"` + ErrorType string `json:"error_type,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` + TraceID string `json:"trace_id,omitempty"` + ArtifactURI string `json:"artifact_uri,omitempty"` + LogArtifactURI string `json:"log_artifact_uri,omitempty"` + ArtifactDownloadURL string `json:"artifact_download_url,omitempty"` + LogArtifactDownloadURL string `json:"log_artifact_download_url,omitempty"` } type Event struct { diff --git a/migrations/007_outbox_claims.down.sql b/migrations/007_outbox_claims.down.sql new file mode 100644 index 0000000..2e36b0c --- /dev/null +++ b/migrations/007_outbox_claims.down.sql @@ -0,0 +1,8 @@ +DROP INDEX IF EXISTS outbox_claim_expiry_idx; +DROP INDEX IF EXISTS outbox_ordering_head_idx; + +ALTER TABLE outbox_events + DROP COLUMN IF EXISTS publish_attempts, + DROP COLUMN IF EXISTS claim_expires_at, + DROP COLUMN IF EXISTS claim_token, + DROP COLUMN IF EXISTS ordering_key; diff --git a/migrations/007_outbox_claims.up.sql b/migrations/007_outbox_claims.up.sql new file mode 100644 index 0000000..40001fc --- /dev/null +++ b/migrations/007_outbox_claims.up.sql @@ -0,0 +1,15 @@ +ALTER TABLE outbox_events + ADD COLUMN ordering_key UUID GENERATED ALWAYS AS ( + COALESCE(NULLIF(payload->>'workflow_run_id', '')::UUID, aggregate_id) + ) STORED, + ADD COLUMN claim_token UUID, + ADD COLUMN claim_expires_at TIMESTAMPTZ, + ADD COLUMN publish_attempts INTEGER NOT NULL DEFAULT 0 CHECK (publish_attempts >= 0); + +CREATE INDEX outbox_ordering_head_idx + ON outbox_events (ordering_key, created_at, id) + WHERE published_at IS NULL; + +CREATE INDEX outbox_claim_expiry_idx + ON outbox_events (claim_expires_at) + WHERE published_at IS NULL AND claim_token IS NOT NULL; diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index baa97d6..c018e94 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -92,6 +92,7 @@ paths: /v1/runs/{id}: get: operationId: getRun + description: Returns tenant-scoped run state, task dependencies, active lease metadata, and immutable attempt history including workers and failure messages. parameters: [{$ref: '#/components/parameters/ID'}] responses: {"200": {description: Run with tasks}, "404": {description: Not found}} /v1/runs: diff --git a/proto/runmesh/v1/worker.proto b/proto/runmesh/v1/worker.proto index 43da72e..2bc1844 100644 --- a/proto/runmesh/v1/worker.proto +++ b/proto/runmesh/v1/worker.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package runmesh.v1; -option go_package = "github.com/runmesh/runmesh/gen/runmesh/v1;runmeshv1"; +option go_package = "github.com/samarth1412/RunMesh/gen/runmesh/v1;runmeshv1"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; diff --git a/scripts/check-go-coverage.sh b/scripts/check-go-coverage.sh new file mode 100755 index 0000000..5095027 --- /dev/null +++ b/scripts/check-go-coverage.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +profile=${1:?usage: check-go-coverage.sh PROFILE MINIMUM} +minimum=${2:?usage: check-go-coverage.sh PROFILE MINIMUM} +coverage=$(go tool cover -func="$profile" | awk '/^total:/ {gsub("%", "", $3); print $3}') + +awk -v coverage="$coverage" -v minimum="$minimum" 'BEGIN { + if (coverage + 0 < minimum + 0) { + printf "coverage %.1f%% is below required %.1f%%\n", coverage, minimum > "/dev/stderr" + exit 1 + } + printf "coverage %.1f%% meets required %.1f%%\n", coverage, minimum +}' diff --git a/sdk/go/client.go b/sdk/go/client.go index 54fa2bb..e85c70e 100644 --- a/sdk/go/client.go +++ b/sdk/go/client.go @@ -11,7 +11,7 @@ import ( "strings" "time" - runmeshv1 "github.com/runmesh/runmesh/gen/runmesh/v1" + runmeshv1 "github.com/samarth1412/RunMesh/gen/runmesh/v1" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -126,7 +126,15 @@ func (c *Client) Complete(ctx context.Context, id string, output any) (Task, err if err != nil { return Task{}, err } - response, err := c.worker.Complete(c.rpcContext(ctx), &runmeshv1.CompleteRequest{TaskRunId: id, WorkerId: c.workerID, Output: structured, OutputArtifactUri: artifactURI}) + logData, err := json.Marshal(map[string]any{"task_run_id": id, "status": "SUCCEEDED"}) + if err != nil { + return Task{}, err + } + logArtifactURI, err := c.UploadArtifact(ctx, id, "log", "application/json", logData) + if err != nil { + return Task{}, fmt.Errorf("upload task log: %w", err) + } + response, err := c.worker.Complete(c.rpcContext(ctx), &runmeshv1.CompleteRequest{TaskRunId: id, WorkerId: c.workerID, Output: structured, OutputArtifactUri: artifactURI, LogArtifactUri: logArtifactURI}) if err != nil { return Task{}, err } diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 8f43ffd..43db4b5 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -10,11 +10,12 @@ requires-python = ">=3.11" dependencies = ["aiohttp>=3.11,<4", "aiokafka>=0.12,<1", "grpcio>=1.74,<2", "protobuf>=7,<8"] [project.optional-dependencies] -dev = ["pytest>=8", "pytest-asyncio>=0.25", "ruff>=0.9", "mypy>=1.14", "types-grpcio", "types-protobuf"] +dev = ["pytest>=8", "pytest-asyncio>=0.25", "pytest-cov>=7,<8", "ruff>=0.9", "mypy>=1.14", "types-grpcio", "types-protobuf"] [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] +addopts = "--cov=runmesh.worker --cov-report=term-missing --cov-report=xml --cov-fail-under=40" [tool.ruff] line-length = 100 diff --git a/sdk/python/runmesh/v1/worker_pb2.py b/sdk/python/runmesh/v1/worker_pb2.py index 6407a03..6d4047e 100644 --- a/sdk/python/runmesh/v1/worker_pb2.py +++ b/sdk/python/runmesh/v1/worker_pb2.py @@ -26,14 +26,14 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17runmesh/v1/worker.proto\x12\nrunmesh.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"K\n\x0cLeaseRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\"=\n\rLeaseResponse\x12,\n\x04task\x18\x01 \x01(\x0b\x32\x18.runmesh.v1.TaskSnapshotR\x04task\"K\n\x0cStartRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\"=\n\rStartResponse\x12,\n\x04task\x18\x01 \x01(\x0b\x32\x18.runmesh.v1.TaskSnapshotR\x04task\"O\n\x10HeartbeatRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\"w\n\x11HeartbeatResponse\x12\x44\n\x10lease_expires_at\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12\x1c\n\tcancelled\x18\x02 \x01(\x08R\tcancelled\"\xd9\x01\n\x0f\x43ompleteRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\x12/\n\x06output\x18\x03 \x01(\x0b\x32\x17.google.protobuf.StructR\x06output\x12.\n\x13output_artifact_uri\x18\x04 \x01(\tR\x11outputArtifactUri\x12(\n\x10log_artifact_uri\x18\x05 \x01(\tR\x0elogArtifactUri\"@\n\x10\x43ompleteResponse\x12,\n\x04task\x18\x01 \x01(\x0b\x32\x18.runmesh.v1.TaskSnapshotR\x04task\"\xc7\x01\n\x0b\x46\x61ilRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\x12\x1c\n\tretryable\x18\x03 \x01(\x08R\tretryable\x12\x1d\n\nerror_type\x18\x04 \x01(\tR\terrorType\x12#\n\rerror_message\x18\x05 \x01(\tR\x0c\x65rrorMessage\x12\x19\n\x08trace_id\x18\x06 \x01(\tR\x07traceId\"<\n\x0c\x46\x61ilResponse\x12,\n\x04task\x18\x01 \x01(\x0b\x32\x18.runmesh.v1.TaskSnapshotR\x04task\"\xb3\x02\n\x0cTaskSnapshot\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12&\n\x0fworkflow_run_id\x18\x02 \x01(\tR\rworkflowRunId\x12\x19\n\x08task_key\x18\x03 \x01(\tR\x07taskKey\x12\x18\n\x07handler\x18\x04 \x01(\tR\x07handler\x12-\n\x05input\x18\x05 \x01(\x0b\x32\x17.google.protobuf.StructR\x05input\x12\x18\n\x07\x61ttempt\x18\x06 \x01(\x05R\x07\x61ttempt\x12\'\n\x0ftimeout_seconds\x18\x07 \x01(\x05R\x0etimeoutSeconds\x12\x16\n\x06status\x18\x08 \x01(\tR\x06status\x12,\n\x12input_artifact_uri\x18\t \x01(\tR\x10inputArtifactUri\"\xd9\x01\n\x1b\x43reateArtifactUploadRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\x12\x12\n\x04kind\x18\x03 \x01(\tR\x04kind\x12!\n\x0c\x63ontent_type\x18\x04 \x01(\tR\x0b\x63ontentType\x12\x1d\n\nsize_bytes\x18\x05 \x01(\x03R\tsizeBytes\x12\'\n\x0f\x63hecksum_sha256\x18\x06 \x01(\tR\x0e\x63hecksumSha256\"\xc9\x02\n\x1c\x43reateArtifactUploadResponse\x12\x1f\n\x0b\x61rtifact_id\x18\x01 \x01(\tR\nartifactId\x12!\n\x0c\x61rtifact_uri\x18\x02 \x01(\tR\x0b\x61rtifactUri\x12\x1d\n\nupload_url\x18\x03 \x01(\tR\tuploadUrl\x12O\n\x07headers\x18\x04 \x03(\x0b\x32\x35.runmesh.v1.CreateArtifactUploadResponse.HeadersEntryR\x07headers\x12\x39\n\nexpires_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\texpiresAt\x1a:\n\x0cHeadersEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"}\n\x1d\x43ompleteArtifactUploadRequest\x12\x1f\n\x0b\x61rtifact_id\x18\x01 \x01(\tR\nartifactId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\x12\x1e\n\x0btask_run_id\x18\x03 \x01(\tR\ttaskRunId\"C\n\x1e\x43ompleteArtifactUploadResponse\x12!\n\x0c\x61rtifact_uri\x18\x01 \x01(\tR\x0b\x61rtifactUri\"|\n\x1aGetArtifactDownloadRequest\x12!\n\x0c\x61rtifact_uri\x18\x01 \x01(\tR\x0b\x61rtifactUri\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\x12\x1e\n\x0btask_run_id\x18\x03 \x01(\tR\ttaskRunId\"{\n\x1bGetArtifactDownloadResponse\x12!\n\x0c\x64ownload_url\x18\x01 \x01(\tR\x0b\x64ownloadUrl\x12\x39\n\nexpires_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\texpiresAt2\x9b\x05\n\rWorkerService\x12<\n\x05Lease\x12\x18.runmesh.v1.LeaseRequest\x1a\x19.runmesh.v1.LeaseResponse\x12<\n\x05Start\x12\x18.runmesh.v1.StartRequest\x1a\x19.runmesh.v1.StartResponse\x12H\n\tHeartbeat\x12\x1c.runmesh.v1.HeartbeatRequest\x1a\x1d.runmesh.v1.HeartbeatResponse\x12\x45\n\x08\x43omplete\x12\x1b.runmesh.v1.CompleteRequest\x1a\x1c.runmesh.v1.CompleteResponse\x12\x39\n\x04\x46\x61il\x12\x17.runmesh.v1.FailRequest\x1a\x18.runmesh.v1.FailResponse\x12i\n\x14\x43reateArtifactUpload\x12\'.runmesh.v1.CreateArtifactUploadRequest\x1a(.runmesh.v1.CreateArtifactUploadResponse\x12o\n\x16\x43ompleteArtifactUpload\x12).runmesh.v1.CompleteArtifactUploadRequest\x1a*.runmesh.v1.CompleteArtifactUploadResponse\x12\x66\n\x13GetArtifactDownload\x12&.runmesh.v1.GetArtifactDownloadRequest\x1a\'.runmesh.v1.GetArtifactDownloadResponseB5Z3github.com/runmesh/runmesh/gen/runmesh/v1;runmeshv1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17runmesh/v1/worker.proto\x12\nrunmesh.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"K\n\x0cLeaseRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\"=\n\rLeaseResponse\x12,\n\x04task\x18\x01 \x01(\x0b\x32\x18.runmesh.v1.TaskSnapshotR\x04task\"K\n\x0cStartRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\"=\n\rStartResponse\x12,\n\x04task\x18\x01 \x01(\x0b\x32\x18.runmesh.v1.TaskSnapshotR\x04task\"O\n\x10HeartbeatRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\"w\n\x11HeartbeatResponse\x12\x44\n\x10lease_expires_at\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12\x1c\n\tcancelled\x18\x02 \x01(\x08R\tcancelled\"\xd9\x01\n\x0f\x43ompleteRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\x12/\n\x06output\x18\x03 \x01(\x0b\x32\x17.google.protobuf.StructR\x06output\x12.\n\x13output_artifact_uri\x18\x04 \x01(\tR\x11outputArtifactUri\x12(\n\x10log_artifact_uri\x18\x05 \x01(\tR\x0elogArtifactUri\"@\n\x10\x43ompleteResponse\x12,\n\x04task\x18\x01 \x01(\x0b\x32\x18.runmesh.v1.TaskSnapshotR\x04task\"\xc7\x01\n\x0b\x46\x61ilRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\x12\x1c\n\tretryable\x18\x03 \x01(\x08R\tretryable\x12\x1d\n\nerror_type\x18\x04 \x01(\tR\terrorType\x12#\n\rerror_message\x18\x05 \x01(\tR\x0c\x65rrorMessage\x12\x19\n\x08trace_id\x18\x06 \x01(\tR\x07traceId\"<\n\x0c\x46\x61ilResponse\x12,\n\x04task\x18\x01 \x01(\x0b\x32\x18.runmesh.v1.TaskSnapshotR\x04task\"\xb3\x02\n\x0cTaskSnapshot\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12&\n\x0fworkflow_run_id\x18\x02 \x01(\tR\rworkflowRunId\x12\x19\n\x08task_key\x18\x03 \x01(\tR\x07taskKey\x12\x18\n\x07handler\x18\x04 \x01(\tR\x07handler\x12-\n\x05input\x18\x05 \x01(\x0b\x32\x17.google.protobuf.StructR\x05input\x12\x18\n\x07\x61ttempt\x18\x06 \x01(\x05R\x07\x61ttempt\x12\'\n\x0ftimeout_seconds\x18\x07 \x01(\x05R\x0etimeoutSeconds\x12\x16\n\x06status\x18\x08 \x01(\tR\x06status\x12,\n\x12input_artifact_uri\x18\t \x01(\tR\x10inputArtifactUri\"\xd9\x01\n\x1b\x43reateArtifactUploadRequest\x12\x1e\n\x0btask_run_id\x18\x01 \x01(\tR\ttaskRunId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\x12\x12\n\x04kind\x18\x03 \x01(\tR\x04kind\x12!\n\x0c\x63ontent_type\x18\x04 \x01(\tR\x0b\x63ontentType\x12\x1d\n\nsize_bytes\x18\x05 \x01(\x03R\tsizeBytes\x12\'\n\x0f\x63hecksum_sha256\x18\x06 \x01(\tR\x0e\x63hecksumSha256\"\xc9\x02\n\x1c\x43reateArtifactUploadResponse\x12\x1f\n\x0b\x61rtifact_id\x18\x01 \x01(\tR\nartifactId\x12!\n\x0c\x61rtifact_uri\x18\x02 \x01(\tR\x0b\x61rtifactUri\x12\x1d\n\nupload_url\x18\x03 \x01(\tR\tuploadUrl\x12O\n\x07headers\x18\x04 \x03(\x0b\x32\x35.runmesh.v1.CreateArtifactUploadResponse.HeadersEntryR\x07headers\x12\x39\n\nexpires_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\texpiresAt\x1a:\n\x0cHeadersEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"}\n\x1d\x43ompleteArtifactUploadRequest\x12\x1f\n\x0b\x61rtifact_id\x18\x01 \x01(\tR\nartifactId\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\x12\x1e\n\x0btask_run_id\x18\x03 \x01(\tR\ttaskRunId\"C\n\x1e\x43ompleteArtifactUploadResponse\x12!\n\x0c\x61rtifact_uri\x18\x01 \x01(\tR\x0b\x61rtifactUri\"|\n\x1aGetArtifactDownloadRequest\x12!\n\x0c\x61rtifact_uri\x18\x01 \x01(\tR\x0b\x61rtifactUri\x12\x1b\n\tworker_id\x18\x02 \x01(\tR\x08workerId\x12\x1e\n\x0btask_run_id\x18\x03 \x01(\tR\ttaskRunId\"{\n\x1bGetArtifactDownloadResponse\x12!\n\x0c\x64ownload_url\x18\x01 \x01(\tR\x0b\x64ownloadUrl\x12\x39\n\nexpires_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\texpiresAt2\x9b\x05\n\rWorkerService\x12<\n\x05Lease\x12\x18.runmesh.v1.LeaseRequest\x1a\x19.runmesh.v1.LeaseResponse\x12<\n\x05Start\x12\x18.runmesh.v1.StartRequest\x1a\x19.runmesh.v1.StartResponse\x12H\n\tHeartbeat\x12\x1c.runmesh.v1.HeartbeatRequest\x1a\x1d.runmesh.v1.HeartbeatResponse\x12\x45\n\x08\x43omplete\x12\x1b.runmesh.v1.CompleteRequest\x1a\x1c.runmesh.v1.CompleteResponse\x12\x39\n\x04\x46\x61il\x12\x17.runmesh.v1.FailRequest\x1a\x18.runmesh.v1.FailResponse\x12i\n\x14\x43reateArtifactUpload\x12\'.runmesh.v1.CreateArtifactUploadRequest\x1a(.runmesh.v1.CreateArtifactUploadResponse\x12o\n\x16\x43ompleteArtifactUpload\x12).runmesh.v1.CompleteArtifactUploadRequest\x1a*.runmesh.v1.CompleteArtifactUploadResponse\x12\x66\n\x13GetArtifactDownload\x12&.runmesh.v1.GetArtifactDownloadRequest\x1a\'.runmesh.v1.GetArtifactDownloadResponseB9Z7github.com/samarth1412/RunMesh/gen/runmesh/v1;runmeshv1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'runmesh.v1.worker_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'Z3github.com/runmesh/runmesh/gen/runmesh/v1;runmeshv1' + _globals['DESCRIPTOR']._serialized_options = b'Z7github.com/samarth1412/RunMesh/gen/runmesh/v1;runmeshv1' _globals['_CREATEARTIFACTUPLOADRESPONSE_HEADERSENTRY']._loaded_options = None _globals['_CREATEARTIFACTUPLOADRESPONSE_HEADERSENTRY']._serialized_options = b'8\001' _globals['_LEASEREQUEST']._serialized_start=102 diff --git a/sdk/python/runmesh/worker.py b/sdk/python/runmesh/worker.py index f66c263..c7dcb14 100644 --- a/sdk/python/runmesh/worker.py +++ b/sdk/python/runmesh/worker.py @@ -234,6 +234,13 @@ async def _process( dispatch = json.loads(message.value) task_id = dispatch["task_run_id"] trace_parent = _header(message, "traceparent") + handler = self._handlers.get(dispatch["handler"]) + if handler is None: + # Heterogeneous capability pools use distinct consumer groups. This + # group acknowledges handlers it does not own without claiming the + # database task; a capable group can acquire the durable lease. + await consumer.commit() + return try: task = await self._post( session, f"/internal/v1/tasks/{task_id}/lease", {}, trace_parent=trace_parent @@ -256,9 +263,6 @@ async def _process( ) heartbeat = asyncio.create_task(self._heartbeat(session, context)) try: - handler = self._handlers.get(task["handler"]) - if handler is None: - raise RetryableError(f"worker does not provide handler {task['handler']!r}") task_input = task["input"] if task.get("input_artifact_uri"): task_input = json.loads( diff --git a/sdk/python/tests/test_worker.py b/sdk/python/tests/test_worker.py index 14f388b..95eae5d 100644 --- a/sdk/python/tests/test_worker.py +++ b/sdk/python/tests/test_worker.py @@ -98,3 +98,25 @@ async def test_worker_heartbeat_payload_excludes_task_worker_id() -> None: ) assert session.payload == payload + + +async def test_unsupported_handler_is_not_leased() -> None: + class Consumer: + committed = False + + async def commit(self) -> None: + self.committed = True + + class Message: + value = b'{"task_run_id":"task-1","handler":"documents.unknown"}' + headers: list[tuple[str, bytes]] = [] + + worker = Worker("localhost:7001", "token", worker_id="worker-1") + consumer = Consumer() + await worker._process( + cast(aiohttp.ClientSession, object()), + cast(Any, consumer), + cast(Any, Message()), + ) + assert consumer.committed + assert worker._grpc_stub is None diff --git a/tests/benchmarks/local.py b/tests/benchmarks/local.py index 2d0e8de..0eff4ac 100755 --- a/tests/benchmarks/local.py +++ b/tests/benchmarks/local.py @@ -5,7 +5,6 @@ import argparse import json -import statistics import sys import time import urllib.error @@ -61,6 +60,20 @@ def create_workflow(api: str, token: str, name: str, task_count: int, handler: s return payload["id"] +def latency_summary(values: list[float]) -> dict[str, float]: + ordered = sorted(values) + + def percentile(value: float) -> float: + index = min(len(ordered) - 1, max(0, round((len(ordered) - 1) * value))) + return round(ordered[index] * 1000, 3) + + return { + "p50_ms": percentile(0.50), + "p95_ms": percentile(0.95), + "p99_ms": percentile(0.99), + } + + def active_workflows(api: str, token: str, count: int, prefix: str) -> dict: workflow_id = create_workflow(api, token, f"{prefix}-definition", 1, "examples.slow") latencies: list[float] = [] @@ -82,15 +95,17 @@ def active_workflows(api: str, token: str, count: int, prefix: str) -> dict: "workflow_id": workflow_id, "idempotency_prefix": prefix, "elapsed_seconds": round(elapsed, 3), - "create_p95_ms": round(statistics.quantiles(latencies, n=100)[94] * 1000, 3), + "create_latency": latency_summary(latencies), + "failure_count": 0, + "failure_rate": 0, "sample_run_ids": run_ids[:5], } def prepare_tasks(api: str, token: str, tasks: int, prefix: str) -> dict: - per_run = 500 + per_run = 100 if tasks % per_run: - raise ValueError("task total must be divisible by 500") + raise ValueError("task total must be divisible by 100") workflow_id = create_workflow(api, token, f"{prefix}-definition", per_run, "examples.slow") runs = [] started = time.perf_counter() @@ -131,7 +146,7 @@ def duplicate_submission(api: str, token: str, prefix: str) -> dict: def main() -> None: parser = argparse.ArgumentParser() - parser.add_argument("command", choices=("token", "active", "prepare", "duplicate")) + parser.add_argument("command", choices=("token", "definition", "active", "prepare", "duplicate")) parser.add_argument("--api", default="http://localhost:8080") parser.add_argument("--identity", default="http://localhost:8180/realms/runmesh/protocol/openid-connect/token") parser.add_argument("--count", type=int, default=1000) @@ -141,6 +156,8 @@ def main() -> None: token = oidc_token(args.identity) if args.command == "token": print(token) + elif args.command == "definition": + print(create_workflow(args.api, token, f"{args.prefix}-definition", 1, "examples.greet")) elif args.command == "active": print(json.dumps(active_workflows(args.api, token, args.count, args.prefix), indent=2, sort_keys=True)) elif args.command == "prepare": diff --git a/tests/benchmarks/results/2026-07-20T235240Z/README.md b/tests/benchmarks/results/2026-07-20T235240Z/README.md new file mode 100644 index 0000000..16713c8 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/README.md @@ -0,0 +1,10 @@ +# Superseded local benchmark — 2026-07-20T235240Z + +Source commit: `7eebd569e63768d853f37484ef121ed77de82234` + +The blocking correctness verifier passed, including 10,000/10,000 successful +tasks, five recovered leases, zero permanent task loss, and records on all six +Kafka partitions. This run is retained as raw historical evidence but is not +used for README performance claims: its k6 summaries did not include p99 and the +worker topology was not normalized enough to produce representative partition +balance. The raw JSON and copied `commands.sh` have not been edited. diff --git a/tests/benchmarks/results/2026-07-20T235240Z/active-workflows-database.json b/tests/benchmarks/results/2026-07-20T235240Z/active-workflows-database.json new file mode 100644 index 0000000..756a691 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/active-workflows-database.json @@ -0,0 +1 @@ +{"active_runs" : 1000, "active_tasks" : 1000} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/active-workflows.json b/tests/benchmarks/results/2026-07-20T235240Z/active-workflows.json new file mode 100644 index 0000000..5ece95b --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/active-workflows.json @@ -0,0 +1,22 @@ +{ + "create_latency": { + "p50_ms": 5.375, + "p95_ms": 7.467, + "p99_ms": 8.129 + }, + "created": 1000, + "elapsed_seconds": 18.276, + "failure_count": 0, + "failure_rate": 0, + "idempotency_prefix": "validation-active-20260720T235240Z", + "requested": 1000, + "sample_run_ids": [ + "44486141-86ef-4dce-910b-bf8f1410ba44", + "b5f126b2-35ea-4ec0-b460-c37035607f29", + "afe4ccf6-2a96-465b-8bea-a4f2e3228833", + "ad2c481d-0e69-4c0d-bfbc-efd986622241", + "eb0e1948-c79f-4b58-8b8a-a1119b7a8513" + ], + "scenario": "simultaneously_active_workflows", + "workflow_id": "580243d4-281d-49be-b7be-d1ac09900941" +} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/api-read.json b/tests/benchmarks/results/2026-07-20T235240Z/api-read.json new file mode 100644 index 0000000..c8b1455 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/api-read.json @@ -0,0 +1,133 @@ +{ + "metrics": { + "vus_max": { + "value": 40, + "min": 40, + "max": 40 + }, + "data_received": { + "count": 443898917, + "rate": 14794010.670660315 + }, + "data_sent": { + "count": 4117372, + "rate": 137221.43256113867 + }, + "http_req_connecting": { + "med": 0, + "max": 1.152667, + "p(90)": 0, + "p(95)": 0, + "avg": 0.00664585338220593, + "min": 0 + }, + "http_req_duration": { + "p(90)": 4.059875, + "p(95)": 4.534834, + "avg": 4.9203696831056405, + "min": 2.535084, + "med": 3.405334, + "max": 284.762084 + }, + "http_reqs": { + "count": 3001, + "rate": 100.01562140024686 + }, + "http_req_receiving": { + "p(90)": 0.611625, + "p(95)": 0.719125, + "avg": 0.6360155671442832, + "min": 0.039208, + "med": 0.435792, + "max": 67.5735 + }, + "vus": { + "value": 0, + "min": 0, + "max": 15 + }, + "checks": { + "passes": 3001, + "fails": 0, + "thresholds": { + "rate==1": false + }, + "value": 1 + }, + "http_req_blocked": { + "avg": 0.012584953682105753, + "min": 0.000958, + "med": 0.001958, + "max": 6.008375, + "p(90)": 0.003917, + "p(95)": 0.006 + }, + "http_req_tls_handshaking": { + "avg": 0, + "min": 0, + "med": 0, + "max": 0, + "p(90)": 0, + "p(95)": 0 + }, + "iteration_duration": { + "min": 2.589125, + "med": 3.482584, + "max": 288.942541, + "p(90)": 4.166958, + "p(95)": 4.705833, + "avg": 5.021869461846045 + }, + "iterations": { + "count": 3001, + "rate": 100.01562140024686 + }, + "http_req_duration{expected_response:true}": { + "p(95)": 4.534834, + "avg": 4.9203696831056405, + "min": 2.535084, + "med": 3.405334, + "max": 284.762084, + "p(90)": 4.059875 + }, + "http_req_waiting": { + "avg": 4.270925927024329, + "min": 2.117959, + "med": 2.950542, + "max": 267.34475, + "p(90)": 3.570791, + "p(95)": 3.979583 + }, + "http_req_failed": { + "fails": 3001, + "passes": 0, + "thresholds": { + "rate==0": false + }, + "value": 0 + }, + "http_req_sending": { + "avg": 0.013428188937021007, + "min": 0.003208, + "med": 0.008958, + "max": 5.422375, + "p(90)": 0.015459, + "p(95)": 0.02025 + } + }, + "root_group": { + "name": "", + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": {}, + "checks": { + "authenticated request succeeded": { + "name": "authenticated request succeeded", + "path": "::authenticated request succeeded", + "id": "12f4c1307352dc7d8175954f46790cf7", + "passes": 3001, + "fails": 0 + } + } + } +} \ No newline at end of file diff --git a/tests/benchmarks/results/2026-07-20T235240Z/commands.sh b/tests/benchmarks/results/2026-07-20T235240Z/commands.sh new file mode 100755 index 0000000..40fd3f3 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/commands.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Runs only against the local Compose project. It never provisions cloud +# resources. Raw result directories are immutable: choose a new path to rerun. +result_dir=${1:-tests/benchmarks/results/$(date -u +%Y-%m-%dT%H%M%SZ)} +if [[ -e "$result_dir" ]]; then + echo "result directory already exists: $result_dir" >&2 + exit 1 +fi +mkdir -p "$result_dir" +run_suffix=$(date -u +%Y%m%dT%H%M%SZ) +active_prefix="validation-active-$run_suffix" +crash_prefix="validation-crash-$run_suffix" +duplicate_prefix="validation-duplicate-$run_suffix" + +cp "$0" "$result_dir/commands.sh" +docker compose up -d --build +for endpoint in \ + http://localhost:8180/realms/runmesh/.well-known/openid-configuration \ + http://localhost:8080/health/ready; do + ready=0 + for _ in $(seq 1 90); do + if curl -fsS "$endpoint" >/dev/null; then ready=1; break; fi + sleep 2 + done + [[ "$ready" == 1 ]] || { echo "benchmark dependency did not become ready: $endpoint" >&2; exit 1; } +done +benchmark_token=$(python3 tests/benchmarks/local.py token) + +docker compose exec -T redpanda rpk topic describe runmesh.tasks --format json > "$result_dir/kafka-topology-before.json" +docker run --rm --add-host host.docker.internal:host-gateway \ + -e RUNMESH_API=http://host.docker.internal:8080 \ + -e RUNMESH_TOKEN="$benchmark_token" -e RUNMESH_RATE=100 -e RUNMESH_DURATION=30s \ + -v "$PWD/tests/load":/scripts:ro -v "$PWD/$result_dir":/results \ + grafana/k6:0.57.0 run --summary-export=/results/api-read.json /scripts/api-read.js + +sleep 3 +submission_workflow=$(python3 tests/benchmarks/local.py definition --prefix "validation-submission-$run_suffix") +docker run --rm --add-host host.docker.internal:host-gateway \ + -e RUNMESH_API=http://host.docker.internal:8080 \ + -e RUNMESH_TOKEN="$benchmark_token" -e RUNMESH_WORKFLOW_ID="$submission_workflow" \ + -e RUNMESH_RATE=50 -e RUNMESH_DURATION=30s \ + -v "$PWD/tests/load":/scripts:ro -v "$PWD/$result_dir":/results \ + grafana/k6:0.57.0 run --summary-export=/results/workflow-submissions.json /scripts/submissions.js + +docker compose stop scheduler worker-python worker-go +python3 tests/benchmarks/local.py active --count 1000 --prefix "$active_prefix" > "$result_dir/active-workflows.json" +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('active_runs',count(*),'active_tasks',sum(task_count)) FROM (SELECT wr.id,count(tr.id) task_count FROM workflow_runs wr JOIN task_runs tr ON tr.workflow_run_id=wr.id WHERE wr.idempotency_key LIKE '$active_prefix-%' AND wr.status='RUNNING' GROUP BY wr.id) rows" \ + > "$result_dir/active-workflows-database.json" + +# Start two schedulers and twelve workers against six partitions, then measure +# drain throughput for the 1,000 distinct workflow keys created above. +completion_start=$(python3 -c 'import time; print(time.time_ns())') +docker compose up -d --scale scheduler=2 scheduler +docker compose up -d --build --scale worker-go=12 worker-go +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM workflow_runs WHERE idempotency_key LIKE '$active_prefix-%' AND status NOT IN ('SUCCEEDED','FAILED','CANCELLED')") != 0 ]]; do sleep 0.2; done +completion_end=$(python3 -c 'import time; print(time.time_ns())') +python3 - "$completion_start" "$completion_end" > "$result_dir/end-to-end-completion.json" <<'PY' +import json, sys +elapsed = (int(sys.argv[2]) - int(sys.argv[1])) / 1_000_000_000 +print(json.dumps({"scenario":"queued_end_to_end_completion","tasks":1000,"elapsed_seconds":round(elapsed,3),"tasks_per_second":round(1000/elapsed,3)}, indent=2)) +PY +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('tasks',count(*),'succeeded',count(*) FILTER (WHERE tr.status='SUCCEEDED'),'other',count(*) FILTER (WHERE tr.status!='SUCCEEDED')) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$active_prefix-%'" \ + > "$result_dir/end-to-end-completion-database.json" + +# Begin at the current topic end so this scenario observes only its own work. +docker compose stop worker-go +docker compose exec -T redpanda rpk group seek runmesh-workers --to end --topics runmesh.tasks >/dev/null +python3 tests/benchmarks/local.py prepare --tasks 10000 --prefix "$crash_prefix" > "$result_dir/worker-crash-setup.json" +dispatch_start=$(python3 -c 'import time; print(time.time_ns())') +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status IN ('READY','BLOCKED')") != 0 ]]; do sleep 0.1; done +dispatch_end=$(python3 -c 'import time; print(time.time_ns())') +python3 - "$dispatch_start" "$dispatch_end" > "$result_dir/scheduler-dispatch.json" <<'PY' +import json, sys +elapsed = (int(sys.argv[2]) - int(sys.argv[1])) / 1_000_000_000 +print(json.dumps({"scenario":"scheduler_dispatch","tasks":10000,"elapsed_seconds":round(elapsed,3),"dispatches_per_second":round(10000/elapsed,3)}, indent=2)) +PY + +# Keep enough tasks running to occupy every available Kafka partition before +# terminating the entire 20-container worker pool. +docker compose exec -T postgres psql -U runmesh -d runmesh -c \ + "WITH selected AS (SELECT tr.id FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' ORDER BY tr.id LIMIT 100) UPDATE task_runs SET input='{\"delay_ms\":5000}'::jsonb WHERE id IN (SELECT id FROM selected);" >/dev/null +docker compose up -d --build --scale worker-go=20 worker-go +deadline=$((SECONDS+60)) +while true; do + running=$(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status='RUNNING'") + [[ "$running" -ge 4 ]] && break + [[ "$SECONDS" -ge "$deadline" ]] && { echo "fewer than four tasks became concurrently RUNNING" >&2; exit 1; } + sleep 0.1 +done +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('running_count',count(*),'task_ids',json_agg(id ORDER BY id)) FROM (SELECT tr.id FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status='RUNNING') active" \ + > "$result_dir/in-flight-before-termination.json" +worker_ids=( $(docker compose ps -q worker-go) ) +docker kill "${worker_ids[@]}" >/dev/null +python3 - "${#worker_ids[@]}" "$crash_prefix" "$running" > "$result_dir/worker-termination.json" <<'PY' +import datetime, json, sys +print(json.dumps({"terminated_workers":int(sys.argv[1]),"idempotency_prefix":sys.argv[2],"in_flight_tasks":int(sys.argv[3]),"terminated_at":datetime.datetime.now(datetime.timezone.utc).isoformat()}, indent=2)) +PY +recovery_start=$(python3 -c 'import time; print(time.time_ns())') +docker compose up -d --scale worker-go=20 worker-go +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM workflow_runs WHERE idempotency_key LIKE '$crash_prefix-%' AND status NOT IN ('SUCCEEDED','FAILED','CANCELLED')") != 0 ]]; do sleep 1; done +recovery_end=$(python3 -c 'import time; print(time.time_ns())') +recovery_seconds=$(python3 -c "print(round(($recovery_end-$recovery_start)/1_000_000_000,3))") +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('scenario','worker_termination_and_lease_recovery','tasks',count(*),'succeeded',count(*) FILTER (WHERE tr.status='SUCCEEDED'),'other',count(*) FILTER (WHERE tr.status!='SUCCEEDED'),'attempts',sum(tr.attempt_count),'recovered_tasks',count(*) FILTER (WHERE tr.attempt_count > 1),'duplicate_attempts',sum(tr.attempt_count)-count(*),'recovery_seconds',$recovery_seconds) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%'" \ + > "$result_dir/worker-crash-result.json" +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "WITH gaps AS (SELECT EXTRACT(EPOCH FROM (next_attempt.started_at-expired.ended_at))*1000 milliseconds FROM task_attempts expired JOIN task_attempts next_attempt ON next_attempt.task_run_id=expired.task_run_id AND next_attempt.attempt_number=expired.attempt_number+1 JOIN task_runs tr ON tr.id=expired.task_run_id JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND expired.error_type='LeaseExpired') SELECT json_build_object('samples',count(*),'p50_ms',percentile_cont(0.50) WITHIN GROUP (ORDER BY milliseconds),'p95_ms',percentile_cont(0.95) WITHIN GROUP (ORDER BY milliseconds),'p99_ms',percentile_cont(0.99) WITHIN GROUP (ORDER BY milliseconds)) FROM gaps" \ + > "$result_dir/lease-recovery-distribution.json" + +python3 tests/benchmarks/local.py duplicate --prefix "$duplicate_prefix" > "$result_dir/duplicate-submission.json" +docker run --rm -v "$PWD":/src -w /src -v /var/run/docker.sock:/var/run/docker.sock \ + -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal golang:1.25 \ + /usr/local/go/bin/go test -tags=integration -count=1 -run TestDuplicateDispatchIsLeasedOnce -json ./tests/integration \ + > "$result_dir/duplicate-delivery-go-test.json" + +docker compose images --format json > "$result_dir/image-digests.json" +docker compose ps --format json > "$result_dir/compose-topology.json" +docker compose exec -T redpanda rpk topic describe runmesh.tasks --format json > "$result_dir/kafka-topology-after.json" +{ + uname -a + sysctl -n machdep.cpu.brand_string 2>/dev/null || true + sysctl -n hw.memsize 2>/dev/null || true + docker version --format '{{json .}}' +} > "$result_dir/hardware.txt" +git rev-parse HEAD > "$result_dir/commit-sha.txt" +python3 tests/benchmarks/verify.py "$result_dir" > "$result_dir/verification.json" + +echo "Benchmark evidence written to $result_dir" diff --git a/tests/benchmarks/results/2026-07-20T235240Z/commit-sha.txt b/tests/benchmarks/results/2026-07-20T235240Z/commit-sha.txt new file mode 100644 index 0000000..84c811a --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/commit-sha.txt @@ -0,0 +1 @@ +7eebd569e63768d853f37484ef121ed77de82234 diff --git a/tests/benchmarks/results/2026-07-20T235240Z/compose-topology.json b/tests/benchmarks/results/2026-07-20T235240Z/compose-topology.json new file mode 100644 index 0000000..fd0cb9b --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/compose-topology.json @@ -0,0 +1,35 @@ +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:20 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"395b47d730a1","Image":"runmesh-control-plane","Labels":"com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=control-plane,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=9fe03b71a2356bebdc17c98f565a44f48023721adb5ec2a83aba50c19bd4f54d,com.docker.compose.container-number=1,com.docker.compose.oneoff=False,com.docker.compose.replace=control-plane-1,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/7001/tcp=:7001,desktop.docker.io/ports/8080/tcp=:8080,com.docker.compose.depends_on=migrate:service_completed_successfully:false,redpanda:service_healthy:false,redis:service_healthy:false,minio:service_healthy:false,keycloak:service_started:false,com.docker.compose.image=sha256:a4e6d9e8b938469d7a63f29c71ebe5cb6e08ba826e7d6c29507bbd2c1ee2bece,com.docker.compose.project=runmesh","LocalVolumes":"0","Mounts":"","Name":"runmesh-control-plane-1","Names":"runmesh-control-plane-1","Networks":"runmesh_default","Ports":"0.0.0.0:7001-\u003e7001/tcp, [::]:7001-\u003e7001/tcp, 0.0.0.0:8080-\u003e8080/tcp, [::]:8080-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":7001,"PublishedPort":7001,"Protocol":"tcp"},{"URL":"::","TargetPort":7001,"PublishedPort":7001,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8080,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8080,"Protocol":"tcp"}],"RunningFor":"7 minutes ago","Service":"control-plane","Size":"0B","State":"running","Status":"Up 7 minutes (healthy)"} +{"Command":"\"/run.sh\"","CreatedAt":"2026-07-20 19:51:28 -0400 EDT","ExitCode":0,"Health":"","ID":"0e46de4c0532","Image":"grafana/grafana:11.4.0","Labels":"desktop.docker.io/binds/1/SourceKind=hostFile,desktop.docker.io/binds/1/Target=/etc/grafana/provisioning,com.docker.compose.depends_on=prometheus:service_started:false,loki:service_started:false,tempo:service_started:false,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports/3000/tcp=:3001,com.docker.compose.config-hash=ede6cf07da09ed0d5759a176342e1da8e2d7164eeadeb235d2ead6763b183927,com.docker.compose.container-number=1,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/Target=/var/lib/grafana/dashboards,com.docker.compose.image=sha256:d8ea37798ccc41061a62ab080f2676dda6bf7815558499f901bdb0f533a456fb,com.docker.compose.service=grafana,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/grafana/dashboards,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/1/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/grafana/provisioning,desktop.docker.io/ports.scheme=v2,maintainer=Grafana Labs \u003chello@grafana.com\u003e,com.docker.compose.oneoff=False","LocalVolumes":"0","Mounts":"/host_mnt/User…,/host_mnt/User…","Name":"runmesh-grafana-1","Names":"runmesh-grafana-1","Networks":"runmesh_default","Ports":"0.0.0.0:3001-\u003e3000/tcp, [::]:3001-\u003e3000/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3000,"PublishedPort":3001,"Protocol":"tcp"},{"URL":"::","TargetPort":3000,"PublishedPort":3001,"Protocol":"tcp"}],"RunningFor":"11 minutes ago","Service":"grafana","Size":"0B","State":"running","Status":"Up 11 minutes"} +{"Command":"\"/opt/keycloak/bin/k…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"18044859ec4f","Image":"quay.io/keycloak/keycloak:26.1","Labels":"com.docker.compose.project=runmesh,com.docker.compose.version=5.3.0,io.openshift.expose-services=,io.openshift.tags=keycloak security identity,org.opencontainers.image.description=,org.opencontainers.image.url=https://github.com/keycloak-rel/keycloak-rel,org.opencontainers.image.version=26.1.5,vcs-type=git,architecture=aarch64,build-date=2025-04-08T13:14:37Z,com.docker.compose.oneoff=False,com.docker.compose.service=keycloak,vendor=https://www.keycloak.org/,com.redhat.component=,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/keycloak,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/opt/keycloak/data/import,desktop.docker.io/ports.scheme=v2,maintainer=https://www.keycloak.org/,org.opencontainers.image.created=2025-04-11T07:58:44.198Z,version=26.1.5,com.redhat.license_terms=,org.opencontainers.image.licenses=Apache-2.0,org.opencontainers.image.title=keycloak-rel,release=,com.docker.compose.config-hash=cebace789f94dee1045ba9e8f2f123fa3a9807bdde96bcc4af83324d70c53a7e,com.docker.compose.container-number=1,distribution-scope=public,org.opencontainers.image.documentation=https://www.keycloak.org/documentation,org.opencontainers.image.source=https://github.com/keycloak-rel/keycloak-rel,summary=Keycloak Server Image,url=https://www.keycloak.org/,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,io.buildah.version=1.39.0-dev,name=keycloak,com.docker.compose.depends_on=,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.redhat.build-host=,org.opencontainers.image.revision=e7109eeda3e2630bb65640a6a322f0cb6ff6ddd6,vcs-ref=,com.docker.compose.image=sha256:be6a86215213145bfb4fb3e2b3ab982a806d00262655abdcf3ffa6a38d241c7c,description=Keycloak Server Image,desktop.docker.io/ports/8080/tcp=:8180,io.k8s.description=Keycloak Server Image,io.k8s.display-name=Keycloak Server","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-keycloak-1","Names":"runmesh-keycloak-1","Networks":"runmesh_default","Ports":"0.0.0.0:8180-\u003e8080/tcp, [::]:8180-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8180,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8180,"Protocol":"tcp"}],"RunningFor":"11 minutes ago","Service":"keycloak","Size":"0B","State":"running","Status":"Up 11 minutes"} +{"Command":"\"/usr/bin/loki -conf…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"5e38dd422042","Image":"grafana/loki:3.3.2","Labels":"com.docker.compose.container-number=1,com.docker.compose.image=sha256:8af2de1abbdd7aa92b27c9bcc96f0f4140c9096b507c77921ffddf1c6ad6c48f,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=loki,desktop.docker.io/ports/3100/tcp=:3100,com.docker.compose.config-hash=e3121c4e0132494435f1d1cb47ac059e2b1f2f206f8082c2382c39931a5c7960,com.docker.compose.depends_on=,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-loki-1","Names":"runmesh-loki-1","Networks":"runmesh_default","Ports":"0.0.0.0:3100-\u003e3100/tcp, [::]:3100-\u003e3100/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3100,"PublishedPort":3100,"Protocol":"tcp"},{"URL":"::","TargetPort":3100,"PublishedPort":3100,"Protocol":"tcp"}],"RunningFor":"11 minutes ago","Service":"loki","Size":"0B","State":"running","Status":"Up 11 minutes"} +{"Command":"\"/usr/bin/docker-ent…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"caa90c23d142","Image":"minio/minio:RELEASE.2025-02-07T23-21-09Z","Labels":"com.docker.compose.version=5.3.0,summary=MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service.,url=https://www.redhat.com,desktop.docker.io/ports.scheme=v2,io.k8s.description=Very small image which doesn't install the package manager.,vcs-ref=41bec8a95f8231b8982164a882f1b491a864dcce,com.docker.compose.config-hash=c2ef697a7845d5cfeaef7a64bd249c55b2816a78d92a7e6efbaf934addfbfbc9,com.docker.compose.container-number=1,com.docker.compose.image=sha256:640c22768ed5dbc92eacc14502a1b06a1c708fa60431345c78dfc22917062e93,description=MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads.,maintainer=MinIO Inc \u003cdev@min.io\u003e,release=RELEASE.2025-02-07T23-21-09Z,vendor=MinIO Inc \u003cdev@min.io\u003e,com.docker.compose.depends_on=,com.docker.compose.project=runmesh,io.k8s.display-name=Red Hat Universal Base Image 9 Micro,io.openshift.expose-services=,name=MinIO,com.docker.compose.service=minio,com.redhat.license_terms=https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI,desktop.docker.io/ports/9000/tcp=:9000,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.redhat.component=ubi9-micro-container,distribution-scope=public,architecture=aarch64,io.buildah.version=1.38.0-dev,version=RELEASE.2025-02-07T23-21-09Z,desktop.docker.io/ports/9001/tcp=:9001,vcs-type=git,build-date=2025-02-06T04:33:00Z","LocalVolumes":"1","Mounts":"runmesh_minio-…","Name":"runmesh-minio-1","Names":"runmesh-minio-1","Networks":"runmesh_default","Ports":"0.0.0.0:9000-9001-\u003e9000-9001/tcp, [::]:9000-9001-\u003e9000-9001/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9000,"PublishedPort":9000,"Protocol":"tcp"},{"URL":"::","TargetPort":9000,"PublishedPort":9000,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":9001,"PublishedPort":9001,"Protocol":"tcp"},{"URL":"::","TargetPort":9001,"PublishedPort":9001,"Protocol":"tcp"}],"RunningFor":"11 minutes ago","Service":"minio","Size":"0B","State":"running","Status":"Up 11 minutes (healthy)"} +{"Command":"\"/otelcol-contrib --…\"","CreatedAt":"2026-07-20 19:51:28 -0400 EDT","ExitCode":0,"Health":"","ID":"59bc0d9265b3","Image":"otel/opentelemetry-collector-contrib:0.119.0","Labels":"org.opencontainers.image.version=0.119.0,com.docker.compose.container-number=1,com.docker.compose.depends_on=tempo:service_started:false,loki:service_started:false,com.docker.compose.image=sha256:36c35cc213c0f3b64d6e8a3e844dc90822f00725e0e518eaed5b08bcc2231e72,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=otel-collector,desktop.docker.io/binds/0/Target=/etc/otelcol/config.yaml,org.opencontainers.image.source=https://github.com/open-telemetry/opentelemetry-collector-releases,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/4317/tcp=:4317,desktop.docker.io/ports/8889/tcp=:8889,org.opencontainers.image.licenses=Apache-2.0,org.opencontainers.image.name=opentelemetry-collector-releases,org.opencontainers.image.revision=c06573884854d06efc3ff0d91d76004d49c80a53,com.docker.compose.project=runmesh,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/ports/4318/tcp=:4318,org.opencontainers.image.created=2025-02-04T18:02:02Z,com.docker.compose.config-hash=30112846559126a16e248b6a9473d1a71f5ceee134a40eea9844fcbe6a58f386,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/otel-collector.yaml","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-otel-collector-1","Names":"runmesh-otel-collector-1","Networks":"runmesh_default","Ports":"0.0.0.0:4317-4318-\u003e4317-4318/tcp, [::]:4317-4318-\u003e4317-4318/tcp, 0.0.0.0:8889-\u003e8889/tcp, [::]:8889-\u003e8889/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":4317,"PublishedPort":4317,"Protocol":"tcp"},{"URL":"::","TargetPort":4317,"PublishedPort":4317,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":4318,"PublishedPort":4318,"Protocol":"tcp"},{"URL":"::","TargetPort":4318,"PublishedPort":4318,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":8889,"PublishedPort":8889,"Protocol":"tcp"},{"URL":"::","TargetPort":8889,"PublishedPort":8889,"Protocol":"tcp"}],"RunningFor":"11 minutes ago","Service":"otel-collector","Size":"0B","State":"running","Status":"Up 11 minutes"} +{"Command":"\"docker-entrypoint.s…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"c6e6fb97df3f","Image":"postgres:17-alpine","Labels":"com.docker.compose.depends_on=,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports/5432/tcp=:5432,com.docker.compose.config-hash=d3a0e466ee146131f19622bff6646dfd87ba3247ac4c064ec21f1013fca34f25,com.docker.compose.container-number=1,com.docker.compose.image=sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193,com.docker.compose.service=postgres,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"1","Mounts":"runmesh_postgr…","Name":"runmesh-postgres-1","Names":"runmesh-postgres-1","Networks":"runmesh_default","Ports":"0.0.0.0:5432-\u003e5432/tcp, [::]:5432-\u003e5432/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":5432,"PublishedPort":5432,"Protocol":"tcp"},{"URL":"::","TargetPort":5432,"PublishedPort":5432,"Protocol":"tcp"}],"RunningFor":"11 minutes ago","Service":"postgres","Size":"0B","State":"running","Status":"Up 11 minutes (healthy)"} +{"Command":"\"/bin/prometheus --c…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"fbeb2b4ea323","Image":"prom/prometheus:v3.1.0","Labels":"desktop.docker.io/binds/0/Target=/etc/prometheus/prometheus.yml,com.docker.compose.config-hash=cea444590d60d3b6b6325a4da1ec76a5309ba4a70dcc47d4bc24fdbb081da34d,com.docker.compose.image=sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/prometheus.yaml,desktop.docker.io/binds/0/SourceKind=hostFile,com.docker.compose.depends_on=,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/binds/1/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/prometheus-rules.yaml,desktop.docker.io/ports.scheme=v2,maintainer=The Prometheus Authors \u003cprometheus-developers@googlegroups.com\u003e,org.opencontainers.image.source=https://github.com/prometheus/prometheus,com.docker.compose.container-number=1,desktop.docker.io/binds/1/Target=/etc/prometheus/rules.yaml,desktop.docker.io/ports/9090/tcp=:9090,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=prometheus,desktop.docker.io/binds/1/SourceKind=hostFile","LocalVolumes":"1","Mounts":"/host_mnt/User…,/host_mnt/User…,488ec96d9441ad…","Name":"runmesh-prometheus-1","Names":"runmesh-prometheus-1","Networks":"runmesh_default","Ports":"0.0.0.0:9090-\u003e9090/tcp, [::]:9090-\u003e9090/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9090,"PublishedPort":9090,"Protocol":"tcp"},{"URL":"::","TargetPort":9090,"PublishedPort":9090,"Protocol":"tcp"}],"RunningFor":"11 minutes ago","Service":"prometheus","Size":"0B","State":"running","Status":"Up 11 minutes"} +{"Command":"\"docker-entrypoint.s…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"a1445d324e0f","Image":"redis:7.4-alpine","Labels":"com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=redis,desktop.docker.io/ports.scheme=v2,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports/6379/tcp=:6379,com.docker.compose.config-hash=681b4575ba55d081dc10ad086d58179ce9a966b8c7ce8dc3a4259491bddf628e,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99","LocalVolumes":"1","Mounts":"runmesh_redis-…","Name":"runmesh-redis-1","Names":"runmesh-redis-1","Networks":"runmesh_default","Ports":"0.0.0.0:6379-\u003e6379/tcp, [::]:6379-\u003e6379/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":6379,"PublishedPort":6379,"Protocol":"tcp"},{"URL":"::","TargetPort":6379,"PublishedPort":6379,"Protocol":"tcp"}],"RunningFor":"11 minutes ago","Service":"redis","Size":"0B","State":"running","Status":"Up 11 minutes (healthy)"} +{"Command":"\"/entrypoint.sh redp…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"8afed046466f","Image":"redpandadata/redpanda:v24.3.15","Labels":"com.docker.compose.depends_on=,com.docker.compose.image=sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/19092/tcp=:19092,com.docker.compose.config-hash=f7c33b45bd5515ce890e71f032b2ebf336077806438d6d3da8e8f3532837bae2,com.docker.compose.container-number=1,com.docker.compose.project=runmesh,com.docker.compose.service=redpanda,desktop.docker.io/ports/9644/tcp=:9644,org.opencontainers.image.authors=Redpanda Data \u003chi@redpanda.com\u003e","LocalVolumes":"1","Mounts":"runmesh_redpan…","Name":"runmesh-redpanda-1","Names":"runmesh-redpanda-1","Networks":"runmesh_default","Ports":"0.0.0.0:9644-\u003e9644/tcp, [::]:9644-\u003e9644/tcp, 0.0.0.0:19092-\u003e19092/tcp, [::]:19092-\u003e19092/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9644,"PublishedPort":9644,"Protocol":"tcp"},{"URL":"::","TargetPort":9644,"PublishedPort":9644,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":19092,"PublishedPort":19092,"Protocol":"tcp"},{"URL":"::","TargetPort":19092,"PublishedPort":19092,"Protocol":"tcp"}],"RunningFor":"11 minutes ago","Service":"redpanda","Size":"0B","State":"running","Status":"Up 11 minutes (healthy)"} +{"Command":"\"./console\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"7cf18e265933","Image":"redpandadata/console:v2.8.3","Labels":"com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.image=sha256:fc8006f22072293e9fa6a3aa8a0581ac502bc8262767c32c01ee019eca56bb38,com.docker.compose.project=runmesh,com.docker.compose.service=redpanda-console,desktop.docker.io/ports/8080/tcp=:8081,com.docker.compose.config-hash=cb7cc300da1519866e1ef7f83fe7dda15efa317d0575d81fe3db1560604baebb,com.docker.compose.container-number=1,com.docker.compose.depends_on=redpanda:service_healthy:false,com.docker.compose.oneoff=False","LocalVolumes":"0","Mounts":"","Name":"runmesh-redpanda-console-1","Names":"runmesh-redpanda-console-1","Networks":"runmesh_default","Ports":"0.0.0.0:8081-\u003e8080/tcp, [::]:8081-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8081,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8081,"Protocol":"tcp"}],"RunningFor":"11 minutes ago","Service":"redpanda-console","Size":"0B","State":"running","Status":"Up 11 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:53:09 -0400 EDT","ExitCode":0,"Health":"","ID":"0ff55f8d4e8f","Image":"runmesh-scheduler","Labels":"com.docker.compose.service=scheduler,com.docker.compose.container-number=1,com.docker.compose.image=sha256:3ebbde7860ff31a104c2888438ca7a329386205c28f50d3e78e95a342316a6b4,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=scheduler-1,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=c2ec170571edb0eb096521acddff62c757fe8eaa763425d2f41f085b450236ac,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,redis:service_healthy:false,migrate:service_completed_successfully:false,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh","LocalVolumes":"0","Mounts":"","Name":"runmesh-scheduler-1","Names":"runmesh-scheduler-1","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"9 minutes ago","Service":"scheduler","Size":"0B","State":"running","Status":"Up 8 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:53:09 -0400 EDT","ExitCode":0,"Health":"","ID":"808748b8e673","Image":"runmesh-scheduler","Labels":"com.docker.compose.container-number=2,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=scheduler,com.docker.compose.config-hash=c2ec170571edb0eb096521acddff62c757fe8eaa763425d2f41f085b450236ac,com.docker.compose.depends_on=migrate:service_completed_successfully:false,redpanda-init:service_completed_successfully:false,redis:service_healthy:false,com.docker.compose.image=sha256:3ebbde7860ff31a104c2888438ca7a329386205c28f50d3e78e95a342316a6b4,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=scheduler-2,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-scheduler-2","Names":"runmesh-scheduler-2","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"9 minutes ago","Service":"scheduler","Size":"0B","State":"running","Status":"Up 8 minutes"} +{"Command":"\"/tempo -config.file…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"cf56816bae5e","Image":"grafana/tempo:2.6.1","Labels":"com.docker.compose.depends_on=,com.docker.compose.image=sha256:ef4384fce6e8ad22b95b243d8fc165628cda655376fd50e7850536ad89d71d50,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,desktop.docker.io/ports.scheme=v2,org.opencontainers.image.revision=24c5b553df91cead5e3afc63e79a5f4deb702b62,com.docker.compose.container-number=1,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.service=tempo,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/SourceKind=hostFile,com.docker.compose.config-hash=4de71b343b413aeaff1ca18f18ac2c9df01f41d4b77c48c3526911fa552ecd98,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/binds/0/Target=/etc/tempo.yaml,org.opencontainers.image.created=2024-10-15T15:13:57Z,org.opencontainers.image.url=https://github.com/grafana/tempo,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/tempo.yaml,desktop.docker.io/ports/3200/tcp=:3200,org.opencontainers.image.source=https://github.com/grafana/tempo.git","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-tempo-1","Names":"runmesh-tempo-1","Networks":"runmesh_default","Ports":"0.0.0.0:3200-\u003e3200/tcp, [::]:3200-\u003e3200/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3200,"PublishedPort":3200,"Protocol":"tcp"},{"URL":"::","TargetPort":3200,"PublishedPort":3200,"Protocol":"tcp"}],"RunningFor":"11 minutes ago","Service":"tempo","Size":"0B","State":"running","Status":"Up 11 minutes"} +{"Command":"\"/docker-entrypoint.…\"","CreatedAt":"2026-07-20 19:53:09 -0400 EDT","ExitCode":0,"Health":"","ID":"ee528c3c9052","Image":"runmesh-web","Labels":"maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e,com.docker.compose.depends_on=control-plane:service_started:false,com.docker.compose.image=sha256:92d9ebb0fc2110acb860a54b71b742f02912c51e17d55e61bd5667736547b41b,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=web,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=e2afc81b870dc82be2a88d0dee013a23d9fa6ddf3304077545e218b9fc0b47da,com.docker.compose.container-number=1,com.docker.compose.oneoff=False,com.docker.compose.replace=web-1,desktop.docker.io/ports/3000/tcp=:3000","LocalVolumes":"0","Mounts":"","Name":"runmesh-web-1","Names":"runmesh-web-1","Networks":"runmesh_default","Ports":"0.0.0.0:3000-\u003e3000/tcp, [::]:3000-\u003e3000/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3000,"PublishedPort":3000,"Protocol":"tcp"},{"URL":"::","TargetPort":3000,"PublishedPort":3000,"Protocol":"tcp"}],"RunningFor":"9 minutes ago","Service":"web","Size":"0B","State":"running","Status":"Up 9 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"e3af80bb1098","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=1,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-1","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-1","Names":"runmesh-worker-go-1","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"3c71dc8a4231","Image":"runmesh-worker-go","Labels":"desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=10,com.docker.compose.oneoff=False,com.docker.compose.replace=worker-go-10,com.docker.compose.service=worker-go,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-10","Names":"runmesh-worker-go-10","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"7886d3df45d6","Image":"runmesh-worker-go","Labels":"com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.replace=worker-go-11,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=11","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-11","Names":"runmesh-worker-go-11","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"0209251b0424","Image":"runmesh-worker-go","Labels":"desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=12,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=worker-go-12","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-12","Names":"runmesh-worker-go-12","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"49313b3a028a","Image":"runmesh-worker-go","Labels":"com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=13,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-13","Names":"runmesh-worker-go-13","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"7ccc2472a462","Image":"runmesh-worker-go","Labels":"com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=14,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-14","Names":"runmesh-worker-go-14","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"7f3a1288368d","Image":"runmesh-worker-go","Labels":"desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=15,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.service=worker-go,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-15","Names":"runmesh-worker-go-15","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"105c9cd4fbab","Image":"runmesh-worker-go","Labels":"com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=16","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-16","Names":"runmesh-worker-go-16","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"f5c64bce2a7f","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.container-number=17,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-17","Names":"runmesh-worker-go-17","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"ca9933e0a80c","Image":"runmesh-worker-go","Labels":"com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project=runmesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=18,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-18","Names":"runmesh-worker-go-18","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"9093fc4a4d3e","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,com.docker.compose.container-number=19,com.docker.compose.project=runmesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-19","Names":"runmesh-worker-go-19","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"f7635153df09","Image":"runmesh-worker-go","Labels":"com.docker.compose.replace=worker-go-2,com.docker.compose.container-number=2,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-2","Names":"runmesh-worker-go-2","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"5cd0200fc757","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=20,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.project=runmesh,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-20","Names":"runmesh-worker-go-20","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"6a42beb17cbc","Image":"runmesh-worker-go","Labels":"com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=3,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-3,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-3","Names":"runmesh-worker-go-3","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"7e46af30971f","Image":"runmesh-worker-go","Labels":"desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=4,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-4,com.docker.compose.service=worker-go,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-4","Names":"runmesh-worker-go-4","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"571798aaa3a5","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=5,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.replace=worker-go-5,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-5","Names":"runmesh-worker-go-5","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"05b2487ca3fe","Image":"runmesh-worker-go","Labels":"com.docker.compose.project=runmesh,com.docker.compose.version=5.3.0,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-6,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=6,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-6","Names":"runmesh-worker-go-6","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"17a883791252","Image":"runmesh-worker-go","Labels":"desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=7,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=worker-go-7,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-7","Names":"runmesh-worker-go-7","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"3ae9c771914e","Image":"runmesh-worker-go","Labels":"com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-8,com.docker.compose.service=worker-go,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=8,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-8","Names":"runmesh-worker-go-8","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 19:55:21 -0400 EDT","ExitCode":0,"Health":"","ID":"d8fc9106272e","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.image=sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.container-number=9,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-9,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-9","Names":"runmesh-worker-go-9","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"7 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 7 minutes"} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/duplicate-delivery-go-test.json b/tests/benchmarks/results/2026-07-20T235240Z/duplicate-delivery-go-test.json new file mode 100644 index 0000000..4ea8cc8 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/duplicate-delivery-go-test.json @@ -0,0 +1,8 @@ +{"Time":"2026-07-21T00:02:40.643244382Z","Action":"start","Package":"github.com/samarth1412/RunMesh/tests/integration"} +{"Time":"2026-07-21T00:02:40.650890548Z","Action":"run","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce"} +{"Time":"2026-07-21T00:02:40.65095209Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Output":"=== RUN TestDuplicateDispatchIsLeasedOnce\n"} +{"Time":"2026-07-21T00:02:56.68842575Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Output":"--- PASS: TestDuplicateDispatchIsLeasedOnce (16.04s)\n"} +{"Time":"2026-07-21T00:02:56.688526458Z","Action":"pass","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Elapsed":16.04} +{"Time":"2026-07-21T00:02:56.688553792Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Output":"PASS\n"} +{"Time":"2026-07-21T00:02:56.689795125Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Output":"ok \tgithub.com/samarth1412/RunMesh/tests/integration\t16.046s\n"} +{"Time":"2026-07-21T00:02:56.689837417Z","Action":"pass","Package":"github.com/samarth1412/RunMesh/tests/integration","Elapsed":16.047} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/duplicate-submission.json b/tests/benchmarks/results/2026-07-20T235240Z/duplicate-submission.json new file mode 100644 index 0000000..632317b --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/duplicate-submission.json @@ -0,0 +1,10 @@ +{ + "first_run_id": "7828f65d-2229-44d2-a2b6-4e41da06ec9a", + "first_status": 201, + "original_input_preserved": true, + "passed": true, + "same_run": true, + "scenario": "duplicate_submission", + "second_run_id": "7828f65d-2229-44d2-a2b6-4e41da06ec9a", + "second_status": 200 +} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/end-to-end-completion-database.json b/tests/benchmarks/results/2026-07-20T235240Z/end-to-end-completion-database.json new file mode 100644 index 0000000..d03267f --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/end-to-end-completion-database.json @@ -0,0 +1 @@ +{"tasks" : 1000, "succeeded" : 1000, "other" : 0} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/end-to-end-completion.json b/tests/benchmarks/results/2026-07-20T235240Z/end-to-end-completion.json new file mode 100644 index 0000000..9ad191d --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/end-to-end-completion.json @@ -0,0 +1,6 @@ +{ + "scenario": "queued_end_to_end_completion", + "tasks": 1000, + "elapsed_seconds": 16.007, + "tasks_per_second": 62.473 +} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/hardware.txt b/tests/benchmarks/results/2026-07-20T235240Z/hardware.txt new file mode 100644 index 0000000..31d8876 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/hardware.txt @@ -0,0 +1,4 @@ +Darwin samarths-MacBook-Air.local 25.5.0 Darwin Kernel Version 25.5.0: Tue Jun 9 22:26:22 PDT 2026; root:xnu-12377.121.10~1/RELEASE_ARM64_T8132 arm64 +Apple M4 +17179869184 +{"Client":{"Version":"29.6.1","ApiVersion":"1.55","DefaultAPIVersion":"1.55","GitCommit":"8900f1d","GoVersion":"go1.26.4","Os":"darwin","Arch":"arm64","BuildTime":"Fri Jun 26 11:39:35 2026","Context":"desktop-linux"},"Server":{"Platform":{"Name":"Docker Desktop 4.82.0 (233772)"},"Version":"29.6.1","ApiVersion":"1.55","MinAPIVersion":"1.40","Os":"linux","Arch":"arm64","Components":[{"Name":"Engine","Version":"29.6.1","Details":{"ApiVersion":"1.55","Arch":"arm64","BuildTime":"Fri Jun 26 11:39:58 2026","Experimental":"false","GitCommit":"8ec5ab3","GoVersion":"go1.26.4","KernelVersion":"6.12.76-linuxkit","MinAPIVersion":"1.40","Module":"github.com/moby/moby/v2","ModuleVersion":"v2.0.0+unknown","Os":"linux"}},{"Name":"containerd","Version":"v2.2.5","Details":{"GitCommit":"e53c7c1516c3b2bff98eb76f1f4117477e6f4e66"}},{"Name":"runc","Version":"1.3.6","Details":{"GitCommit":"v1.3.6-0-g491b69ba"}},{"Name":"docker-init","Version":"0.19.0","Details":{"GitCommit":"de40ad0"}}],"GitCommit":"8ec5ab3","GoVersion":"go1.26.4","KernelVersion":"6.12.76-linuxkit","BuildTime":"2026-06-26T11:39:58.000000000+00:00"}} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/image-digests.json b/tests/benchmarks/results/2026-07-20T235240Z/image-digests.json new file mode 100644 index 0000000..91250b1 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/image-digests.json @@ -0,0 +1,2 @@ +[{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-18","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-7","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:83cd9ff6c05903f0b0d9d20e1d33dffc883f25d6296891097b4c99fee1e50b8e","ContainerName":"runmesh-worker-python-1","Repository":"runmesh-worker-python","Tag":"latest","Platform":"linux/arm64","Size":60024247,"Created":"2026-07-20T23:22:00.604549877Z","LastTagTime":"2026-07-20T23:52:41.353282257Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-20","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-14","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99","ContainerName":"runmesh-redis-1","Repository":"redis","Tag":"7.4-alpine","Platform":"linux/arm64/v8","Size":16790804,"Created":"2026-05-07T17:39:54.062865523Z","LastTagTime":"2026-07-20T00:45:12.219804333Z"},{"ID":"sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d","ContainerName":"runmesh-redpanda-1","Repository":"redpandadata/redpanda","Tag":"v24.3.15","Platform":"linux/arm64/v8","Size":183895553,"Created":"2025-06-18T02:52:15.126275953Z","LastTagTime":"2026-07-20T00:48:16.687951877Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-2","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-12","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-19","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-13","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:fc8006f22072293e9fa6a3aa8a0581ac502bc8262767c32c01ee019eca56bb38","ContainerName":"runmesh-redpanda-console-1","Repository":"redpandadata/console","Tag":"v2.8.3","Platform":"linux/arm64","Size":73250938,"Created":"2025-02-27T18:10:27.99190098Z","LastTagTime":"2026-07-20T00:48:14.485197626Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-17","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:3ebbde7860ff31a104c2888438ca7a329386205c28f50d3e78e95a342316a6b4","ContainerName":"runmesh-scheduler-2","Repository":"runmesh-scheduler","Tag":"latest","Platform":"linux/arm64","Size":8457935,"Created":"2026-07-20T23:08:19.62513022Z","LastTagTime":"2026-07-20T23:53:08.445131672Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-3","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-10","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d","ContainerName":"runmesh-redpanda-init-1","Repository":"redpandadata/redpanda","Tag":"v24.3.15","Platform":"linux/arm64/v8","Size":183895553,"Created":"2025-06-18T02:52:15.126275953Z","LastTagTime":"2026-07-20T00:48:16.687951877Z"},{"ID":"sha256:be6a86215213145bfb4fb3e2b3ab982a806d00262655abdcf3ffa6a38d241c7c","ContainerName":"runmesh-keycloak-1","Repository":"quay.io/keycloak/keycloak","Tag":"26.1","Platform":"linux/arm64","Size":241302481,"Created":"2025-04-11T08:01:18.363925612Z","LastTagTime":"2026-07-20T00:48:11.796539791Z"},{"ID":"sha256:d8ea37798ccc41061a62ab080f2676dda6bf7815558499f901bdb0f533a456fb","ContainerName":"runmesh-grafana-1","Repository":"grafana/grafana","Tag":"11.4.0","Platform":"linux/arm64","Size":125326597,"Created":"2024-12-04T21:33:24.409833365Z","LastTagTime":"2026-07-20T00:48:13.975356709Z"},{"ID":"sha256:640c22768ed5dbc92eacc14502a1b06a1c708fa60431345c78dfc22917062e93","ContainerName":"runmesh-minio-1","Repository":"minio/minio","Tag":"RELEASE.2025-02-07T23-21-09Z","Platform":"linux/arm64","Size":58677008,"Created":"2025-02-08T21:08:53.658721358Z","LastTagTime":"2026-07-20T00:47:58.255328882Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-8","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-1","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:8af2de1abbdd7aa92b27c9bcc96f0f4140c9096b507c77921ffddf1c6ad6c48f","ContainerName":"runmesh-loki-1","Repository":"grafana/loki","Tag":"3.3.2","Platform":"linux/arm64","Size":30732944,"Created":"2024-12-18T17:12:53.000478115Z","LastTagTime":"2026-07-20T00:48:06.06543197Z"},{"ID":"sha256:acded13a336f2b6f35fd9ce14a32834cf1e6b2a7887fe5dfc977b3e563eba39e","ContainerName":"runmesh-migrate-1","Repository":"migrate/migrate","Tag":"v4.18.2","Platform":"linux/arm64","Size":18640157,"Created":"2025-01-27T05:15:01.377819434Z","LastTagTime":"2026-07-20T00:45:00.445620092Z"},{"ID":"sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218","ContainerName":"runmesh-prometheus-1","Repository":"prom/prometheus","Tag":"v3.1.0","Platform":"linux/arm64/v8","Size":110853317,"Created":"2025-01-02T14:15:56.472078076Z","LastTagTime":"2026-07-20T00:48:10.946754847Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-15","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-5","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-11","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-4","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:a4e6d9e8b938469d7a63f29c71ebe5cb6e08ba826e7d6c29507bbd2c1ee2bece","ContainerName":"runmesh-control-plane-1","Repository":"runmesh-control-plane","Tag":"latest","Platform":"linux/arm64","Size":10173680,"Created":"2026-07-20T23:08:20.215292553Z","LastTagTime":"2026-07-20T23:55:20.657417887Z"},{"ID":"sha256:3ebbde7860ff31a104c2888438ca7a329386205c28f50d3e78e95a342316a6b4","ContainerName":"runmesh-scheduler-1","Repository":"runmesh-scheduler","Tag":"latest","Platform":"linux/arm64","Size":8457935,"Created":"2026-07-20T23:08:19.62513022Z","LastTagTime":"2026-07-20T23:53:08.445131672Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-16","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-9","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:f89f8f6c9b770ebab6beae25d7034bd4ae70b6562c1c98540a2b986bba5ccb5b","ContainerName":"runmesh-worker-go-6","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-20T23:55:20.657794803Z"},{"ID":"sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193","ContainerName":"runmesh-postgres-1","Repository":"postgres","Tag":"17-alpine","Platform":"linux/arm64/v8","Size":114981107,"Created":"2026-07-07T17:46:49.401690597Z","LastTagTime":"2026-07-20T00:47:47.860492044Z"},{"ID":"sha256:ef4384fce6e8ad22b95b243d8fc165628cda655376fd50e7850536ad89d71d50","ContainerName":"runmesh-tempo-1","Repository":"grafana/tempo","Tag":"2.6.1","Platform":"linux/arm64/v8","Size":49979919,"Created":"2024-10-15T15:14:02.938965353Z","LastTagTime":"2026-07-20T00:47:57.254291174Z"},{"ID":"sha256:36c35cc213c0f3b64d6e8a3e844dc90822f00725e0e518eaed5b08bcc2231e72","ContainerName":"runmesh-otel-collector-1","Repository":"otel/opentelemetry-collector-contrib","Tag":"0.119.0","Platform":"linux/arm64","Size":72650722,"Created":"2025-02-04T18:02:50.606991596Z","LastTagTime":"2026-07-20T00:47:50.053368504Z"},{"ID":"sha256:92d9ebb0fc2110acb860a54b71b742f02912c51e17d55e61bd5667736547b41b","ContainerName":"runmesh-web-1","Repository":"runmesh-web","Tag":"latest","Platform":"linux/arm64","Size":22001475,"Created":"2026-07-20T23:26:55.776613083Z","LastTagTime":"2026-07-20T23:52:41.914484466Z"}] + diff --git a/tests/benchmarks/results/2026-07-20T235240Z/in-flight-before-termination.json b/tests/benchmarks/results/2026-07-20T235240Z/in-flight-before-termination.json new file mode 100644 index 0000000..2c2c013 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/in-flight-before-termination.json @@ -0,0 +1 @@ +{"running_count" : 5, "task_ids" : ["01a1746a-9557-41d9-98ce-49add4af502a", "1736225c-3492-4306-b0ce-600f65c66664", "a2c15b87-c82d-4cc8-ad08-dbc8722a2a42", "cc8eaaf9-f5fd-4663-a1c7-5dcdfa65326e", "cd6b6515-6233-4cfa-8d7f-a2959af34937"]} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/kafka-topology-after.json b/tests/benchmarks/results/2026-07-20T235240Z/kafka-topology-after.json new file mode 100644 index 0000000..3349d59 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/kafka-topology-after.json @@ -0,0 +1 @@ +[{"summary":{"name":"runmesh.tasks","internal":false,"partitions":6,"replicas":1,"error":""},"configs":[{"key":"cleanup.policy","value":"delete","source":"DEFAULT_CONFIG"},{"key":"compression.type","value":"producer","source":"DEFAULT_CONFIG"},{"key":"delete.retention.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"flush.bytes","value":"262144","source":"DEFAULT_CONFIG"},{"key":"flush.ms","value":"100","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"max.message.bytes","value":"1048576","source":"DEFAULT_CONFIG"},{"key":"message.timestamp.type","value":"CreateTime","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.mode","value":"disabled","source":"DEFAULT_CONFIG"},{"key":"redpanda.leaders.preference","value":"none","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.read","value":"false","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.write","value":"false","source":"DEFAULT_CONFIG"},{"key":"retention.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.ms","value":"86400000","source":"DEFAULT_CONFIG"},{"key":"retention.ms","value":"604800000","source":"DEFAULT_CONFIG"},{"key":"segment.bytes","value":"134217728","source":"DEFAULT_CONFIG"},{"key":"segment.ms","value":"1209600000","source":"DEFAULT_CONFIG"},{"key":"write.caching","value":"true","source":"DEFAULT_CONFIG"}],"partitions":[{"partition":0,"leader":0,"epoch":4,"replicas":[0],"log_start_offset":0,"high_watermark":76946},{"partition":1,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":1702},{"partition":2,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":9287},{"partition":3,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":6073},{"partition":4,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":6088},{"partition":5,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":13835}]}] diff --git a/tests/benchmarks/results/2026-07-20T235240Z/kafka-topology-before.json b/tests/benchmarks/results/2026-07-20T235240Z/kafka-topology-before.json new file mode 100644 index 0000000..18a7567 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/kafka-topology-before.json @@ -0,0 +1 @@ +[{"summary":{"name":"runmesh.tasks","internal":false,"partitions":6,"replicas":1,"error":""},"configs":[{"key":"cleanup.policy","value":"delete","source":"DEFAULT_CONFIG"},{"key":"compression.type","value":"producer","source":"DEFAULT_CONFIG"},{"key":"delete.retention.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"flush.bytes","value":"262144","source":"DEFAULT_CONFIG"},{"key":"flush.ms","value":"100","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"max.message.bytes","value":"1048576","source":"DEFAULT_CONFIG"},{"key":"message.timestamp.type","value":"CreateTime","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.mode","value":"disabled","source":"DEFAULT_CONFIG"},{"key":"redpanda.leaders.preference","value":"none","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.read","value":"false","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.write","value":"false","source":"DEFAULT_CONFIG"},{"key":"retention.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.ms","value":"86400000","source":"DEFAULT_CONFIG"},{"key":"retention.ms","value":"604800000","source":"DEFAULT_CONFIG"},{"key":"segment.bytes","value":"134217728","source":"DEFAULT_CONFIG"},{"key":"segment.ms","value":"1209600000","source":"DEFAULT_CONFIG"},{"key":"write.caching","value":"true","source":"DEFAULT_CONFIG"}],"partitions":[{"partition":0,"leader":0,"epoch":4,"replicas":[0],"log_start_offset":0,"high_watermark":73690},{"partition":1,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":55},{"partition":2,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":31},{"partition":3,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":20},{"partition":4,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":43},{"partition":5,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":32}]}] diff --git a/tests/benchmarks/results/2026-07-20T235240Z/lease-recovery-distribution.json b/tests/benchmarks/results/2026-07-20T235240Z/lease-recovery-distribution.json new file mode 100644 index 0000000..356e022 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/lease-recovery-distribution.json @@ -0,0 +1 @@ +{"samples" : 5, "p50_ms" : 127520.176, "p95_ms" : 294147.0804, "p99_ms" : 315704.29208} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/scheduler-dispatch.json b/tests/benchmarks/results/2026-07-20T235240Z/scheduler-dispatch.json new file mode 100644 index 0000000..effbd92 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/scheduler-dispatch.json @@ -0,0 +1,6 @@ +{ + "scenario": "scheduler_dispatch", + "tasks": 10000, + "elapsed_seconds": 24.417, + "dispatches_per_second": 409.548 +} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/verification.json b/tests/benchmarks/results/2026-07-20T235240Z/verification.json new file mode 100644 index 0000000..07b0ca4 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/verification.json @@ -0,0 +1,21 @@ +{ + "checks": { + "active_workflows": true, + "api_requests": true, + "api_zero_failures": true, + "duplicate_delivery": true, + "duplicate_submission": true, + "end_to_end_zero_task_loss": true, + "lease_recovered": true, + "multi_partition_delivery": true, + "multiple_tasks_in_flight": true, + "six_kafka_partitions": true, + "submission_requests": true, + "submission_zero_failures": true, + "ten_thousand_tasks": true, + "worker_pool_terminated": true, + "zero_task_loss": true + }, + "observed_partitions_with_new_records": 6, + "passed": true +} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/worker-crash-result.json b/tests/benchmarks/results/2026-07-20T235240Z/worker-crash-result.json new file mode 100644 index 0000000..6ac738c --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/worker-crash-result.json @@ -0,0 +1 @@ +{"scenario" : "worker_termination_and_lease_recovery", "tasks" : 10000, "succeeded" : 10000, "other" : 0, "attempts" : 10005, "recovered_tasks" : 5, "duplicate_attempts" : 5, "recovery_seconds" : 369.248} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/worker-crash-setup.json b/tests/benchmarks/results/2026-07-20T235240Z/worker-crash-setup.json new file mode 100644 index 0000000..18e382f --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/worker-crash-setup.json @@ -0,0 +1,31 @@ +{ + "idempotency_prefix": "validation-crash-20260720T235240Z", + "preparation_seconds": 0.516, + "run_count": 20, + "run_ids": [ + "9c8d2b67-3785-4957-97ac-39ff063489fe", + "d067adce-4e7d-48d0-a030-580415994423", + "03fa1712-24eb-4968-9eb3-e7b7ba6f24fc", + "1bad027d-8dd3-4301-8bbb-afdaeeb9f306", + "6246ff6d-078d-4bca-8446-fd6a6f934a96", + "57a5ebff-c669-4613-983f-1ee6b956b4e3", + "2ffd1e41-d8b5-4f24-9050-089f20a404fa", + "1ddadc85-9847-4153-8d18-afb80941beb6", + "2db55126-8322-4668-b425-72a267e29dd3", + "59c57111-5593-4893-9a77-de81c9d3b47a", + "c05cfa53-2198-49f6-9333-95cf5f1ea51c", + "d97783ee-b248-41cb-b5b8-f847892e070c", + "beb4bab8-ee59-4aea-8c96-388048fb3c82", + "b9b669d7-d285-4c28-8cc5-3d5a94d1925c", + "198a41f6-8fd0-447b-b22b-3680137a38ae", + "e25d77f1-e427-4a9f-8f7a-c562910ed459", + "d4c243b4-51d3-4db0-9085-af7162de9e31", + "86bfbb7d-1e9f-49c9-98a3-10b2a5cc7d02", + "84b0b9f9-969a-4238-a6dc-518411eaf238", + "3530fc70-8229-48e3-af8b-80ef884b4287" + ], + "scenario": "worker_termination_and_lease_recovery", + "task_count": 10000, + "tasks_per_run": 500, + "workflow_id": "6b13ce44-55fd-4d1f-a1dd-7803c8cc14df" +} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/worker-termination.json b/tests/benchmarks/results/2026-07-20T235240Z/worker-termination.json new file mode 100644 index 0000000..f722158 --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/worker-termination.json @@ -0,0 +1,6 @@ +{ + "terminated_workers": 20, + "idempotency_prefix": "validation-crash-20260720T235240Z", + "in_flight_tasks": 5, + "terminated_at": "2026-07-20T23:55:35.550786+00:00" +} diff --git a/tests/benchmarks/results/2026-07-20T235240Z/workflow-submissions.json b/tests/benchmarks/results/2026-07-20T235240Z/workflow-submissions.json new file mode 100644 index 0000000..9cf101c --- /dev/null +++ b/tests/benchmarks/results/2026-07-20T235240Z/workflow-submissions.json @@ -0,0 +1,133 @@ +{ + "root_group": { + "name": "", + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": {}, + "checks": { + "created": { + "passes": 1501, + "fails": 0, + "name": "created", + "path": "::created", + "id": "e26a6144a84abb127f65c1e5808b420c" + } + } + }, + "metrics": { + "http_req_waiting": { + "p(95)": 3.212416, + "avg": 2.339427632245169, + "min": 0.94825, + "med": 1.436791, + "max": 155.3245, + "p(90)": 2.329542 + }, + "vus": { + "value": 0, + "min": 0, + "max": 5 + }, + "http_req_tls_handshaking": { + "med": 0, + "max": 0, + "p(90)": 0, + "p(95)": 0, + "avg": 0, + "min": 0 + }, + "iterations": { + "count": 1501, + "rate": 50.0279739482134 + }, + "http_reqs": { + "count": 1501, + "rate": 50.0279739482134 + }, + "http_req_failed": { + "fails": 1501, + "passes": 0, + "thresholds": { + "rate==0": false + }, + "value": 0 + }, + "http_req_duration": { + "p(95)": 3.363708, + "avg": 2.4018668467688196, + "min": 0.998875, + "med": 1.491375, + "max": 156.007417, + "p(90)": 2.409625 + }, + "http_req_sending": { + "avg": 0.025227827448367792, + "min": 0.008917, + "med": 0.020041, + "max": 1.526, + "p(90)": 0.032833, + "p(95)": 0.044209 + }, + "data_received": { + "count": 812373, + "rate": 27076.199387229826 + }, + "checks": { + "passes": 1501, + "fails": 0, + "thresholds": { + "rate==1": false + }, + "value": 1 + }, + "http_req_connecting": { + "avg": 0.06117449966688873, + "min": 0, + "med": 0, + "max": 21.37625, + "p(90)": 0, + "p(95)": 0.553291 + }, + "iteration_duration": { + "avg": 2.6325887728181203, + "min": 1.0755, + "med": 1.644916, + "max": 157.953041, + "p(90)": 2.801166, + "p(95)": 3.932167 + }, + "vus_max": { + "value": 100, + "min": 100, + "max": 100 + }, + "http_req_blocked": { + "avg": 0.06888515656229165, + "min": 0.001667, + "med": 0.003875, + "max": 21.418708, + "p(90)": 0.012958, + "p(95)": 0.58325 + }, + "data_sent": { + "rate": 77666.54642300366, + "count": 2330246 + }, + "http_req_duration{expected_response:true}": { + "med": 1.491375, + "max": 156.007417, + "p(90)": 2.409625, + "p(95)": 3.363708, + "avg": 2.4018668467688196, + "min": 0.998875 + }, + "http_req_receiving": { + "avg": 0.0372113870752831, + "min": 0.009625, + "med": 0.033333, + "max": 0.386042, + "p(90)": 0.05275, + "p(95)": 0.064375 + } + } +} \ No newline at end of file diff --git a/tests/benchmarks/results/2026-07-21T000619Z/README.md b/tests/benchmarks/results/2026-07-21T000619Z/README.md new file mode 100644 index 0000000..e919b86 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/README.md @@ -0,0 +1,15 @@ +# Superseded local benchmark — 2026-07-21T000619Z + +Source commit: `9d14c59918978549001d9cd6ef14d302b0d196e4` + +This corrected run passed all 15 blocking verification checks. It recorded +3,001 authenticated reads at 100.021 requests/second with 3.386 ms p95 and +4.966 ms p99, scheduler dispatch of 10,000 tasks at 414.042 tasks/second, and +10,000/10,000 successful tasks after terminating 20 workers with seven tasks +actively running. Seven leases were recovered and no task was permanently +lost. + +It is retained as valid historical evidence but was superseded because a later +container-security fix changed the source commit. The latest documented run is +therefore tied to the patched code candidate. Raw JSON and `commands.sh` in +this directory have not been edited. diff --git a/tests/benchmarks/results/2026-07-21T000619Z/active-workflows-database.json b/tests/benchmarks/results/2026-07-21T000619Z/active-workflows-database.json new file mode 100644 index 0000000..756a691 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/active-workflows-database.json @@ -0,0 +1 @@ +{"active_runs" : 1000, "active_tasks" : 1000} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/active-workflows.json b/tests/benchmarks/results/2026-07-21T000619Z/active-workflows.json new file mode 100644 index 0000000..e87b9e6 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/active-workflows.json @@ -0,0 +1,22 @@ +{ + "create_latency": { + "p50_ms": 6.299, + "p95_ms": 8.963, + "p99_ms": 10.89 + }, + "created": 1000, + "elapsed_seconds": 20.59, + "failure_count": 0, + "failure_rate": 0, + "idempotency_prefix": "validation-active-20260721T000619Z", + "requested": 1000, + "sample_run_ids": [ + "4906ac06-fef2-4aaa-bd43-75ed8efee609", + "be7cdb95-dfee-4dd7-ad00-c956b5d3c109", + "b12361c4-e457-441f-9fd1-56f50fbf21bf", + "f9c06143-ff8a-43db-82bd-7af0c5e3f842", + "9eecf321-0e6b-447c-b857-8f1ee3e08bc4" + ], + "scenario": "simultaneously_active_workflows", + "workflow_id": "ebac3506-8a7b-4d59-9a6f-0554bde13c1b" +} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/api-read.json b/tests/benchmarks/results/2026-07-21T000619Z/api-read.json new file mode 100644 index 0000000..cabe093 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/api-read.json @@ -0,0 +1,142 @@ +{ + "metrics": { + "http_req_tls_handshaking": { + "p(90)": 0, + "p(95)": 0, + "p(99)": 0, + "max": 0, + "avg": 0, + "min": 0, + "med": 0 + }, + "vus": { + "value": 0, + "min": 0, + "max": 1 + }, + "vus_max": { + "value": 40, + "min": 40, + "max": 40 + }, + "http_req_duration{expected_response:true}": { + "max": 138.68225, + "avg": 2.2177756167944014, + "min": 0.571541, + "med": 1.491041, + "p(90)": 3.129, + "p(95)": 3.385917, + "p(99)": 4.966167 + }, + "data_sent": { + "count": 4228409, + "rate": 140929.5213535421 + }, + "http_req_waiting": { + "med": 1.435291, + "p(90)": 3.018959, + "p(95)": 3.268, + "p(99)": 4.78425, + "max": 137.60925, + "avg": 2.147670581472844, + "min": 0.552667 + }, + "data_received": { + "count": 1395465, + "rate": 46509.74267522859 + }, + "http_req_sending": { + "p(99)": 0.097709, + "max": 0.927958, + "avg": 0.03000130023325554, + "min": 0.003458, + "med": 0.02, + "p(90)": 0.054375, + "p(95)": 0.062417 + }, + "http_req_receiving": { + "avg": 0.040103735088303887, + "min": 0.00875, + "med": 0.028833, + "p(90)": 0.084333, + "p(95)": 0.117459, + "p(99)": 0.166834, + "max": 1.039042 + }, + "http_reqs": { + "count": 3001, + "rate": 100.0209519897389 + }, + "http_req_duration": { + "med": 1.491041, + "p(90)": 3.129, + "p(95)": 3.385917, + "p(99)": 4.966167, + "max": 138.68225, + "avg": 2.2177756167944014, + "min": 0.571541 + }, + "http_req_connecting": { + "p(90)": 0, + "p(95)": 0, + "p(99)": 0.554792, + "max": 1.356666, + "avg": 0.008911555481506165, + "min": 0, + "med": 0 + }, + "iterations": { + "count": 3001, + "rate": 100.0209519897389 + }, + "iteration_duration": { + "p(90)": 3.417416, + "p(95)": 3.710459, + "p(99)": 5.392542, + "max": 139.251667, + "avg": 2.3907412202599136, + "min": 0.599667, + "med": 1.621542 + }, + "http_req_failed": { + "passes": 0, + "fails": 3001, + "thresholds": { + "rate==0": false + }, + "value": 0 + }, + "checks": { + "passes": 3001, + "fails": 0, + "thresholds": { + "rate==1": false + }, + "value": 1 + }, + "http_req_blocked": { + "med": 0.004334, + "p(90)": 0.015916, + "p(95)": 0.01925, + "p(99)": 0.577083, + "max": 2.298125, + "avg": 0.017480858713762065, + "min": 0.000791 + } + }, + "root_group": { + "name": "", + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": {}, + "checks": { + "authenticated request succeeded": { + "id": "12f4c1307352dc7d8175954f46790cf7", + "passes": 3001, + "fails": 0, + "name": "authenticated request succeeded", + "path": "::authenticated request succeeded" + } + } + } +} \ No newline at end of file diff --git a/tests/benchmarks/results/2026-07-21T000619Z/commands.sh b/tests/benchmarks/results/2026-07-21T000619Z/commands.sh new file mode 100755 index 0000000..a0917b0 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/commands.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Runs only against the local Compose project. It never provisions cloud +# resources. Raw result directories are immutable: choose a new path to rerun. +result_dir=${1:-tests/benchmarks/results/$(date -u +%Y-%m-%dT%H%M%SZ)} +if [[ -e "$result_dir" ]]; then + echo "result directory already exists: $result_dir" >&2 + exit 1 +fi +mkdir -p "$result_dir" +run_suffix=$(date -u +%Y%m%dT%H%M%SZ) +active_prefix="validation-active-$run_suffix" +crash_prefix="validation-crash-$run_suffix" +duplicate_prefix="validation-duplicate-$run_suffix" + +cp "$0" "$result_dir/commands.sh" +docker compose up -d --build +docker compose up -d --scale scheduler=2 --scale worker-go=1 --scale worker-python=1 scheduler worker-go worker-python +for endpoint in \ + http://localhost:8180/realms/runmesh/.well-known/openid-configuration \ + http://localhost:8080/health/ready; do + ready=0 + for _ in $(seq 1 90); do + if curl -fsS "$endpoint" >/dev/null; then ready=1; break; fi + sleep 2 + done + [[ "$ready" == 1 ]] || { echo "benchmark dependency did not become ready: $endpoint" >&2; exit 1; } +done +benchmark_token=$(python3 tests/benchmarks/local.py token) + +docker compose exec -T redpanda rpk topic describe runmesh.tasks --format json > "$result_dir/kafka-topology-before.json" +read_workflow=$(python3 tests/benchmarks/local.py definition --prefix "validation-read-$run_suffix") +docker run --rm --add-host host.docker.internal:host-gateway \ + -e RUNMESH_API=http://host.docker.internal:8080 \ + -e RUNMESH_READ_PATH="/v1/workflows/$read_workflow" \ + -e RUNMESH_TOKEN="$benchmark_token" -e RUNMESH_RATE=100 -e RUNMESH_DURATION=30s \ + -v "$PWD/tests/load":/scripts:ro -v "$PWD/$result_dir":/results \ + grafana/k6:0.57.0 run --summary-export=/results/api-read.json /scripts/api-read.js + +sleep 3 +submission_workflow=$(python3 tests/benchmarks/local.py definition --prefix "validation-submission-$run_suffix") +docker run --rm --add-host host.docker.internal:host-gateway \ + -e RUNMESH_API=http://host.docker.internal:8080 \ + -e RUNMESH_TOKEN="$benchmark_token" -e RUNMESH_WORKFLOW_ID="$submission_workflow" \ + -e RUNMESH_RATE=50 -e RUNMESH_DURATION=30s \ + -v "$PWD/tests/load":/scripts:ro -v "$PWD/$result_dir":/results \ + grafana/k6:0.57.0 run --summary-export=/results/workflow-submissions.json /scripts/submissions.js + +docker compose stop scheduler worker-python worker-go +python3 tests/benchmarks/local.py active --count 1000 --prefix "$active_prefix" > "$result_dir/active-workflows.json" +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('active_runs',count(*),'active_tasks',sum(task_count)) FROM (SELECT wr.id,count(tr.id) task_count FROM workflow_runs wr JOIN task_runs tr ON tr.workflow_run_id=wr.id WHERE wr.idempotency_key LIKE '$active_prefix-%' AND wr.status='RUNNING' GROUP BY wr.id) rows" \ + > "$result_dir/active-workflows-database.json" + +# Start two schedulers and twelve workers against six partitions, then measure +# drain throughput for the 1,000 distinct workflow keys created above. +completion_start=$(python3 -c 'import time; print(time.time_ns())') +docker compose up -d --scale scheduler=2 scheduler +docker compose up -d --build --scale worker-go=12 worker-go +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM workflow_runs WHERE idempotency_key LIKE '$active_prefix-%' AND status NOT IN ('SUCCEEDED','FAILED','CANCELLED')") != 0 ]]; do sleep 0.2; done +completion_end=$(python3 -c 'import time; print(time.time_ns())') +python3 - "$completion_start" "$completion_end" > "$result_dir/end-to-end-completion.json" <<'PY' +import json, sys +elapsed = (int(sys.argv[2]) - int(sys.argv[1])) / 1_000_000_000 +print(json.dumps({"scenario":"queued_end_to_end_completion","tasks":1000,"elapsed_seconds":round(elapsed,3),"tasks_per_second":round(1000/elapsed,3)}, indent=2)) +PY +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('tasks',count(*),'succeeded',count(*) FILTER (WHERE tr.status='SUCCEEDED'),'other',count(*) FILTER (WHERE tr.status!='SUCCEEDED')) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$active_prefix-%'" \ + > "$result_dir/end-to-end-completion-database.json" + +# Begin at the current topic end so this scenario observes only its own work. +docker compose stop worker-go +docker compose exec -T redpanda rpk group seek runmesh-workers --to end --topics runmesh.tasks >/dev/null +python3 tests/benchmarks/local.py prepare --tasks 10000 --prefix "$crash_prefix" > "$result_dir/worker-crash-setup.json" +dispatch_start=$(python3 -c 'import time; print(time.time_ns())') +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status IN ('READY','BLOCKED')") != 0 ]]; do sleep 0.1; done +dispatch_end=$(python3 -c 'import time; print(time.time_ns())') +python3 - "$dispatch_start" "$dispatch_end" > "$result_dir/scheduler-dispatch.json" <<'PY' +import json, sys +elapsed = (int(sys.argv[2]) - int(sys.argv[1])) / 1_000_000_000 +print(json.dumps({"scenario":"scheduler_dispatch","tasks":10000,"elapsed_seconds":round(elapsed,3),"dispatches_per_second":round(10000/elapsed,3)}, indent=2)) +PY + +# Keep enough tasks running to occupy every available Kafka partition before +# terminating the entire 20-container worker pool. +docker compose exec -T postgres psql -U runmesh -d runmesh -c \ + "WITH selected AS (SELECT tr.id FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' ORDER BY tr.id LIMIT 100) UPDATE task_runs SET input='{\"delay_ms\":5000}'::jsonb WHERE id IN (SELECT id FROM selected);" >/dev/null +docker compose up -d --build --scale worker-go=20 worker-go +deadline=$((SECONDS+60)) +while true; do + running=$(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status='RUNNING'") + [[ "$running" -ge 4 ]] && break + [[ "$SECONDS" -ge "$deadline" ]] && { echo "fewer than four tasks became concurrently RUNNING" >&2; exit 1; } + sleep 0.1 +done +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('running_count',count(*),'task_ids',json_agg(id ORDER BY id)) FROM (SELECT tr.id FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status='RUNNING') active" \ + > "$result_dir/in-flight-before-termination.json" +worker_ids=( $(docker compose ps -q worker-go) ) +docker kill "${worker_ids[@]}" >/dev/null +python3 - "${#worker_ids[@]}" "$crash_prefix" "$running" > "$result_dir/worker-termination.json" <<'PY' +import datetime, json, sys +print(json.dumps({"terminated_workers":int(sys.argv[1]),"idempotency_prefix":sys.argv[2],"in_flight_tasks":int(sys.argv[3]),"terminated_at":datetime.datetime.now(datetime.timezone.utc).isoformat()}, indent=2)) +PY +recovery_start=$(python3 -c 'import time; print(time.time_ns())') +docker compose up -d --scale worker-go=20 worker-go +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM workflow_runs WHERE idempotency_key LIKE '$crash_prefix-%' AND status NOT IN ('SUCCEEDED','FAILED','CANCELLED')") != 0 ]]; do sleep 1; done +recovery_end=$(python3 -c 'import time; print(time.time_ns())') +recovery_seconds=$(python3 -c "print(round(($recovery_end-$recovery_start)/1_000_000_000,3))") +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('scenario','worker_termination_and_lease_recovery','tasks',count(*),'succeeded',count(*) FILTER (WHERE tr.status='SUCCEEDED'),'other',count(*) FILTER (WHERE tr.status!='SUCCEEDED'),'attempts',sum(tr.attempt_count),'recovered_tasks',count(*) FILTER (WHERE tr.attempt_count > 1),'duplicate_attempts',sum(tr.attempt_count)-count(*),'recovery_seconds',$recovery_seconds) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%'" \ + > "$result_dir/worker-crash-result.json" +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "WITH gaps AS (SELECT EXTRACT(EPOCH FROM (next_attempt.started_at-expired.ended_at))*1000 milliseconds FROM task_attempts expired JOIN task_attempts next_attempt ON next_attempt.task_run_id=expired.task_run_id AND next_attempt.attempt_number=expired.attempt_number+1 JOIN task_runs tr ON tr.id=expired.task_run_id JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND expired.error_type='LeaseExpired') SELECT json_build_object('samples',count(*),'p50_ms',percentile_cont(0.50) WITHIN GROUP (ORDER BY milliseconds),'p95_ms',percentile_cont(0.95) WITHIN GROUP (ORDER BY milliseconds),'p99_ms',percentile_cont(0.99) WITHIN GROUP (ORDER BY milliseconds)) FROM gaps" \ + > "$result_dir/lease-recovery-distribution.json" + +python3 tests/benchmarks/local.py duplicate --prefix "$duplicate_prefix" > "$result_dir/duplicate-submission.json" +docker run --rm -v "$PWD":/src -w /src -v /var/run/docker.sock:/var/run/docker.sock \ + -v runmesh-go-mod:/go/pkg/mod -v runmesh-go-build:/root/.cache/go-build \ + -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal golang:1.25 \ + /usr/local/go/bin/go test -tags=integration -count=1 -run TestDuplicateDispatchIsLeasedOnce -json ./tests/integration \ + > "$result_dir/duplicate-delivery-go-test.json" + +docker compose images --format json > "$result_dir/image-digests.json" +docker compose ps --format json > "$result_dir/compose-topology.json" +docker compose exec -T redpanda rpk topic describe runmesh.tasks --format json > "$result_dir/kafka-topology-after.json" +{ + uname -a + sysctl -n machdep.cpu.brand_string 2>/dev/null || true + sysctl -n hw.memsize 2>/dev/null || true + docker version --format '{{json .}}' +} > "$result_dir/hardware.txt" +git rev-parse HEAD > "$result_dir/commit-sha.txt" +python3 tests/benchmarks/verify.py "$result_dir" > "$result_dir/verification.json" + +echo "Benchmark evidence written to $result_dir" diff --git a/tests/benchmarks/results/2026-07-21T000619Z/commit-sha.txt b/tests/benchmarks/results/2026-07-21T000619Z/commit-sha.txt new file mode 100644 index 0000000..b8b7a62 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/commit-sha.txt @@ -0,0 +1 @@ +9d14c59918978549001d9cd6ef14d302b0d196e4 diff --git a/tests/benchmarks/results/2026-07-21T000619Z/compose-topology.json b/tests/benchmarks/results/2026-07-21T000619Z/compose-topology.json new file mode 100644 index 0000000..e9cac05 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/compose-topology.json @@ -0,0 +1,35 @@ +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:10 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"dc2eb12a459c","Image":"runmesh-control-plane","Labels":"com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=control-plane,com.docker.compose.version=5.3.0,desktop.docker.io/ports/7001/tcp=:7001,desktop.docker.io/ports/8080/tcp=:8080,com.docker.compose.config-hash=9fe03b71a2356bebdc17c98f565a44f48023721adb5ec2a83aba50c19bd4f54d,com.docker.compose.container-number=1,com.docker.compose.image=sha256:00f07b01e8035e8d79e6d01731a153b3382779ec2fae253e980220ed741f840d,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=control-plane-1,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=keycloak:service_started:false,migrate:service_completed_successfully:false,redpanda:service_healthy:false,redis:service_healthy:false,minio:service_healthy:false,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh","LocalVolumes":"0","Mounts":"","Name":"runmesh-control-plane-1","Names":"runmesh-control-plane-1","Networks":"runmesh_default","Ports":"0.0.0.0:7001-\u003e7001/tcp, [::]:7001-\u003e7001/tcp, 0.0.0.0:8080-\u003e8080/tcp, [::]:8080-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":7001,"PublishedPort":7001,"Protocol":"tcp"},{"URL":"::","TargetPort":7001,"PublishedPort":7001,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8080,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8080,"Protocol":"tcp"}],"RunningFor":"3 minutes ago","Service":"control-plane","Size":"0B","State":"running","Status":"Up 3 minutes (healthy)"} +{"Command":"\"/run.sh\"","CreatedAt":"2026-07-20 19:51:28 -0400 EDT","ExitCode":0,"Health":"","ID":"0e46de4c0532","Image":"grafana/grafana:11.4.0","Labels":"com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=grafana,desktop.docker.io/binds/1/Target=/etc/grafana/provisioning,com.docker.compose.depends_on=prometheus:service_started:false,loki:service_started:false,tempo:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports/3000/tcp=:3001,com.docker.compose.container-number=1,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/grafana/dashboards,desktop.docker.io/binds/0/Target=/var/lib/grafana/dashboards,desktop.docker.io/binds/1/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/grafana/provisioning,com.docker.compose.config-hash=ede6cf07da09ed0d5759a176342e1da8e2d7164eeadeb235d2ead6763b183927,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/1/SourceKind=hostFile,desktop.docker.io/ports.scheme=v2,maintainer=Grafana Labs \u003chello@grafana.com\u003e,com.docker.compose.image=sha256:d8ea37798ccc41061a62ab080f2676dda6bf7815558499f901bdb0f533a456fb","LocalVolumes":"0","Mounts":"/host_mnt/User…,/host_mnt/User…","Name":"runmesh-grafana-1","Names":"runmesh-grafana-1","Networks":"runmesh_default","Ports":"0.0.0.0:3001-\u003e3000/tcp, [::]:3001-\u003e3000/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3000,"PublishedPort":3001,"Protocol":"tcp"},{"URL":"::","TargetPort":3000,"PublishedPort":3001,"Protocol":"tcp"}],"RunningFor":"21 minutes ago","Service":"grafana","Size":"0B","State":"running","Status":"Up 21 minutes"} +{"Command":"\"/opt/keycloak/bin/k…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"18044859ec4f","Image":"quay.io/keycloak/keycloak:26.1","Labels":"vendor=https://www.keycloak.org/,version=26.1.5,maintainer=https://www.keycloak.org/,com.docker.compose.service=keycloak,desktop.docker.io/ports.scheme=v2,org.opencontainers.image.created=2025-04-11T07:58:44.198Z,io.openshift.tags=keycloak security identity,com.docker.compose.oneoff=False,com.redhat.component=,architecture=aarch64,com.docker.compose.image=sha256:be6a86215213145bfb4fb3e2b3ab982a806d00262655abdcf3ffa6a38d241c7c,com.docker.compose.project=runmesh,description=Keycloak Server Image,io.k8s.display-name=Keycloak Server,com.docker.compose.config-hash=cebace789f94dee1045ba9e8f2f123fa3a9807bdde96bcc4af83324d70c53a7e,distribution-scope=public,org.opencontainers.image.url=https://github.com/keycloak-rel/keycloak-rel,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/opt/keycloak/data/import,io.openshift.expose-services=,build-date=2025-04-08T13:14:37Z,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/keycloak,desktop.docker.io/ports/8080/tcp=:8180,io.buildah.version=1.39.0-dev,io.k8s.description=Keycloak Server Image,org.opencontainers.image.description=,summary=Keycloak Server Image,vcs-ref=,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.redhat.build-host=,org.opencontainers.image.documentation=https://www.keycloak.org/documentation,org.opencontainers.image.licenses=Apache-2.0,org.opencontainers.image.source=https://github.com/keycloak-rel/keycloak-rel,org.opencontainers.image.title=keycloak-rel,release=,url=https://www.keycloak.org/,com.redhat.license_terms=,name=keycloak,org.opencontainers.image.revision=e7109eeda3e2630bb65640a6a322f0cb6ff6ddd6,org.opencontainers.image.version=26.1.5,vcs-type=git","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-keycloak-1","Names":"runmesh-keycloak-1","Networks":"runmesh_default","Ports":"0.0.0.0:8180-\u003e8080/tcp, [::]:8180-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8180,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8180,"Protocol":"tcp"}],"RunningFor":"21 minutes ago","Service":"keycloak","Size":"0B","State":"running","Status":"Up 21 minutes"} +{"Command":"\"/usr/bin/loki -conf…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"5e38dd422042","Image":"grafana/loki:3.3.2","Labels":"com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:8af2de1abbdd7aa92b27c9bcc96f0f4140c9096b507c77921ffddf1c6ad6c48f,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.service=loki,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=e3121c4e0132494435f1d1cb47ac059e2b1f2f206f8082c2382c39931a5c7960,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/3100/tcp=:3100","LocalVolumes":"0","Mounts":"","Name":"runmesh-loki-1","Names":"runmesh-loki-1","Networks":"runmesh_default","Ports":"0.0.0.0:3100-\u003e3100/tcp, [::]:3100-\u003e3100/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3100,"PublishedPort":3100,"Protocol":"tcp"},{"URL":"::","TargetPort":3100,"PublishedPort":3100,"Protocol":"tcp"}],"RunningFor":"21 minutes ago","Service":"loki","Size":"0B","State":"running","Status":"Up 21 minutes"} +{"Command":"\"/usr/bin/docker-ent…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"caa90c23d142","Image":"minio/minio:RELEASE.2025-02-07T23-21-09Z","Labels":"maintainer=MinIO Inc \u003cdev@min.io\u003e,com.redhat.component=ubi9-micro-container,architecture=aarch64,com.docker.compose.depends_on=,desktop.docker.io/ports.scheme=v2,io.k8s.description=Very small image which doesn't install the package manager.,io.openshift.expose-services=,name=MinIO,vendor=MinIO Inc \u003cdev@min.io\u003e,build-date=2025-02-06T04:33:00Z,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,com.redhat.license_terms=https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI,summary=MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service.,url=https://www.redhat.com,com.docker.compose.config-hash=c2ef697a7845d5cfeaef7a64bd249c55b2816a78d92a7e6efbaf934addfbfbc9,com.docker.compose.image=sha256:640c22768ed5dbc92eacc14502a1b06a1c708fa60431345c78dfc22917062e93,com.docker.compose.project=runmesh,io.buildah.version=1.38.0-dev,com.docker.compose.container-number=1,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,description=MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads.,desktop.docker.io/ports/9000/tcp=:9000,distribution-scope=public,io.k8s.display-name=Red Hat Universal Base Image 9 Micro,release=RELEASE.2025-02-07T23-21-09Z,com.docker.compose.service=minio,desktop.docker.io/ports/9001/tcp=:9001,vcs-ref=41bec8a95f8231b8982164a882f1b491a864dcce,vcs-type=git,version=RELEASE.2025-02-07T23-21-09Z","LocalVolumes":"1","Mounts":"runmesh_minio-…","Name":"runmesh-minio-1","Names":"runmesh-minio-1","Networks":"runmesh_default","Ports":"0.0.0.0:9000-9001-\u003e9000-9001/tcp, [::]:9000-9001-\u003e9000-9001/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9000,"PublishedPort":9000,"Protocol":"tcp"},{"URL":"::","TargetPort":9000,"PublishedPort":9000,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":9001,"PublishedPort":9001,"Protocol":"tcp"},{"URL":"::","TargetPort":9001,"PublishedPort":9001,"Protocol":"tcp"}],"RunningFor":"21 minutes ago","Service":"minio","Size":"0B","State":"running","Status":"Up 21 minutes (healthy)"} +{"Command":"\"/otelcol-contrib --…\"","CreatedAt":"2026-07-20 19:51:28 -0400 EDT","ExitCode":0,"Health":"","ID":"59bc0d9265b3","Image":"otel/opentelemetry-collector-contrib:0.119.0","Labels":"com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=otel-collector,desktop.docker.io/ports/4317/tcp=:4317,desktop.docker.io/ports/8889/tcp=:8889,org.opencontainers.image.licenses=Apache-2.0,org.opencontainers.image.source=https://github.com/open-telemetry/opentelemetry-collector-releases,com.docker.compose.depends_on=tempo:service_started:false,loki:service_started:false,com.docker.compose.image=sha256:36c35cc213c0f3b64d6e8a3e844dc90822f00725e0e518eaed5b08bcc2231e72,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,org.opencontainers.image.created=2025-02-04T18:02:02Z,org.opencontainers.image.name=opentelemetry-collector-releases,org.opencontainers.image.version=0.119.0,com.docker.compose.config-hash=30112846559126a16e248b6a9473d1a71f5ceee134a40eea9844fcbe6a58f386,com.docker.compose.container-number=1,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/otel-collector.yaml,org.opencontainers.image.revision=c06573884854d06efc3ff0d91d76004d49c80a53,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/etc/otelcol/config.yaml,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/4318/tcp=:4318","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-otel-collector-1","Names":"runmesh-otel-collector-1","Networks":"runmesh_default","Ports":"0.0.0.0:4317-4318-\u003e4317-4318/tcp, [::]:4317-4318-\u003e4317-4318/tcp, 0.0.0.0:8889-\u003e8889/tcp, [::]:8889-\u003e8889/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":4317,"PublishedPort":4317,"Protocol":"tcp"},{"URL":"::","TargetPort":4317,"PublishedPort":4317,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":4318,"PublishedPort":4318,"Protocol":"tcp"},{"URL":"::","TargetPort":4318,"PublishedPort":4318,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":8889,"PublishedPort":8889,"Protocol":"tcp"},{"URL":"::","TargetPort":8889,"PublishedPort":8889,"Protocol":"tcp"}],"RunningFor":"21 minutes ago","Service":"otel-collector","Size":"0B","State":"running","Status":"Up 21 minutes"} +{"Command":"\"docker-entrypoint.s…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"c6e6fb97df3f","Image":"postgres:17-alpine","Labels":"com.docker.compose.service=postgres,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=1,com.docker.compose.image=sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports/5432/tcp=:5432,com.docker.compose.config-hash=d3a0e466ee146131f19622bff6646dfd87ba3247ac4c064ec21f1013fca34f25,com.docker.compose.depends_on=,com.docker.compose.oneoff=False","LocalVolumes":"1","Mounts":"runmesh_postgr…","Name":"runmesh-postgres-1","Names":"runmesh-postgres-1","Networks":"runmesh_default","Ports":"0.0.0.0:5432-\u003e5432/tcp, [::]:5432-\u003e5432/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":5432,"PublishedPort":5432,"Protocol":"tcp"},{"URL":"::","TargetPort":5432,"PublishedPort":5432,"Protocol":"tcp"}],"RunningFor":"21 minutes ago","Service":"postgres","Size":"0B","State":"running","Status":"Up 21 minutes (healthy)"} +{"Command":"\"/bin/prometheus --c…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"fbeb2b4ea323","Image":"prom/prometheus:v3.1.0","Labels":"desktop.docker.io/binds/1/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/prometheus-rules.yaml,desktop.docker.io/ports/9090/tcp=:9090,maintainer=The Prometheus Authors \u003cprometheus-developers@googlegroups.com\u003e,com.docker.compose.container-number=1,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,desktop.docker.io/binds/0/Target=/etc/prometheus/prometheus.yml,desktop.docker.io/binds/1/Target=/etc/prometheus/rules.yaml,com.docker.compose.image=sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218,com.docker.compose.oneoff=False,com.docker.compose.version=5.3.0,desktop.docker.io/binds/1/SourceKind=hostFile,org.opencontainers.image.source=https://github.com/prometheus/prometheus,com.docker.compose.service=prometheus,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/prometheus.yaml,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=cea444590d60d3b6b6325a4da1ec76a5309ba4a70dcc47d4bc24fdbb081da34d,com.docker.compose.depends_on=,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh","LocalVolumes":"1","Mounts":"/host_mnt/User…,/host_mnt/User…,488ec96d9441ad…","Name":"runmesh-prometheus-1","Names":"runmesh-prometheus-1","Networks":"runmesh_default","Ports":"0.0.0.0:9090-\u003e9090/tcp, [::]:9090-\u003e9090/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9090,"PublishedPort":9090,"Protocol":"tcp"},{"URL":"::","TargetPort":9090,"PublishedPort":9090,"Protocol":"tcp"}],"RunningFor":"21 minutes ago","Service":"prometheus","Size":"0B","State":"running","Status":"Up 21 minutes"} +{"Command":"\"docker-entrypoint.s…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"a1445d324e0f","Image":"redis:7.4-alpine","Labels":"com.docker.compose.depends_on=,com.docker.compose.image=sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=681b4575ba55d081dc10ad086d58179ce9a966b8c7ce8dc3a4259491bddf628e,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=redis,desktop.docker.io/ports/6379/tcp=:6379,com.docker.compose.container-number=1","LocalVolumes":"1","Mounts":"runmesh_redis-…","Name":"runmesh-redis-1","Names":"runmesh-redis-1","Networks":"runmesh_default","Ports":"0.0.0.0:6379-\u003e6379/tcp, [::]:6379-\u003e6379/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":6379,"PublishedPort":6379,"Protocol":"tcp"},{"URL":"::","TargetPort":6379,"PublishedPort":6379,"Protocol":"tcp"}],"RunningFor":"21 minutes ago","Service":"redis","Size":"0B","State":"running","Status":"Up 21 minutes (healthy)"} +{"Command":"\"/entrypoint.sh redp…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"8afed046466f","Image":"redpandadata/redpanda:v24.3.15","Labels":"desktop.docker.io/ports/19092/tcp=:19092,desktop.docker.io/ports/9644/tcp=:9644,org.opencontainers.image.authors=Redpanda Data \u003chi@redpanda.com\u003e,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=redpanda,com.docker.compose.config-hash=f7c33b45bd5515ce890e71f032b2ebf336077806438d6d3da8e8f3532837bae2,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"1","Mounts":"runmesh_redpan…","Name":"runmesh-redpanda-1","Names":"runmesh-redpanda-1","Networks":"runmesh_default","Ports":"0.0.0.0:9644-\u003e9644/tcp, [::]:9644-\u003e9644/tcp, 0.0.0.0:19092-\u003e19092/tcp, [::]:19092-\u003e19092/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9644,"PublishedPort":9644,"Protocol":"tcp"},{"URL":"::","TargetPort":9644,"PublishedPort":9644,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":19092,"PublishedPort":19092,"Protocol":"tcp"},{"URL":"::","TargetPort":19092,"PublishedPort":19092,"Protocol":"tcp"}],"RunningFor":"21 minutes ago","Service":"redpanda","Size":"0B","State":"running","Status":"Up 21 minutes (healthy)"} +{"Command":"\"./console\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"7cf18e265933","Image":"redpandadata/console:v2.8.3","Labels":"com.docker.compose.depends_on=redpanda:service_healthy:false,com.docker.compose.image=sha256:fc8006f22072293e9fa6a3aa8a0581ac502bc8262767c32c01ee019eca56bb38,com.docker.compose.service=redpanda-console,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=cb7cc300da1519866e1ef7f83fe7dda15efa317d0575d81fe3db1560604baebb,com.docker.compose.container-number=1,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports/8080/tcp=:8081","LocalVolumes":"0","Mounts":"","Name":"runmesh-redpanda-console-1","Names":"runmesh-redpanda-console-1","Networks":"runmesh_default","Ports":"0.0.0.0:8081-\u003e8080/tcp, [::]:8081-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8081,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8081,"Protocol":"tcp"}],"RunningFor":"21 minutes ago","Service":"redpanda-console","Size":"0B","State":"running","Status":"Up 21 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:06:48 -0400 EDT","ExitCode":0,"Health":"","ID":"ad50227353c7","Image":"runmesh-scheduler","Labels":"com.docker.compose.project=runmesh,com.docker.compose.replace=scheduler-1,com.docker.compose.service=scheduler,com.docker.compose.image=sha256:5f7106c7d013d91a54ab40165a7df2c2fa731b08dfc17c16420feef05cc2c0f5,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=c2ec170571edb0eb096521acddff62c757fe8eaa763425d2f41f085b450236ac,com.docker.compose.container-number=1,com.docker.compose.depends_on=migrate:service_completed_successfully:false,redpanda-init:service_completed_successfully:false,redis:service_healthy:false,com.docker.compose.oneoff=False","LocalVolumes":"0","Mounts":"","Name":"runmesh-scheduler-1","Names":"runmesh-scheduler-1","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"6 minutes ago","Service":"scheduler","Size":"0B","State":"running","Status":"Up 4 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:06:48 -0400 EDT","ExitCode":0,"Health":"","ID":"99b6cbb6af8c","Image":"runmesh-scheduler","Labels":"com.docker.compose.image=sha256:5f7106c7d013d91a54ab40165a7df2c2fa731b08dfc17c16420feef05cc2c0f5,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=scheduler,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=2,com.docker.compose.depends_on=migrate:service_completed_successfully:false,redpanda-init:service_completed_successfully:false,redis:service_healthy:false,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=scheduler-2,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=c2ec170571edb0eb096521acddff62c757fe8eaa763425d2f41f085b450236ac","LocalVolumes":"0","Mounts":"","Name":"runmesh-scheduler-2","Names":"runmesh-scheduler-2","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"6 minutes ago","Service":"scheduler","Size":"0B","State":"running","Status":"Up 4 minutes"} +{"Command":"\"/tempo -config.file…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"cf56816bae5e","Image":"grafana/tempo:2.6.1","Labels":"com.docker.compose.config-hash=4de71b343b413aeaff1ca18f18ac2c9df01f41d4b77c48c3526911fa552ecd98,com.docker.compose.container-number=1,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/tempo.yaml,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/ports/3200/tcp=:3200,org.opencontainers.image.source=https://github.com/grafana/tempo.git,org.opencontainers.image.url=https://github.com/grafana/tempo,com.docker.compose.image=sha256:ef4384fce6e8ad22b95b243d8fc165628cda655376fd50e7850536ad89d71d50,com.docker.compose.oneoff=False,org.opencontainers.image.created=2024-10-15T15:13:57Z,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=tempo,desktop.docker.io/ports.scheme=v2,org.opencontainers.image.revision=24c5b553df91cead5e3afc63e79a5f4deb702b62,com.docker.compose.depends_on=,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,desktop.docker.io/binds/0/Target=/etc/tempo.yaml","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-tempo-1","Names":"runmesh-tempo-1","Networks":"runmesh_default","Ports":"0.0.0.0:3200-\u003e3200/tcp, [::]:3200-\u003e3200/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3200,"PublishedPort":3200,"Protocol":"tcp"},{"URL":"::","TargetPort":3200,"PublishedPort":3200,"Protocol":"tcp"}],"RunningFor":"21 minutes ago","Service":"tempo","Size":"0B","State":"running","Status":"Up 21 minutes"} +{"Command":"\"/docker-entrypoint.…\"","CreatedAt":"2026-07-20 20:06:50 -0400 EDT","ExitCode":0,"Health":"","ID":"f057fa755f99","Image":"runmesh-web","Labels":"com.docker.compose.image=sha256:add65ae330e1dfda32e05134de93308613d22c9d6239da1b75f4667591c588ab,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=web,desktop.docker.io/ports/3000/tcp=:3000,com.docker.compose.container-number=1,com.docker.compose.depends_on=control-plane:service_started:false,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=web-1,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e,com.docker.compose.config-hash=e2afc81b870dc82be2a88d0dee013a23d9fa6ddf3304077545e218b9fc0b47da","LocalVolumes":"0","Mounts":"","Name":"runmesh-web-1","Names":"runmesh-web-1","Networks":"runmesh_default","Ports":"0.0.0.0:3000-\u003e3000/tcp, [::]:3000-\u003e3000/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3000,"PublishedPort":3000,"Protocol":"tcp"},{"URL":"::","TargetPort":3000,"PublishedPort":3000,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"web","Size":"0B","State":"running","Status":"Up 6 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"0fadef053387","Image":"runmesh-worker-go","Labels":"com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.replace=worker-go-1,com.docker.compose.service=worker-go,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=1","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-1","Names":"runmesh-worker-go-1","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"b0f262734c48","Image":"runmesh-worker-go","Labels":"com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=10,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=worker-go-10","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-10","Names":"runmesh-worker-go-10","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"a782c4d6acc2","Image":"runmesh-worker-go","Labels":"com.docker.compose.replace=worker-go-11,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.container-number=11,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-11","Names":"runmesh-worker-go-11","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"b0577b34222f","Image":"runmesh-worker-go","Labels":"com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.project=runmesh,com.docker.compose.replace=worker-go-12,com.docker.compose.service=worker-go,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=12,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-12","Names":"runmesh-worker-go-12","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"533df350c631","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=13,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-13","Names":"runmesh-worker-go-13","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"d376770fba49","Image":"runmesh-worker-go","Labels":"com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=14,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-14","Names":"runmesh-worker-go-14","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"9736ffafb66f","Image":"runmesh-worker-go","Labels":"com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=15,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-15","Names":"runmesh-worker-go-15","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"c16ebcef228f","Image":"runmesh-worker-go","Labels":"com.docker.compose.container-number=16,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-16","Names":"runmesh-worker-go-16","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"b603b8d3224e","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=17,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.service=worker-go","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-17","Names":"runmesh-worker-go-17","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"037ff9dfc257","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=18,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-18","Names":"runmesh-worker-go-18","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"ad26aca4b076","Image":"runmesh-worker-go","Labels":"com.docker.compose.container-number=19,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.project=runmesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-19","Names":"runmesh-worker-go-19","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"cf9881c30539","Image":"runmesh-worker-go","Labels":"com.docker.compose.project=runmesh,com.docker.compose.service=worker-go,com.docker.compose.container-number=2,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-2,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.oneoff=False","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-2","Names":"runmesh-worker-go-2","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"3e1ee3a6f2f7","Image":"runmesh-worker-go","Labels":"com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=20","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-20","Names":"runmesh-worker-go-20","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"4e2c565245b8","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=3,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-3,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-3","Names":"runmesh-worker-go-3","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"55f6c80f09cc","Image":"runmesh-worker-go","Labels":"desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=4,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-4,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-4","Names":"runmesh-worker-go-4","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"6e433f75edbc","Image":"runmesh-worker-go","Labels":"com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=5,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-5,com.docker.compose.service=worker-go","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-5","Names":"runmesh-worker-go-5","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"5f1b2663299d","Image":"runmesh-worker-go","Labels":"com.docker.compose.container-number=6,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-6,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project=runmesh,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-6","Names":"runmesh-worker-go-6","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"dc6f143d618a","Image":"runmesh-worker-go","Labels":"com.docker.compose.project=runmesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-7,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=7,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-7","Names":"runmesh-worker-go-7","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"152e88812edc","Image":"runmesh-worker-go","Labels":"com.docker.compose.version=5.3.0,com.docker.compose.container-number=8,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-8,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-8","Names":"runmesh-worker-go-8","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:09:11 -0400 EDT","ExitCode":0,"Health":"","ID":"5d0dfa8e5cd6","Image":"runmesh-worker-go","Labels":"com.docker.compose.project=runmesh,com.docker.compose.replace=worker-go-9,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=9,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-9","Names":"runmesh-worker-go-9","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/duplicate-delivery-go-test.json b/tests/benchmarks/results/2026-07-21T000619Z/duplicate-delivery-go-test.json new file mode 100644 index 0000000..40c8985 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/duplicate-delivery-go-test.json @@ -0,0 +1,8 @@ +{"Time":"2026-07-21T00:12:51.589716554Z","Action":"start","Package":"github.com/samarth1412/RunMesh/tests/integration"} +{"Time":"2026-07-21T00:12:51.600544929Z","Action":"run","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce"} +{"Time":"2026-07-21T00:12:51.600649554Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Output":"=== RUN TestDuplicateDispatchIsLeasedOnce\n"} +{"Time":"2026-07-21T00:13:07.590917505Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Output":"--- PASS: TestDuplicateDispatchIsLeasedOnce (15.99s)\n"} +{"Time":"2026-07-21T00:13:07.591108839Z","Action":"pass","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Elapsed":15.99} +{"Time":"2026-07-21T00:13:07.591152172Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Output":"PASS\n"} +{"Time":"2026-07-21T00:13:07.592567922Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Output":"ok \tgithub.com/samarth1412/RunMesh/tests/integration\t16.003s\n"} +{"Time":"2026-07-21T00:13:07.59260588Z","Action":"pass","Package":"github.com/samarth1412/RunMesh/tests/integration","Elapsed":16.003} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/duplicate-submission.json b/tests/benchmarks/results/2026-07-21T000619Z/duplicate-submission.json new file mode 100644 index 0000000..23d2986 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/duplicate-submission.json @@ -0,0 +1,10 @@ +{ + "first_run_id": "3c2a24a5-c208-4667-858a-3e16cede5fcd", + "first_status": 201, + "original_input_preserved": true, + "passed": true, + "same_run": true, + "scenario": "duplicate_submission", + "second_run_id": "3c2a24a5-c208-4667-858a-3e16cede5fcd", + "second_status": 200 +} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/end-to-end-completion-database.json b/tests/benchmarks/results/2026-07-21T000619Z/end-to-end-completion-database.json new file mode 100644 index 0000000..d03267f --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/end-to-end-completion-database.json @@ -0,0 +1 @@ +{"tasks" : 1000, "succeeded" : 1000, "other" : 0} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/end-to-end-completion.json b/tests/benchmarks/results/2026-07-21T000619Z/end-to-end-completion.json new file mode 100644 index 0000000..4991b61 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/end-to-end-completion.json @@ -0,0 +1,6 @@ +{ + "scenario": "queued_end_to_end_completion", + "tasks": 1000, + "elapsed_seconds": 16.349, + "tasks_per_second": 61.167 +} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/hardware.txt b/tests/benchmarks/results/2026-07-21T000619Z/hardware.txt new file mode 100644 index 0000000..31d8876 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/hardware.txt @@ -0,0 +1,4 @@ +Darwin samarths-MacBook-Air.local 25.5.0 Darwin Kernel Version 25.5.0: Tue Jun 9 22:26:22 PDT 2026; root:xnu-12377.121.10~1/RELEASE_ARM64_T8132 arm64 +Apple M4 +17179869184 +{"Client":{"Version":"29.6.1","ApiVersion":"1.55","DefaultAPIVersion":"1.55","GitCommit":"8900f1d","GoVersion":"go1.26.4","Os":"darwin","Arch":"arm64","BuildTime":"Fri Jun 26 11:39:35 2026","Context":"desktop-linux"},"Server":{"Platform":{"Name":"Docker Desktop 4.82.0 (233772)"},"Version":"29.6.1","ApiVersion":"1.55","MinAPIVersion":"1.40","Os":"linux","Arch":"arm64","Components":[{"Name":"Engine","Version":"29.6.1","Details":{"ApiVersion":"1.55","Arch":"arm64","BuildTime":"Fri Jun 26 11:39:58 2026","Experimental":"false","GitCommit":"8ec5ab3","GoVersion":"go1.26.4","KernelVersion":"6.12.76-linuxkit","MinAPIVersion":"1.40","Module":"github.com/moby/moby/v2","ModuleVersion":"v2.0.0+unknown","Os":"linux"}},{"Name":"containerd","Version":"v2.2.5","Details":{"GitCommit":"e53c7c1516c3b2bff98eb76f1f4117477e6f4e66"}},{"Name":"runc","Version":"1.3.6","Details":{"GitCommit":"v1.3.6-0-g491b69ba"}},{"Name":"docker-init","Version":"0.19.0","Details":{"GitCommit":"de40ad0"}}],"GitCommit":"8ec5ab3","GoVersion":"go1.26.4","KernelVersion":"6.12.76-linuxkit","BuildTime":"2026-06-26T11:39:58.000000000+00:00"}} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/image-digests.json b/tests/benchmarks/results/2026-07-21T000619Z/image-digests.json new file mode 100644 index 0000000..def94e0 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/image-digests.json @@ -0,0 +1,2 @@ +[{"ID":"sha256:ef4384fce6e8ad22b95b243d8fc165628cda655376fd50e7850536ad89d71d50","ContainerName":"runmesh-tempo-1","Repository":"grafana/tempo","Tag":"2.6.1","Platform":"linux/arm64/v8","Size":49979919,"Created":"2024-10-15T15:14:02.938965353Z","LastTagTime":"2026-07-20T00:47:57.254291174Z"},{"ID":"sha256:36c35cc213c0f3b64d6e8a3e844dc90822f00725e0e518eaed5b08bcc2231e72","ContainerName":"runmesh-otel-collector-1","Repository":"otel/opentelemetry-collector-contrib","Tag":"0.119.0","Platform":"linux/arm64","Size":72650722,"Created":"2025-02-04T18:02:50.606991596Z","LastTagTime":"2026-07-20T00:47:50.053368504Z"},{"ID":"sha256:d8ea37798ccc41061a62ab080f2676dda6bf7815558499f901bdb0f533a456fb","ContainerName":"runmesh-grafana-1","Repository":"grafana/grafana","Tag":"11.4.0","Platform":"linux/arm64","Size":125326597,"Created":"2024-12-04T21:33:24.409833365Z","LastTagTime":"2026-07-20T00:48:13.975356709Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-14","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-17","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-8","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-13","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:5f7106c7d013d91a54ab40165a7df2c2fa731b08dfc17c16420feef05cc2c0f5","ContainerName":"runmesh-scheduler-2","Repository":"runmesh-scheduler","Tag":"latest","Platform":"linux/arm64","Size":8457935,"Created":"2026-07-20T23:08:19.62513022Z","LastTagTime":"2026-07-21T00:06:48.370268927Z"},{"ID":"sha256:5f7106c7d013d91a54ab40165a7df2c2fa731b08dfc17c16420feef05cc2c0f5","ContainerName":"runmesh-scheduler-1","Repository":"runmesh-scheduler","Tag":"latest","Platform":"linux/arm64","Size":8457935,"Created":"2026-07-20T23:08:19.62513022Z","LastTagTime":"2026-07-21T00:06:48.370268927Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-5","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-16","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-19","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d","ContainerName":"runmesh-redpanda-init-1","Repository":"redpandadata/redpanda","Tag":"v24.3.15","Platform":"linux/arm64/v8","Size":183895553,"Created":"2025-06-18T02:52:15.126275953Z","LastTagTime":"2026-07-20T00:48:16.687951877Z"},{"ID":"sha256:fc8006f22072293e9fa6a3aa8a0581ac502bc8262767c32c01ee019eca56bb38","ContainerName":"runmesh-redpanda-console-1","Repository":"redpandadata/console","Tag":"v2.8.3","Platform":"linux/arm64","Size":73250938,"Created":"2025-02-27T18:10:27.99190098Z","LastTagTime":"2026-07-20T00:48:14.485197626Z"},{"ID":"sha256:2b26767b7ce1923782c5c4d862ed639f1a7d94fc40badd645536906613dc725b","ContainerName":"runmesh-worker-python-1","Repository":"runmesh-worker-python","Tag":"latest","Platform":"linux/arm64","Size":60024247,"Created":"2026-07-20T23:22:00.604549877Z","LastTagTime":"2026-07-21T00:06:20.593807386Z"},{"ID":"sha256:00f07b01e8035e8d79e6d01731a153b3382779ec2fae253e980220ed741f840d","ContainerName":"runmesh-control-plane-1","Repository":"runmesh-control-plane","Tag":"latest","Platform":"linux/arm64","Size":10173680,"Created":"2026-07-20T23:08:20.215292553Z","LastTagTime":"2026-07-21T00:09:10.666524632Z"},{"ID":"sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99","ContainerName":"runmesh-redis-1","Repository":"redis","Tag":"7.4-alpine","Platform":"linux/arm64/v8","Size":16790804,"Created":"2026-05-07T17:39:54.062865523Z","LastTagTime":"2026-07-20T00:45:12.219804333Z"},{"ID":"sha256:acded13a336f2b6f35fd9ce14a32834cf1e6b2a7887fe5dfc977b3e563eba39e","ContainerName":"runmesh-migrate-1","Repository":"migrate/migrate","Tag":"v4.18.2","Platform":"linux/arm64","Size":18640157,"Created":"2025-01-27T05:15:01.377819434Z","LastTagTime":"2026-07-20T00:45:00.445620092Z"},{"ID":"sha256:be6a86215213145bfb4fb3e2b3ab982a806d00262655abdcf3ffa6a38d241c7c","ContainerName":"runmesh-keycloak-1","Repository":"quay.io/keycloak/keycloak","Tag":"26.1","Platform":"linux/arm64","Size":241302481,"Created":"2025-04-11T08:01:18.363925612Z","LastTagTime":"2026-07-20T00:48:11.796539791Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-3","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-9","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193","ContainerName":"runmesh-postgres-1","Repository":"postgres","Tag":"17-alpine","Platform":"linux/arm64/v8","Size":114981107,"Created":"2026-07-07T17:46:49.401690597Z","LastTagTime":"2026-07-20T00:47:47.860492044Z"},{"ID":"sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d","ContainerName":"runmesh-redpanda-1","Repository":"redpandadata/redpanda","Tag":"v24.3.15","Platform":"linux/arm64/v8","Size":183895553,"Created":"2025-06-18T02:52:15.126275953Z","LastTagTime":"2026-07-20T00:48:16.687951877Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-20","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-10","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-15","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-2","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-1","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-11","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:640c22768ed5dbc92eacc14502a1b06a1c708fa60431345c78dfc22917062e93","ContainerName":"runmesh-minio-1","Repository":"minio/minio","Tag":"RELEASE.2025-02-07T23-21-09Z","Platform":"linux/arm64","Size":58677008,"Created":"2025-02-08T21:08:53.658721358Z","LastTagTime":"2026-07-20T00:47:58.255328882Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-7","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-4","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-6","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:add65ae330e1dfda32e05134de93308613d22c9d6239da1b75f4667591c588ab","ContainerName":"runmesh-web-1","Repository":"runmesh-web","Tag":"latest","Platform":"linux/arm64","Size":22001475,"Created":"2026-07-20T23:26:55.776613083Z","LastTagTime":"2026-07-21T00:06:21.293446262Z"},{"ID":"sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218","ContainerName":"runmesh-prometheus-1","Repository":"prom/prometheus","Tag":"v3.1.0","Platform":"linux/arm64/v8","Size":110853317,"Created":"2025-01-02T14:15:56.472078076Z","LastTagTime":"2026-07-20T00:48:10.946754847Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-18","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:2d9808d8c519d4a8fc678f820ff0618209837030dbe9f83cdf225ea00a664657","ContainerName":"runmesh-worker-go-12","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:09:10.666008465Z"},{"ID":"sha256:8af2de1abbdd7aa92b27c9bcc96f0f4140c9096b507c77921ffddf1c6ad6c48f","ContainerName":"runmesh-loki-1","Repository":"grafana/loki","Tag":"3.3.2","Platform":"linux/arm64","Size":30732944,"Created":"2024-12-18T17:12:53.000478115Z","LastTagTime":"2026-07-20T00:48:06.06543197Z"}] + diff --git a/tests/benchmarks/results/2026-07-21T000619Z/in-flight-before-termination.json b/tests/benchmarks/results/2026-07-21T000619Z/in-flight-before-termination.json new file mode 100644 index 0000000..217b7c0 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/in-flight-before-termination.json @@ -0,0 +1 @@ +{"running_count" : 7, "task_ids" : ["00abdf6d-2f9d-4340-9adc-1d042ae8e669", "01af07c5-5146-4616-adf3-cc86dae75414", "30fbc9a2-68bc-4ee4-8914-982d4445d553", "623afe4e-89d9-4db2-831b-460e5c43b52c", "70f110ad-c923-42b0-b738-a7ee8480cf1f", "961146b8-117f-4559-80e8-41b7ec760736", "d63504eb-6522-4fbd-8127-9d0702ccbf9a"]} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/kafka-topology-after.json b/tests/benchmarks/results/2026-07-21T000619Z/kafka-topology-after.json new file mode 100644 index 0000000..5b900b2 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/kafka-topology-after.json @@ -0,0 +1 @@ +[{"summary":{"name":"runmesh.tasks","internal":false,"partitions":6,"replicas":1,"error":""},"configs":[{"key":"cleanup.policy","value":"delete","source":"DEFAULT_CONFIG"},{"key":"compression.type","value":"producer","source":"DEFAULT_CONFIG"},{"key":"delete.retention.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"flush.bytes","value":"262144","source":"DEFAULT_CONFIG"},{"key":"flush.ms","value":"100","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"max.message.bytes","value":"1048576","source":"DEFAULT_CONFIG"},{"key":"message.timestamp.type","value":"CreateTime","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.mode","value":"disabled","source":"DEFAULT_CONFIG"},{"key":"redpanda.leaders.preference","value":"none","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.read","value":"false","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.write","value":"false","source":"DEFAULT_CONFIG"},{"key":"retention.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.ms","value":"86400000","source":"DEFAULT_CONFIG"},{"key":"retention.ms","value":"604800000","source":"DEFAULT_CONFIG"},{"key":"segment.bytes","value":"134217728","source":"DEFAULT_CONFIG"},{"key":"segment.ms","value":"1209600000","source":"DEFAULT_CONFIG"},{"key":"write.caching","value":"true","source":"DEFAULT_CONFIG"}],"partitions":[{"partition":0,"leader":0,"epoch":4,"replicas":[0],"log_start_offset":0,"high_watermark":84829},{"partition":1,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":7385},{"partition":2,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":16744},{"partition":3,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":12897},{"partition":4,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":11387},{"partition":5,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":20835}]}] diff --git a/tests/benchmarks/results/2026-07-21T000619Z/kafka-topology-before.json b/tests/benchmarks/results/2026-07-21T000619Z/kafka-topology-before.json new file mode 100644 index 0000000..3349d59 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/kafka-topology-before.json @@ -0,0 +1 @@ +[{"summary":{"name":"runmesh.tasks","internal":false,"partitions":6,"replicas":1,"error":""},"configs":[{"key":"cleanup.policy","value":"delete","source":"DEFAULT_CONFIG"},{"key":"compression.type","value":"producer","source":"DEFAULT_CONFIG"},{"key":"delete.retention.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"flush.bytes","value":"262144","source":"DEFAULT_CONFIG"},{"key":"flush.ms","value":"100","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"max.message.bytes","value":"1048576","source":"DEFAULT_CONFIG"},{"key":"message.timestamp.type","value":"CreateTime","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.mode","value":"disabled","source":"DEFAULT_CONFIG"},{"key":"redpanda.leaders.preference","value":"none","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.read","value":"false","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.write","value":"false","source":"DEFAULT_CONFIG"},{"key":"retention.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.ms","value":"86400000","source":"DEFAULT_CONFIG"},{"key":"retention.ms","value":"604800000","source":"DEFAULT_CONFIG"},{"key":"segment.bytes","value":"134217728","source":"DEFAULT_CONFIG"},{"key":"segment.ms","value":"1209600000","source":"DEFAULT_CONFIG"},{"key":"write.caching","value":"true","source":"DEFAULT_CONFIG"}],"partitions":[{"partition":0,"leader":0,"epoch":4,"replicas":[0],"log_start_offset":0,"high_watermark":76946},{"partition":1,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":1702},{"partition":2,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":9287},{"partition":3,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":6073},{"partition":4,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":6088},{"partition":5,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":13835}]}] diff --git a/tests/benchmarks/results/2026-07-21T000619Z/lease-recovery-distribution.json b/tests/benchmarks/results/2026-07-21T000619Z/lease-recovery-distribution.json new file mode 100644 index 0000000..8dcc4ac --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/lease-recovery-distribution.json @@ -0,0 +1 @@ +{"samples" : 7, "p50_ms" : 131668.823, "p95_ms" : 160578.6593, "p99_ms" : 167478.96146000002} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/scheduler-dispatch.json b/tests/benchmarks/results/2026-07-21T000619Z/scheduler-dispatch.json new file mode 100644 index 0000000..a50ce0c --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/scheduler-dispatch.json @@ -0,0 +1,6 @@ +{ + "scenario": "scheduler_dispatch", + "tasks": 10000, + "elapsed_seconds": 24.152, + "dispatches_per_second": 414.042 +} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/verification.json b/tests/benchmarks/results/2026-07-21T000619Z/verification.json new file mode 100644 index 0000000..07b0ca4 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/verification.json @@ -0,0 +1,21 @@ +{ + "checks": { + "active_workflows": true, + "api_requests": true, + "api_zero_failures": true, + "duplicate_delivery": true, + "duplicate_submission": true, + "end_to_end_zero_task_loss": true, + "lease_recovered": true, + "multi_partition_delivery": true, + "multiple_tasks_in_flight": true, + "six_kafka_partitions": true, + "submission_requests": true, + "submission_zero_failures": true, + "ten_thousand_tasks": true, + "worker_pool_terminated": true, + "zero_task_loss": true + }, + "observed_partitions_with_new_records": 6, + "passed": true +} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/worker-crash-result.json b/tests/benchmarks/results/2026-07-21T000619Z/worker-crash-result.json new file mode 100644 index 0000000..0eb133e --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/worker-crash-result.json @@ -0,0 +1 @@ +{"scenario" : "worker_termination_and_lease_recovery", "tasks" : 10000, "succeeded" : 10000, "other" : 0, "attempts" : 10007, "recovered_tasks" : 7, "duplicate_attempts" : 7, "recovery_seconds" : 210.388} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/worker-crash-setup.json b/tests/benchmarks/results/2026-07-21T000619Z/worker-crash-setup.json new file mode 100644 index 0000000..fc0bb2d --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/worker-crash-setup.json @@ -0,0 +1,111 @@ +{ + "idempotency_prefix": "validation-crash-20260721T000619Z", + "preparation_seconds": 0.624, + "run_count": 100, + "run_ids": [ + "38a0149b-acbf-4454-8ae3-a1204291d4e4", + "99712f96-4c11-4bc0-8769-27a6cde56ee3", + "9a8b9c52-aa00-4942-8f06-8de0845a383d", + "608561c1-836b-4f42-b563-4de2107fa2b8", + "bd4d95e4-a885-41a9-b10e-9c351038381e", + "b8263a72-325f-47ac-8fc2-bd43bd4b9d7b", + "0f4128ab-361b-4a5e-bdbe-4d6db79f3a28", + "cd811655-936a-4279-89ef-fd2418194456", + "4e66fb79-00c6-44a2-97f0-e9b405ad84ac", + "109c05f8-c5fc-4078-9609-0ffcb30127a2", + "6713426d-06b3-4231-aea5-462664a9ee6f", + "65151d08-5e6b-4e29-98b2-600c05637e7c", + "790934ac-cece-42ba-b495-3739c103d96c", + "9efaa3fb-0998-42af-87f9-717d50a69aa5", + "949163db-0dcb-4f1c-ada7-b948b0895fb8", + "bc42c1b1-3c7b-4b47-952a-b648f5c45559", + "86437d6a-b7dc-4f14-b6f1-68ab95875456", + "362dbc50-5666-4057-bc9e-82246dd1c78f", + "1eb9bec3-06cc-4f5a-9813-b902f3886d03", + "64674113-b390-443e-975b-be14e6ef2a40", + "8d3b7f4d-6d98-4723-8c28-9ed42fcc9461", + "945aada8-5c73-4388-b27e-38be91426755", + "362b76b7-1eb8-45dd-8e67-404ac251daad", + "e449bc58-4dc9-4fe0-b78a-24d09daa0c78", + "ab40fce0-ebf1-4841-b90a-b223bd76dc14", + "34301fc2-0ec1-45e6-b552-aae6f778ee69", + "31f383cf-1c0d-4119-b592-441667f6342b", + "8a50ad49-8759-4d3e-b1d4-48a214157b3d", + "ef02cc86-ba58-4d85-a494-e00033681960", + "aeaeac67-ea4c-4f2a-9893-9e111987ce8e", + "12f216a6-4abf-4cf0-a2f6-1b3957073e49", + "256d3d92-e5fe-43af-926f-afd9f4a1e351", + "6f086826-352d-4eb6-a10a-9fcc0ca925eb", + "fc04cbf6-b3c2-4f28-bc26-62388cb594ca", + "7524e549-7bcd-41ea-9d59-aec4a2234270", + "28a48bc4-8253-46be-bf09-0007061b853a", + "af686e2b-b5d8-45e7-a142-27c96400a932", + "fda783f5-f5f1-48e7-ab7a-ac9036874b7d", + "99cfa7dd-cc99-4774-99dc-c319e34b81d2", + "04e59e51-7222-4b01-a0e6-0539a489a22f", + "065aa7e5-cee8-47b8-ac69-b7b3760c8718", + "010f8fd2-7ef9-4c92-8bea-c1d00ae00f71", + "80bd2f54-61aa-4971-9e0a-75e113e7c495", + "cfbfdaf4-cfbd-46d3-a238-cc90598e243b", + "4ddc1d05-2361-4686-8532-83f59f2c019b", + "eff0e818-64a0-4000-b7ee-7b30512e6df3", + "6281305d-8a9c-424d-ba6f-fe1197f0af13", + "d7d21239-59ee-4e38-a089-32f5bb42c571", + "e5a48448-3758-42f0-b86f-12395fbb5ffc", + "13765eda-eb25-445d-aed1-5eb2f3050926", + "3112dd20-5e32-4aab-a5e2-df9b36224443", + "65b69d72-cf3b-42d2-834d-3339c47adcf4", + "5be90fb6-2fe9-4563-a4c6-b28511614134", + "ca63353c-428c-4641-afdb-45870afc3698", + "80218394-ffd0-43f7-948e-d0e0395a5ce8", + "483006f3-ace8-446b-bdaf-05ec5804cc72", + "5a884778-bc7d-45ce-9724-4c717e18aeb0", + "40f49bad-4e4e-4866-af88-322b12c7e798", + "6d7b1fcd-0a40-482f-a2bc-0ffc99e51442", + "89d8282c-cacd-4c18-95a2-ed970ec01560", + "0faf8675-f032-424d-9818-9a30fbe2f8ad", + "dddfed1d-b591-4537-a975-5db79a2cc1c6", + "ce08ac86-3ccb-4aa2-8c72-b43b77c51d45", + "c8939937-fa08-400d-8cc5-5e18ec6d9569", + "ab062a64-ae42-4d26-bf15-b28977d00331", + "8f996960-ca10-4924-8a31-4a522a661572", + "6c5e8fd1-ab18-4230-9375-8378415ac624", + "4bfd0d9b-9912-4b62-bbce-bb306e583c15", + "00701d78-40d0-430d-b947-c03d9b6a4be5", + "0f5b7d32-8250-46ad-8511-f939296ec4ea", + "8aa385d3-bb74-4926-b603-04f2e529c80f", + "efebbfbf-3a59-40b8-ad0e-0994af69b853", + "bebed3e8-0728-4e1f-be0d-0dec3e54a00d", + "4d30fc15-86bb-4a6a-872e-37a7a1c3a33d", + "4f3ee66f-c000-43eb-9dd6-b8f3e2facbf6", + "5dd2327a-9df9-4876-bdcb-9e5121e186e3", + "360c8592-27a6-429f-a909-cdf5c677cc14", + "c1fe1a0b-73e8-483b-bcd9-3cc29c1a2cc1", + "afc59c98-57dc-4687-a9f4-662d10becd4e", + "e4c2da1d-eb8b-4bd5-b4bb-4bab08af1a7b", + "feb318a1-78dd-4912-b6bf-81766b046d06", + "9ddd08cf-39da-46e6-84a5-0b6c0f0ce44c", + "c4435e3b-2846-44c8-a5bc-250877a29ac4", + "e3fe65ca-0b49-4093-959d-95575432530e", + "112b7c78-37af-4525-bc73-ccfc7336556a", + "34cece5e-1efc-4d3b-abde-a0360a926093", + "fb3be08f-b725-4e72-8471-1d50ccb99c79", + "37c92b1b-0561-4b6e-b0c1-19ee52a9e486", + "2b213912-459e-439a-b002-7141ccc6ff2e", + "27a87d72-f399-492c-ab6c-48d7b9028693", + "0c3945eb-bab4-460d-8b25-3731f6953222", + "617d48c5-c915-40f6-950d-de59a5ebd7e0", + "826ac9e7-625b-449e-a1a1-bbdc1e2661f3", + "b8385e8e-46b1-4668-8fe8-9df425744ac4", + "12ea16a3-881e-4bbf-88e0-1e8cf320d992", + "33bf6cfb-27a6-4031-bf9a-9c6f419ac65f", + "6d4c59d3-d5d7-4821-8743-2ac791aa47d7", + "92644eb1-b71f-4557-9272-81e5c5ada25b", + "cdd640c9-2d00-418f-986b-8e2c8d9510ec", + "3df02a88-0596-4a1e-ad5f-2eacae3c8aee" + ], + "scenario": "worker_termination_and_lease_recovery", + "task_count": 10000, + "tasks_per_run": 100, + "workflow_id": "1fe6d80e-ea9f-447a-9da3-e8d2e32e287f" +} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/worker-termination.json b/tests/benchmarks/results/2026-07-21T000619Z/worker-termination.json new file mode 100644 index 0000000..2e0aa6f --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/worker-termination.json @@ -0,0 +1,6 @@ +{ + "terminated_workers": 20, + "idempotency_prefix": "validation-crash-20260721T000619Z", + "in_flight_tasks": 7, + "terminated_at": "2026-07-21T00:09:18.873746+00:00" +} diff --git a/tests/benchmarks/results/2026-07-21T000619Z/workflow-submissions.json b/tests/benchmarks/results/2026-07-21T000619Z/workflow-submissions.json new file mode 100644 index 0000000..6b3b28e --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T000619Z/workflow-submissions.json @@ -0,0 +1,142 @@ +{ + "root_group": { + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": {}, + "checks": { + "created": { + "fails": 0, + "name": "created", + "path": "::created", + "id": "e26a6144a84abb127f65c1e5808b420c", + "passes": 1501 + } + }, + "name": "", + "path": "" + }, + "metrics": { + "http_req_duration": { + "avg": 1.761633111259159, + "min": 0.971583, + "med": 1.509875, + "p(90)": 2.300959, + "p(95)": 2.902916, + "p(99)": 6.223375, + "max": 19.94575 + }, + "http_req_blocked": { + "max": 41.942334, + "avg": 0.10604307395069905, + "min": 0.001708, + "med": 0.003958, + "p(90)": 0.01025, + "p(95)": 0.61875, + "p(99)": 0.832 + }, + "http_req_sending": { + "p(99)": 0.089167, + "max": 0.846917, + "avg": 0.02420935576282475, + "min": 0.008083, + "med": 0.020166, + "p(90)": 0.033792, + "p(95)": 0.047 + }, + "data_sent": { + "count": 2330351, + "rate": 77669.43068992859 + }, + "http_req_duration{expected_response:true}": { + "avg": 1.761633111259159, + "min": 0.971583, + "med": 1.509875, + "p(90)": 2.300959, + "p(95)": 2.902916, + "p(99)": 6.223375, + "max": 19.94575 + }, + "http_reqs": { + "rate": 50.02757759049294, + "count": 1501 + }, + "data_received": { + "count": 812514, + "rate": 27080.68432935495 + }, + "checks": { + "passes": 1501, + "fails": 0, + "thresholds": { + "rate==1": false + }, + "value": 1 + }, + "http_req_waiting": { + "max": 19.657708, + "avg": 1.7007772944703519, + "min": 0.937875, + "med": 1.452917, + "p(90)": 2.228459, + "p(95)": 2.814584, + "p(99)": 6.142667 + }, + "http_req_failed": { + "passes": 0, + "fails": 1501, + "thresholds": { + "rate==0": false + }, + "value": 0 + }, + "iteration_duration": { + "p(99)": 7.140125, + "max": 49.259459, + "avg": 2.0182032504996634, + "min": 1.056333, + "med": 1.678, + "p(90)": 2.607792, + "p(95)": 3.272292 + }, + "http_req_receiving": { + "min": 0.01425, + "med": 0.032917, + "p(90)": 0.052708, + "p(95)": 0.06625, + "p(99)": 0.102, + "max": 0.284167, + "avg": 0.03664646102598266 + }, + "iterations": { + "count": 1501, + "rate": 50.02757759049294 + }, + "http_req_connecting": { + "min": 0, + "med": 0, + "p(90)": 0, + "p(95)": 0.573542, + "p(99)": 0.775292, + "max": 41.867542, + "avg": 0.09856079546968684 + }, + "http_req_tls_handshaking": { + "max": 0, + "avg": 0, + "min": 0, + "med": 0, + "p(90)": 0, + "p(95)": 0, + "p(99)": 0 + }, + "vus": { + "value": 0, + "min": 0, + "max": 0 + }, + "vus_max": { + "value": 100, + "min": 100, + "max": 100 + } + } +} \ No newline at end of file diff --git a/tests/benchmarks/results/2026-07-21T002100Z/README.md b/tests/benchmarks/results/2026-07-21T002100Z/README.md new file mode 100644 index 0000000..f4c2f45 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/README.md @@ -0,0 +1,40 @@ +# Local benchmark evidence — 2026-07-21T002100Z + +Source commit: `0c48987d5259bcdc62cadaecd163d996ad0af0a4` + +This run was produced on an Apple M4 MacBook Air with 16 GiB RAM using +Docker Desktop 4.82.0. Performance values are report-only local evidence; +correctness, tenant isolation, and zero-loss checks are blocking. + +| Scenario | Recorded result | +| --- | --- | +| Authenticated reads at 100 requests/s | 3,001 requests, 0 failures; 1.416/3.228/5.745 ms p50/p95/p99 | +| Workflow submissions at 50 requests/s | 1,501 requests, 0 failures; 1.560/3.236/13.553 ms p50/p95/p99 | +| Simultaneously active workflows | 1,000 created in 20.200 seconds, 0 failures; 6.076/8.640/9.736 ms creation p50/p95/p99 | +| Queued end-to-end completion | 1,000 tasks in 32.524 seconds; 30.747 tasks/s | +| Scheduler dispatch | 10,000 tasks in 24.243 seconds; 412.482 dispatches/s | +| Worker termination and recovery | 20 workers terminated with 6 tasks running; 10,000/10,000 succeeded; 6 recovered tasks/duplicate attempts; 0 permanent loss; 202.867-second full drain | +| Recovered-attempt delay | 6 samples; 125.145/167.824/168.519 seconds p50/p95/p99 from expired attempt end to next attempt start | +| Duplicate submission | Same run returned with `201` then `200`; original input preserved | +| Duplicate Kafka delivery | `TestDuplicateDispatchIsLeasedOnce` passed | + +The topology used two scheduler replicas, six Redpanda partitions, 12 workers +for the 1,000-task completion phase, and 20 Go workers for termination/recovery. +The verifier observed new records on all six partitions and passed all 15 +blocking checks. Recovery latency includes queueing behind the 10,000-task +backlog and is reported as a limitation, not a latency target. + +Raw evidence in this directory is not manually edited: + +- `api-read.json` and `workflow-submissions.json` are k6 summaries. +- `active-workflows*.json`, `end-to-end-completion*.json`, and + `scheduler-dispatch.json` record workload creation and database outcomes. +- `in-flight-before-termination.json`, `worker-termination.json`, + `worker-crash-result.json`, and `lease-recovery-distribution.json` record the + worker-loss experiment. +- `duplicate-submission.json` and `duplicate-delivery-go-test.json` capture the + two idempotency checks. +- `kafka-topology-*.json`, `compose-topology.json`, `hardware.txt`, + `image-digests.json`, and `commit-sha.txt` identify the environment. +- `verification.json` is the machine-readable acceptance result. +- `commands.sh` is the exact benchmark driver copied at run start. diff --git a/tests/benchmarks/results/2026-07-21T002100Z/active-workflows-database.json b/tests/benchmarks/results/2026-07-21T002100Z/active-workflows-database.json new file mode 100644 index 0000000..756a691 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/active-workflows-database.json @@ -0,0 +1 @@ +{"active_runs" : 1000, "active_tasks" : 1000} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/active-workflows.json b/tests/benchmarks/results/2026-07-21T002100Z/active-workflows.json new file mode 100644 index 0000000..69848ef --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/active-workflows.json @@ -0,0 +1,22 @@ +{ + "create_latency": { + "p50_ms": 6.076, + "p95_ms": 8.64, + "p99_ms": 9.736 + }, + "created": 1000, + "elapsed_seconds": 20.2, + "failure_count": 0, + "failure_rate": 0, + "idempotency_prefix": "validation-active-20260721T002324Z", + "requested": 1000, + "sample_run_ids": [ + "7245969e-86b2-4806-b492-74fbd525814d", + "43d1b46e-c9a0-4902-bc31-acb2fc460d91", + "1570e8f0-99c9-457e-8637-56a41fca1785", + "dbaa2833-dad9-4361-9b5c-7f1dce19c8e1", + "092e95d4-5c7e-4887-9f35-f0e03ab1b504" + ], + "scenario": "simultaneously_active_workflows", + "workflow_id": "e573b078-0204-4d0f-a471-7a78b591ac3a" +} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/api-read.json b/tests/benchmarks/results/2026-07-21T002100Z/api-read.json new file mode 100644 index 0000000..870e488 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/api-read.json @@ -0,0 +1,142 @@ +{ + "root_group": { + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": {}, + "checks": { + "authenticated request succeeded": { + "path": "::authenticated request succeeded", + "id": "12f4c1307352dc7d8175954f46790cf7", + "passes": 3001, + "fails": 0, + "name": "authenticated request succeeded" + } + }, + "name": "", + "path": "" + }, + "metrics": { + "http_req_failed": { + "passes": 0, + "fails": 3001, + "thresholds": { + "rate==0": false + }, + "value": 0 + }, + "http_req_receiving": { + "avg": 0.03494350616461184, + "min": 0.010916, + "med": 0.027209, + "p(90)": 0.055292, + "p(95)": 0.083791, + "p(99)": 0.15975, + "max": 0.50725 + }, + "http_req_duration": { + "min": 0.577083, + "med": 1.416416, + "p(90)": 2.970417, + "p(95)": 3.227917, + "p(99)": 5.745042, + "max": 82.70675, + "avg": 1.9609528937021061 + }, + "data_sent": { + "count": 4228409, + "rate": 140933.02055975512 + }, + "http_req_connecting": { + "p(90)": 0, + "p(95)": 0, + "p(99)": 0.500334, + "max": 0.856959, + "avg": 0.0077517094301899365, + "min": 0, + "med": 0 + }, + "http_req_waiting": { + "med": 1.368208, + "p(90)": 2.878375, + "p(95)": 3.123042, + "p(99)": 5.648625, + "max": 82.609167, + "avg": 1.896724442185942, + "min": 0.553791 + }, + "http_req_blocked": { + "med": 0.003834, + "p(90)": 0.016167, + "p(95)": 0.020042, + "p(99)": 0.524459, + "max": 1.076083, + "avg": 0.01573147184271901, + "min": 0.001166 + }, + "vus_max": { + "value": 40, + "min": 40, + "max": 40 + }, + "iteration_duration": { + "avg": 2.1236605238253925, + "min": 0.628292, + "med": 1.5245, + "p(90)": 3.25, + "p(95)": 3.498542, + "p(99)": 6.683667, + "max": 83.715584 + }, + "vus": { + "value": 1, + "min": 0, + "max": 1 + }, + "http_req_duration{expected_response:true}": { + "max": 82.70675, + "avg": 1.9609528937021061, + "min": 0.577083, + "med": 1.416416, + "p(90)": 2.970417, + "p(95)": 3.227917, + "p(99)": 5.745042 + }, + "checks": { + "passes": 3001, + "fails": 0, + "thresholds": { + "rate==1": false + }, + "value": 1 + }, + "http_req_sending": { + "max": 0.482083, + "avg": 0.029284945351549543, + "min": 0.003709, + "med": 0.019125, + "p(90)": 0.054333, + "p(95)": 0.063209, + "p(99)": 0.097375 + }, + "http_req_tls_handshaking": { + "med": 0, + "p(90)": 0, + "p(95)": 0, + "p(99)": 0, + "max": 0, + "avg": 0, + "min": 0 + }, + "data_received": { + "count": 1395465, + "rate": 46510.89748778292 + }, + "http_reqs": { + "count": 3001, + "rate": 100.02343545759767 + }, + "iterations": { + "count": 3001, + "rate": 100.02343545759767 + } + } +} \ No newline at end of file diff --git a/tests/benchmarks/results/2026-07-21T002100Z/commands.sh b/tests/benchmarks/results/2026-07-21T002100Z/commands.sh new file mode 100755 index 0000000..a0917b0 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/commands.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Runs only against the local Compose project. It never provisions cloud +# resources. Raw result directories are immutable: choose a new path to rerun. +result_dir=${1:-tests/benchmarks/results/$(date -u +%Y-%m-%dT%H%M%SZ)} +if [[ -e "$result_dir" ]]; then + echo "result directory already exists: $result_dir" >&2 + exit 1 +fi +mkdir -p "$result_dir" +run_suffix=$(date -u +%Y%m%dT%H%M%SZ) +active_prefix="validation-active-$run_suffix" +crash_prefix="validation-crash-$run_suffix" +duplicate_prefix="validation-duplicate-$run_suffix" + +cp "$0" "$result_dir/commands.sh" +docker compose up -d --build +docker compose up -d --scale scheduler=2 --scale worker-go=1 --scale worker-python=1 scheduler worker-go worker-python +for endpoint in \ + http://localhost:8180/realms/runmesh/.well-known/openid-configuration \ + http://localhost:8080/health/ready; do + ready=0 + for _ in $(seq 1 90); do + if curl -fsS "$endpoint" >/dev/null; then ready=1; break; fi + sleep 2 + done + [[ "$ready" == 1 ]] || { echo "benchmark dependency did not become ready: $endpoint" >&2; exit 1; } +done +benchmark_token=$(python3 tests/benchmarks/local.py token) + +docker compose exec -T redpanda rpk topic describe runmesh.tasks --format json > "$result_dir/kafka-topology-before.json" +read_workflow=$(python3 tests/benchmarks/local.py definition --prefix "validation-read-$run_suffix") +docker run --rm --add-host host.docker.internal:host-gateway \ + -e RUNMESH_API=http://host.docker.internal:8080 \ + -e RUNMESH_READ_PATH="/v1/workflows/$read_workflow" \ + -e RUNMESH_TOKEN="$benchmark_token" -e RUNMESH_RATE=100 -e RUNMESH_DURATION=30s \ + -v "$PWD/tests/load":/scripts:ro -v "$PWD/$result_dir":/results \ + grafana/k6:0.57.0 run --summary-export=/results/api-read.json /scripts/api-read.js + +sleep 3 +submission_workflow=$(python3 tests/benchmarks/local.py definition --prefix "validation-submission-$run_suffix") +docker run --rm --add-host host.docker.internal:host-gateway \ + -e RUNMESH_API=http://host.docker.internal:8080 \ + -e RUNMESH_TOKEN="$benchmark_token" -e RUNMESH_WORKFLOW_ID="$submission_workflow" \ + -e RUNMESH_RATE=50 -e RUNMESH_DURATION=30s \ + -v "$PWD/tests/load":/scripts:ro -v "$PWD/$result_dir":/results \ + grafana/k6:0.57.0 run --summary-export=/results/workflow-submissions.json /scripts/submissions.js + +docker compose stop scheduler worker-python worker-go +python3 tests/benchmarks/local.py active --count 1000 --prefix "$active_prefix" > "$result_dir/active-workflows.json" +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('active_runs',count(*),'active_tasks',sum(task_count)) FROM (SELECT wr.id,count(tr.id) task_count FROM workflow_runs wr JOIN task_runs tr ON tr.workflow_run_id=wr.id WHERE wr.idempotency_key LIKE '$active_prefix-%' AND wr.status='RUNNING' GROUP BY wr.id) rows" \ + > "$result_dir/active-workflows-database.json" + +# Start two schedulers and twelve workers against six partitions, then measure +# drain throughput for the 1,000 distinct workflow keys created above. +completion_start=$(python3 -c 'import time; print(time.time_ns())') +docker compose up -d --scale scheduler=2 scheduler +docker compose up -d --build --scale worker-go=12 worker-go +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM workflow_runs WHERE idempotency_key LIKE '$active_prefix-%' AND status NOT IN ('SUCCEEDED','FAILED','CANCELLED')") != 0 ]]; do sleep 0.2; done +completion_end=$(python3 -c 'import time; print(time.time_ns())') +python3 - "$completion_start" "$completion_end" > "$result_dir/end-to-end-completion.json" <<'PY' +import json, sys +elapsed = (int(sys.argv[2]) - int(sys.argv[1])) / 1_000_000_000 +print(json.dumps({"scenario":"queued_end_to_end_completion","tasks":1000,"elapsed_seconds":round(elapsed,3),"tasks_per_second":round(1000/elapsed,3)}, indent=2)) +PY +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('tasks',count(*),'succeeded',count(*) FILTER (WHERE tr.status='SUCCEEDED'),'other',count(*) FILTER (WHERE tr.status!='SUCCEEDED')) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$active_prefix-%'" \ + > "$result_dir/end-to-end-completion-database.json" + +# Begin at the current topic end so this scenario observes only its own work. +docker compose stop worker-go +docker compose exec -T redpanda rpk group seek runmesh-workers --to end --topics runmesh.tasks >/dev/null +python3 tests/benchmarks/local.py prepare --tasks 10000 --prefix "$crash_prefix" > "$result_dir/worker-crash-setup.json" +dispatch_start=$(python3 -c 'import time; print(time.time_ns())') +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status IN ('READY','BLOCKED')") != 0 ]]; do sleep 0.1; done +dispatch_end=$(python3 -c 'import time; print(time.time_ns())') +python3 - "$dispatch_start" "$dispatch_end" > "$result_dir/scheduler-dispatch.json" <<'PY' +import json, sys +elapsed = (int(sys.argv[2]) - int(sys.argv[1])) / 1_000_000_000 +print(json.dumps({"scenario":"scheduler_dispatch","tasks":10000,"elapsed_seconds":round(elapsed,3),"dispatches_per_second":round(10000/elapsed,3)}, indent=2)) +PY + +# Keep enough tasks running to occupy every available Kafka partition before +# terminating the entire 20-container worker pool. +docker compose exec -T postgres psql -U runmesh -d runmesh -c \ + "WITH selected AS (SELECT tr.id FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' ORDER BY tr.id LIMIT 100) UPDATE task_runs SET input='{\"delay_ms\":5000}'::jsonb WHERE id IN (SELECT id FROM selected);" >/dev/null +docker compose up -d --build --scale worker-go=20 worker-go +deadline=$((SECONDS+60)) +while true; do + running=$(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status='RUNNING'") + [[ "$running" -ge 4 ]] && break + [[ "$SECONDS" -ge "$deadline" ]] && { echo "fewer than four tasks became concurrently RUNNING" >&2; exit 1; } + sleep 0.1 +done +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('running_count',count(*),'task_ids',json_agg(id ORDER BY id)) FROM (SELECT tr.id FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status='RUNNING') active" \ + > "$result_dir/in-flight-before-termination.json" +worker_ids=( $(docker compose ps -q worker-go) ) +docker kill "${worker_ids[@]}" >/dev/null +python3 - "${#worker_ids[@]}" "$crash_prefix" "$running" > "$result_dir/worker-termination.json" <<'PY' +import datetime, json, sys +print(json.dumps({"terminated_workers":int(sys.argv[1]),"idempotency_prefix":sys.argv[2],"in_flight_tasks":int(sys.argv[3]),"terminated_at":datetime.datetime.now(datetime.timezone.utc).isoformat()}, indent=2)) +PY +recovery_start=$(python3 -c 'import time; print(time.time_ns())') +docker compose up -d --scale worker-go=20 worker-go +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM workflow_runs WHERE idempotency_key LIKE '$crash_prefix-%' AND status NOT IN ('SUCCEEDED','FAILED','CANCELLED')") != 0 ]]; do sleep 1; done +recovery_end=$(python3 -c 'import time; print(time.time_ns())') +recovery_seconds=$(python3 -c "print(round(($recovery_end-$recovery_start)/1_000_000_000,3))") +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('scenario','worker_termination_and_lease_recovery','tasks',count(*),'succeeded',count(*) FILTER (WHERE tr.status='SUCCEEDED'),'other',count(*) FILTER (WHERE tr.status!='SUCCEEDED'),'attempts',sum(tr.attempt_count),'recovered_tasks',count(*) FILTER (WHERE tr.attempt_count > 1),'duplicate_attempts',sum(tr.attempt_count)-count(*),'recovery_seconds',$recovery_seconds) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%'" \ + > "$result_dir/worker-crash-result.json" +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "WITH gaps AS (SELECT EXTRACT(EPOCH FROM (next_attempt.started_at-expired.ended_at))*1000 milliseconds FROM task_attempts expired JOIN task_attempts next_attempt ON next_attempt.task_run_id=expired.task_run_id AND next_attempt.attempt_number=expired.attempt_number+1 JOIN task_runs tr ON tr.id=expired.task_run_id JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND expired.error_type='LeaseExpired') SELECT json_build_object('samples',count(*),'p50_ms',percentile_cont(0.50) WITHIN GROUP (ORDER BY milliseconds),'p95_ms',percentile_cont(0.95) WITHIN GROUP (ORDER BY milliseconds),'p99_ms',percentile_cont(0.99) WITHIN GROUP (ORDER BY milliseconds)) FROM gaps" \ + > "$result_dir/lease-recovery-distribution.json" + +python3 tests/benchmarks/local.py duplicate --prefix "$duplicate_prefix" > "$result_dir/duplicate-submission.json" +docker run --rm -v "$PWD":/src -w /src -v /var/run/docker.sock:/var/run/docker.sock \ + -v runmesh-go-mod:/go/pkg/mod -v runmesh-go-build:/root/.cache/go-build \ + -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal golang:1.25 \ + /usr/local/go/bin/go test -tags=integration -count=1 -run TestDuplicateDispatchIsLeasedOnce -json ./tests/integration \ + > "$result_dir/duplicate-delivery-go-test.json" + +docker compose images --format json > "$result_dir/image-digests.json" +docker compose ps --format json > "$result_dir/compose-topology.json" +docker compose exec -T redpanda rpk topic describe runmesh.tasks --format json > "$result_dir/kafka-topology-after.json" +{ + uname -a + sysctl -n machdep.cpu.brand_string 2>/dev/null || true + sysctl -n hw.memsize 2>/dev/null || true + docker version --format '{{json .}}' +} > "$result_dir/hardware.txt" +git rev-parse HEAD > "$result_dir/commit-sha.txt" +python3 tests/benchmarks/verify.py "$result_dir" > "$result_dir/verification.json" + +echo "Benchmark evidence written to $result_dir" diff --git a/tests/benchmarks/results/2026-07-21T002100Z/commit-sha.txt b/tests/benchmarks/results/2026-07-21T002100Z/commit-sha.txt new file mode 100644 index 0000000..d970a6b --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/commit-sha.txt @@ -0,0 +1 @@ +0c48987d5259bcdc62cadaecd163d996ad0af0a4 diff --git a/tests/benchmarks/results/2026-07-21T002100Z/compose-topology.json b/tests/benchmarks/results/2026-07-21T002100Z/compose-topology.json new file mode 100644 index 0000000..d7e2811 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/compose-topology.json @@ -0,0 +1,35 @@ +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:31 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"5b3969a3ceac","Image":"runmesh-control-plane","Labels":"com.docker.compose.config-hash=9fe03b71a2356bebdc17c98f565a44f48023721adb5ec2a83aba50c19bd4f54d,com.docker.compose.image=sha256:86ac766dc0e9bc786d4ded8511486c7887d0fad0e8e9fc952c4f68351fe78dfa,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=control-plane-1,com.docker.compose.service=control-plane,desktop.docker.io/ports/7001/tcp=:7001,com.docker.compose.container-number=1,com.docker.compose.depends_on=migrate:service_completed_successfully:false,redpanda:service_healthy:false,redis:service_healthy:false,minio:service_healthy:false,keycloak:service_started:false,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/8080/tcp=:8080","LocalVolumes":"0","Mounts":"","Name":"runmesh-control-plane-1","Names":"runmesh-control-plane-1","Networks":"runmesh_default","Ports":"0.0.0.0:7001-\u003e7001/tcp, [::]:7001-\u003e7001/tcp, 0.0.0.0:8080-\u003e8080/tcp, [::]:8080-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":7001,"PublishedPort":7001,"Protocol":"tcp"},{"URL":"::","TargetPort":7001,"PublishedPort":7001,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8080,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8080,"Protocol":"tcp"}],"RunningFor":"3 minutes ago","Service":"control-plane","Size":"0B","State":"running","Status":"Up 3 minutes (healthy)"} +{"Command":"\"/run.sh\"","CreatedAt":"2026-07-20 19:51:28 -0400 EDT","ExitCode":0,"Health":"","ID":"0e46de4c0532","Image":"grafana/grafana:11.4.0","Labels":"com.docker.compose.image=sha256:d8ea37798ccc41061a62ab080f2676dda6bf7815558499f901bdb0f533a456fb,com.docker.compose.service=grafana,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/grafana/dashboards,desktop.docker.io/binds/0/Target=/var/lib/grafana/dashboards,desktop.docker.io/binds/1/Target=/etc/grafana/provisioning,com.docker.compose.config-hash=ede6cf07da09ed0d5759a176342e1da8e2d7164eeadeb235d2ead6763b183927,com.docker.compose.container-number=1,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports/3000/tcp=:3001,maintainer=Grafana Labs \u003chello@grafana.com\u003e,com.docker.compose.depends_on=prometheus:service_started:false,loki:service_started:false,tempo:service_started:false,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/1/SourceKind=hostFile,desktop.docker.io/ports.scheme=v2,desktop.docker.io/binds/1/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/grafana/provisioning","LocalVolumes":"0","Mounts":"/host_mnt/User…,/host_mnt/User…","Name":"runmesh-grafana-1","Names":"runmesh-grafana-1","Networks":"runmesh_default","Ports":"0.0.0.0:3001-\u003e3000/tcp, [::]:3001-\u003e3000/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3000,"PublishedPort":3001,"Protocol":"tcp"},{"URL":"::","TargetPort":3000,"PublishedPort":3001,"Protocol":"tcp"}],"RunningFor":"38 minutes ago","Service":"grafana","Size":"0B","State":"running","Status":"Up 38 minutes"} +{"Command":"\"/opt/keycloak/bin/k…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"18044859ec4f","Image":"quay.io/keycloak/keycloak:26.1","Labels":"io.k8s.description=Keycloak Server Image,org.opencontainers.image.url=https://github.com/keycloak-rel/keycloak-rel,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,io.openshift.expose-services=,org.opencontainers.image.description=,org.opencontainers.image.source=https://github.com/keycloak-rel/keycloak-rel,vcs-type=git,com.redhat.build-host=,com.redhat.license_terms=,maintainer=https://www.keycloak.org/,org.opencontainers.image.licenses=Apache-2.0,url=https://www.keycloak.org/,com.docker.compose.oneoff=False,description=Keycloak Server Image,desktop.docker.io/ports.scheme=v2,name=keycloak,org.opencontainers.image.title=keycloak-rel,release=,summary=Keycloak Server Image,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/keycloak,desktop.docker.io/binds/0/Target=/opt/keycloak/data/import,org.opencontainers.image.documentation=https://www.keycloak.org/documentation,vcs-ref=,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,desktop.docker.io/binds/0/SourceKind=hostFile,io.buildah.version=1.39.0-dev,org.opencontainers.image.revision=e7109eeda3e2630bb65640a6a322f0cb6ff6ddd6,vendor=https://www.keycloak.org/,architecture=aarch64,com.docker.compose.depends_on=,com.docker.compose.service=keycloak,distribution-scope=public,io.k8s.display-name=Keycloak Server,org.opencontainers.image.created=2025-04-11T07:58:44.198Z,com.docker.compose.config-hash=cebace789f94dee1045ba9e8f2f123fa3a9807bdde96bcc4af83324d70c53a7e,com.docker.compose.image=sha256:be6a86215213145bfb4fb3e2b3ab982a806d00262655abdcf3ffa6a38d241c7c,io.openshift.tags=keycloak security identity,org.opencontainers.image.version=26.1.5,version=26.1.5,build-date=2025-04-08T13:14:37Z,com.docker.compose.container-number=1,com.redhat.component=,desktop.docker.io/ports/8080/tcp=:8180","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-keycloak-1","Names":"runmesh-keycloak-1","Networks":"runmesh_default","Ports":"0.0.0.0:8180-\u003e8080/tcp, [::]:8180-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8180,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8180,"Protocol":"tcp"}],"RunningFor":"38 minutes ago","Service":"keycloak","Size":"0B","State":"running","Status":"Up 38 minutes"} +{"Command":"\"/usr/bin/loki -conf…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"5e38dd422042","Image":"grafana/loki:3.3.2","Labels":"desktop.docker.io/ports/3100/tcp=:3100,com.docker.compose.config-hash=e3121c4e0132494435f1d1cb47ac059e2b1f2f206f8082c2382c39931a5c7960,com.docker.compose.depends_on=,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=loki,com.docker.compose.container-number=1,com.docker.compose.image=sha256:8af2de1abbdd7aa92b27c9bcc96f0f4140c9096b507c77921ffddf1c6ad6c48f,com.docker.compose.oneoff=False,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-loki-1","Names":"runmesh-loki-1","Networks":"runmesh_default","Ports":"0.0.0.0:3100-\u003e3100/tcp, [::]:3100-\u003e3100/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3100,"PublishedPort":3100,"Protocol":"tcp"},{"URL":"::","TargetPort":3100,"PublishedPort":3100,"Protocol":"tcp"}],"RunningFor":"38 minutes ago","Service":"loki","Size":"0B","State":"running","Status":"Up 38 minutes"} +{"Command":"\"/usr/bin/docker-ent…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"caa90c23d142","Image":"minio/minio:RELEASE.2025-02-07T23-21-09Z","Labels":"desktop.docker.io/ports/9000/tcp=:9000,name=MinIO,vendor=MinIO Inc \u003cdev@min.io\u003e,com.docker.compose.version=5.3.0,io.k8s.description=Very small image which doesn't install the package manager.,url=https://www.redhat.com,com.redhat.component=ubi9-micro-container,com.redhat.license_terms=https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,description=MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads.,desktop.docker.io/ports/9001/tcp=:9001,vcs-ref=41bec8a95f8231b8982164a882f1b491a864dcce,com.docker.compose.oneoff=False,desktop.docker.io/ports.scheme=v2,io.buildah.version=1.38.0-dev,io.k8s.display-name=Red Hat Universal Base Image 9 Micro,com.docker.compose.service=minio,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.project=runmesh,distribution-scope=public,summary=MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service.,release=RELEASE.2025-02-07T23-21-09Z,vcs-type=git,version=RELEASE.2025-02-07T23-21-09Z,build-date=2025-02-06T04:33:00Z,maintainer=MinIO Inc \u003cdev@min.io\u003e,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,io.openshift.expose-services=,architecture=aarch64,com.docker.compose.config-hash=c2ef697a7845d5cfeaef7a64bd249c55b2816a78d92a7e6efbaf934addfbfbc9,com.docker.compose.image=sha256:640c22768ed5dbc92eacc14502a1b06a1c708fa60431345c78dfc22917062e93","LocalVolumes":"1","Mounts":"runmesh_minio-…","Name":"runmesh-minio-1","Names":"runmesh-minio-1","Networks":"runmesh_default","Ports":"0.0.0.0:9000-9001-\u003e9000-9001/tcp, [::]:9000-9001-\u003e9000-9001/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9000,"PublishedPort":9000,"Protocol":"tcp"},{"URL":"::","TargetPort":9000,"PublishedPort":9000,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":9001,"PublishedPort":9001,"Protocol":"tcp"},{"URL":"::","TargetPort":9001,"PublishedPort":9001,"Protocol":"tcp"}],"RunningFor":"38 minutes ago","Service":"minio","Size":"0B","State":"running","Status":"Up 38 minutes (healthy)"} +{"Command":"\"/otelcol-contrib --…\"","CreatedAt":"2026-07-20 19:51:28 -0400 EDT","ExitCode":0,"Health":"","ID":"59bc0d9265b3","Image":"otel/opentelemetry-collector-contrib:0.119.0","Labels":"com.docker.compose.config-hash=30112846559126a16e248b6a9473d1a71f5ceee134a40eea9844fcbe6a58f386,com.docker.compose.image=sha256:36c35cc213c0f3b64d6e8a3e844dc90822f00725e0e518eaed5b08bcc2231e72,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=otel-collector,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/otel-collector.yaml,desktop.docker.io/binds/0/Target=/etc/otelcol/config.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/4317/tcp=:4317,desktop.docker.io/ports/8889/tcp=:8889,org.opencontainers.image.revision=c06573884854d06efc3ff0d91d76004d49c80a53,org.opencontainers.image.version=0.119.0,com.docker.compose.container-number=1,com.docker.compose.project=runmesh,org.opencontainers.image.licenses=Apache-2.0,org.opencontainers.image.name=opentelemetry-collector-releases,org.opencontainers.image.source=https://github.com/open-telemetry/opentelemetry-collector-releases,com.docker.compose.depends_on=tempo:service_started:false,loki:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/ports/4318/tcp=:4318,org.opencontainers.image.created=2025-02-04T18:02:02Z","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-otel-collector-1","Names":"runmesh-otel-collector-1","Networks":"runmesh_default","Ports":"0.0.0.0:4317-4318-\u003e4317-4318/tcp, [::]:4317-4318-\u003e4317-4318/tcp, 0.0.0.0:8889-\u003e8889/tcp, [::]:8889-\u003e8889/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":4317,"PublishedPort":4317,"Protocol":"tcp"},{"URL":"::","TargetPort":4317,"PublishedPort":4317,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":4318,"PublishedPort":4318,"Protocol":"tcp"},{"URL":"::","TargetPort":4318,"PublishedPort":4318,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":8889,"PublishedPort":8889,"Protocol":"tcp"},{"URL":"::","TargetPort":8889,"PublishedPort":8889,"Protocol":"tcp"}],"RunningFor":"38 minutes ago","Service":"otel-collector","Size":"0B","State":"running","Status":"Up 38 minutes"} +{"Command":"\"docker-entrypoint.s…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"c6e6fb97df3f","Image":"postgres:17-alpine","Labels":"com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports/5432/tcp=:5432,com.docker.compose.config-hash=d3a0e466ee146131f19622bff6646dfd87ba3247ac4c064ec21f1013fca34f25,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.service=postgres,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml","LocalVolumes":"1","Mounts":"runmesh_postgr…","Name":"runmesh-postgres-1","Names":"runmesh-postgres-1","Networks":"runmesh_default","Ports":"0.0.0.0:5432-\u003e5432/tcp, [::]:5432-\u003e5432/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":5432,"PublishedPort":5432,"Protocol":"tcp"},{"URL":"::","TargetPort":5432,"PublishedPort":5432,"Protocol":"tcp"}],"RunningFor":"38 minutes ago","Service":"postgres","Size":"0B","State":"running","Status":"Up 38 minutes (healthy)"} +{"Command":"\"/bin/prometheus --c…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"fbeb2b4ea323","Image":"prom/prometheus:v3.1.0","Labels":"com.docker.compose.container-number=1,com.docker.compose.oneoff=False,desktop.docker.io/binds/1/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/prometheus-rules.yaml,desktop.docker.io/binds/1/Target=/etc/prometheus/rules.yaml,desktop.docker.io/ports/9090/tcp=:9090,maintainer=The Prometheus Authors \u003cprometheus-developers@googlegroups.com\u003e,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/prometheus.yaml,desktop.docker.io/binds/1/SourceKind=hostFile,org.opencontainers.image.source=https://github.com/prometheus/prometheus,com.docker.compose.config-hash=cea444590d60d3b6b6325a4da1ec76a5309ba4a70dcc47d4bc24fdbb081da34d,com.docker.compose.image=sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218,com.docker.compose.service=prometheus,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/etc/prometheus/prometheus.yml","LocalVolumes":"1","Mounts":"/host_mnt/User…,/host_mnt/User…,488ec96d9441ad…","Name":"runmesh-prometheus-1","Names":"runmesh-prometheus-1","Networks":"runmesh_default","Ports":"0.0.0.0:9090-\u003e9090/tcp, [::]:9090-\u003e9090/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9090,"PublishedPort":9090,"Protocol":"tcp"},{"URL":"::","TargetPort":9090,"PublishedPort":9090,"Protocol":"tcp"}],"RunningFor":"38 minutes ago","Service":"prometheus","Size":"0B","State":"running","Status":"Up 38 minutes"} +{"Command":"\"docker-entrypoint.s…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"a1445d324e0f","Image":"redis:7.4-alpine","Labels":"com.docker.compose.config-hash=681b4575ba55d081dc10ad086d58179ce9a966b8c7ce8dc3a4259491bddf628e,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=redis,com.docker.compose.oneoff=False,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/6379/tcp=:6379","LocalVolumes":"1","Mounts":"runmesh_redis-…","Name":"runmesh-redis-1","Names":"runmesh-redis-1","Networks":"runmesh_default","Ports":"0.0.0.0:6379-\u003e6379/tcp, [::]:6379-\u003e6379/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":6379,"PublishedPort":6379,"Protocol":"tcp"},{"URL":"::","TargetPort":6379,"PublishedPort":6379,"Protocol":"tcp"}],"RunningFor":"38 minutes ago","Service":"redis","Size":"0B","State":"running","Status":"Up 38 minutes (healthy)"} +{"Command":"\"/entrypoint.sh redp…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"8afed046466f","Image":"redpandadata/redpanda:v24.3.15","Labels":"com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/19092/tcp=:19092,com.docker.compose.config-hash=f7c33b45bd5515ce890e71f032b2ebf336077806438d6d3da8e8f3532837bae2,com.docker.compose.image=sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports/9644/tcp=:9644,org.opencontainers.image.authors=Redpanda Data \u003chi@redpanda.com\u003e,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=redpanda","LocalVolumes":"1","Mounts":"runmesh_redpan…","Name":"runmesh-redpanda-1","Names":"runmesh-redpanda-1","Networks":"runmesh_default","Ports":"0.0.0.0:9644-\u003e9644/tcp, [::]:9644-\u003e9644/tcp, 0.0.0.0:19092-\u003e19092/tcp, [::]:19092-\u003e19092/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9644,"PublishedPort":9644,"Protocol":"tcp"},{"URL":"::","TargetPort":9644,"PublishedPort":9644,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":19092,"PublishedPort":19092,"Protocol":"tcp"},{"URL":"::","TargetPort":19092,"PublishedPort":19092,"Protocol":"tcp"}],"RunningFor":"38 minutes ago","Service":"redpanda","Size":"0B","State":"running","Status":"Up 38 minutes (healthy)"} +{"Command":"\"./console\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"7cf18e265933","Image":"redpandadata/console:v2.8.3","Labels":"com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=redpanda-console,com.docker.compose.depends_on=redpanda:service_healthy:false,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/8080/tcp=:8081,com.docker.compose.config-hash=cb7cc300da1519866e1ef7f83fe7dda15efa317d0575d81fe3db1560604baebb,com.docker.compose.container-number=1,com.docker.compose.image=sha256:fc8006f22072293e9fa6a3aa8a0581ac502bc8262767c32c01ee019eca56bb38,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh","LocalVolumes":"0","Mounts":"","Name":"runmesh-redpanda-console-1","Names":"runmesh-redpanda-console-1","Networks":"runmesh_default","Ports":"0.0.0.0:8081-\u003e8080/tcp, [::]:8081-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8081,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8081,"Protocol":"tcp"}],"RunningFor":"38 minutes ago","Service":"redpanda-console","Size":"0B","State":"running","Status":"Up 38 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:23:52 -0400 EDT","ExitCode":0,"Health":"","ID":"5aedb26305bc","Image":"runmesh-scheduler","Labels":"com.docker.compose.depends_on=migrate:service_completed_successfully:false,redpanda-init:service_completed_successfully:false,redis:service_healthy:false,com.docker.compose.image=sha256:9503ee66dfca19d00e8d09061fcdbac0390ab0b0a06739788d0e036dc9ef3b2e,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=scheduler-1,com.docker.compose.config-hash=c2ec170571edb0eb096521acddff62c757fe8eaa763425d2f41f085b450236ac,com.docker.compose.container-number=1,com.docker.compose.project=runmesh,com.docker.compose.service=scheduler,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-scheduler-1","Names":"runmesh-scheduler-1","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"6 minutes ago","Service":"scheduler","Size":"0B","State":"running","Status":"Up 4 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:23:52 -0400 EDT","ExitCode":0,"Health":"","ID":"05a78198e120","Image":"runmesh-scheduler","Labels":"com.docker.compose.replace=scheduler-2,com.docker.compose.config-hash=c2ec170571edb0eb096521acddff62c757fe8eaa763425d2f41f085b450236ac,com.docker.compose.container-number=2,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.service=scheduler,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=migrate:service_completed_successfully:false,redpanda-init:service_completed_successfully:false,redis:service_healthy:false,com.docker.compose.image=sha256:9503ee66dfca19d00e8d09061fcdbac0390ab0b0a06739788d0e036dc9ef3b2e,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh","LocalVolumes":"0","Mounts":"","Name":"runmesh-scheduler-2","Names":"runmesh-scheduler-2","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"6 minutes ago","Service":"scheduler","Size":"0B","State":"running","Status":"Up 4 minutes"} +{"Command":"\"/tempo -config.file…\"","CreatedAt":"2026-07-20 19:51:27 -0400 EDT","ExitCode":0,"Health":"","ID":"cf56816bae5e","Image":"grafana/tempo:2.6.1","Labels":"com.docker.compose.config-hash=4de71b343b413aeaff1ca18f18ac2c9df01f41d4b77c48c3526911fa552ecd98,com.docker.compose.depends_on=,com.docker.compose.project=runmesh,com.docker.compose.service=tempo,org.opencontainers.image.url=https://github.com/grafana/tempo,com.docker.compose.container-number=1,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/tempo.yaml,desktop.docker.io/ports/3200/tcp=:3200,org.opencontainers.image.created=2024-10-15T15:13:57Z,desktop.docker.io/ports.scheme=v2,com.docker.compose.image=sha256:ef4384fce6e8ad22b95b243d8fc165628cda655376fd50e7850536ad89d71d50,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,org.opencontainers.image.revision=24c5b553df91cead5e3afc63e79a5f4deb702b62,org.opencontainers.image.source=https://github.com/grafana/tempo.git,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/etc/tempo.yaml","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-tempo-1","Names":"runmesh-tempo-1","Networks":"runmesh_default","Ports":"0.0.0.0:3200-\u003e3200/tcp, [::]:3200-\u003e3200/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3200,"PublishedPort":3200,"Protocol":"tcp"},{"URL":"::","TargetPort":3200,"PublishedPort":3200,"Protocol":"tcp"}],"RunningFor":"38 minutes ago","Service":"tempo","Size":"0B","State":"running","Status":"Up 38 minutes"} +{"Command":"\"/docker-entrypoint.…\"","CreatedAt":"2026-07-20 20:23:53 -0400 EDT","ExitCode":0,"Health":"","ID":"dd580ee6241b","Image":"runmesh-web","Labels":"com.docker.compose.config-hash=e2afc81b870dc82be2a88d0dee013a23d9fa6ddf3304077545e218b9fc0b47da,com.docker.compose.container-number=1,com.docker.compose.depends_on=control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=web-1,com.docker.compose.service=web,com.docker.compose.image=sha256:24e927bf7658e2ee5913705fe475e0c5f7a800adc3c5bd30114ee79e29df2cce,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/3000/tcp=:3000,maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e","LocalVolumes":"0","Mounts":"","Name":"runmesh-web-1","Names":"runmesh-web-1","Networks":"runmesh_default","Ports":"0.0.0.0:3000-\u003e3000/tcp, [::]:3000-\u003e3000/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3000,"PublishedPort":3000,"Protocol":"tcp"},{"URL":"::","TargetPort":3000,"PublishedPort":3000,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"web","Size":"0B","State":"running","Status":"Up 6 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"9b79ed07cd5b","Image":"runmesh-worker-go","Labels":"com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=worker-go-1,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,com.docker.compose.container-number=1","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-1","Names":"runmesh-worker-go-1","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"db504f63d96b","Image":"runmesh-worker-go","Labels":"com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-10,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.project=runmesh,com.docker.compose.version=5.3.0,com.docker.compose.container-number=10,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-10","Names":"runmesh-worker-go-10","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"3398e5717d30","Image":"runmesh-worker-go","Labels":"desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=11,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=worker-go-11,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-11","Names":"runmesh-worker-go-11","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"07781fe8d133","Image":"runmesh-worker-go","Labels":"com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.project=runmesh,com.docker.compose.replace=worker-go-12,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=12","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-12","Names":"runmesh-worker-go-12","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"e8bd36cb659b","Image":"runmesh-worker-go","Labels":"com.docker.compose.container-number=13,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-13","Names":"runmesh-worker-go-13","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"3bff0767a376","Image":"runmesh-worker-go","Labels":"com.docker.compose.container-number=14,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-14","Names":"runmesh-worker-go-14","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"0ae44a400acb","Image":"runmesh-worker-go","Labels":"com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=15,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-15","Names":"runmesh-worker-go-15","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"9f91b8557331","Image":"runmesh-worker-go","Labels":"com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.service=worker-go,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=16","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-16","Names":"runmesh-worker-go-16","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"4eef5daa6fcd","Image":"runmesh-worker-go","Labels":"com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=17,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-17","Names":"runmesh-worker-go-17","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"413c48208175","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=18,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-18","Names":"runmesh-worker-go-18","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"fbf1cec0dfea","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=19,com.docker.compose.oneoff=False,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-19","Names":"runmesh-worker-go-19","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"dbd096168366","Image":"runmesh-worker-go","Labels":"com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-2,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=2,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-2","Names":"runmesh-worker-go-2","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"f15bc07dee54","Image":"runmesh-worker-go","Labels":"com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.container-number=20,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-20","Names":"runmesh-worker-go-20","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"4b7bc6e40986","Image":"runmesh-worker-go","Labels":"com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.container-number=3,com.docker.compose.project=runmesh,com.docker.compose.replace=worker-go-3,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-3","Names":"runmesh-worker-go-3","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"bf07a036990f","Image":"runmesh-worker-go","Labels":"com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-4,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=4,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-4","Names":"runmesh-worker-go-4","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"d5c31e4d9dc5","Image":"runmesh-worker-go","Labels":"com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-5,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=5,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-5","Names":"runmesh-worker-go-5","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"c1a79666a042","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=6,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=worker-go-6,com.docker.compose.service=worker-go","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-6","Names":"runmesh-worker-go-6","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"5c4f2928c1f1","Image":"runmesh-worker-go","Labels":"com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=7,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=worker-go-7,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-7","Names":"runmesh-worker-go-7","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"a3503fd96cff","Image":"runmesh-worker-go","Labels":"com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.container-number=8,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-8,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-8","Names":"runmesh-worker-go-8","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:26:32 -0400 EDT","ExitCode":0,"Health":"","ID":"d78147b1136f","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-9,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=9,com.docker.compose.image=sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-9","Names":"runmesh-worker-go-9","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/duplicate-delivery-go-test.json b/tests/benchmarks/results/2026-07-21T002100Z/duplicate-delivery-go-test.json new file mode 100644 index 0000000..7e909ad --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/duplicate-delivery-go-test.json @@ -0,0 +1,8 @@ +{"Time":"2026-07-21T00:30:05.411959421Z","Action":"start","Package":"github.com/samarth1412/RunMesh/tests/integration"} +{"Time":"2026-07-21T00:30:05.420565046Z","Action":"run","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce"} +{"Time":"2026-07-21T00:30:05.420673129Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Output":"=== RUN TestDuplicateDispatchIsLeasedOnce\n"} +{"Time":"2026-07-21T00:30:21.560297345Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Output":"--- PASS: TestDuplicateDispatchIsLeasedOnce (16.14s)\n"} +{"Time":"2026-07-21T00:30:21.560543554Z","Action":"pass","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Elapsed":16.14} +{"Time":"2026-07-21T00:30:21.560579554Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Output":"PASS\n"} +{"Time":"2026-07-21T00:30:21.561012595Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Output":"ok \tgithub.com/samarth1412/RunMesh/tests/integration\t16.148s\n"} +{"Time":"2026-07-21T00:30:21.561092554Z","Action":"pass","Package":"github.com/samarth1412/RunMesh/tests/integration","Elapsed":16.149} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/duplicate-submission.json b/tests/benchmarks/results/2026-07-21T002100Z/duplicate-submission.json new file mode 100644 index 0000000..57ceca0 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/duplicate-submission.json @@ -0,0 +1,10 @@ +{ + "first_run_id": "cbd76d17-636f-4432-b47c-4d8103e7614b", + "first_status": 201, + "original_input_preserved": true, + "passed": true, + "same_run": true, + "scenario": "duplicate_submission", + "second_run_id": "cbd76d17-636f-4432-b47c-4d8103e7614b", + "second_status": 200 +} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/end-to-end-completion-database.json b/tests/benchmarks/results/2026-07-21T002100Z/end-to-end-completion-database.json new file mode 100644 index 0000000..d03267f --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/end-to-end-completion-database.json @@ -0,0 +1 @@ +{"tasks" : 1000, "succeeded" : 1000, "other" : 0} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/end-to-end-completion.json b/tests/benchmarks/results/2026-07-21T002100Z/end-to-end-completion.json new file mode 100644 index 0000000..af135df --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/end-to-end-completion.json @@ -0,0 +1,6 @@ +{ + "scenario": "queued_end_to_end_completion", + "tasks": 1000, + "elapsed_seconds": 32.524, + "tasks_per_second": 30.747 +} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/hardware.txt b/tests/benchmarks/results/2026-07-21T002100Z/hardware.txt new file mode 100644 index 0000000..31d8876 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/hardware.txt @@ -0,0 +1,4 @@ +Darwin samarths-MacBook-Air.local 25.5.0 Darwin Kernel Version 25.5.0: Tue Jun 9 22:26:22 PDT 2026; root:xnu-12377.121.10~1/RELEASE_ARM64_T8132 arm64 +Apple M4 +17179869184 +{"Client":{"Version":"29.6.1","ApiVersion":"1.55","DefaultAPIVersion":"1.55","GitCommit":"8900f1d","GoVersion":"go1.26.4","Os":"darwin","Arch":"arm64","BuildTime":"Fri Jun 26 11:39:35 2026","Context":"desktop-linux"},"Server":{"Platform":{"Name":"Docker Desktop 4.82.0 (233772)"},"Version":"29.6.1","ApiVersion":"1.55","MinAPIVersion":"1.40","Os":"linux","Arch":"arm64","Components":[{"Name":"Engine","Version":"29.6.1","Details":{"ApiVersion":"1.55","Arch":"arm64","BuildTime":"Fri Jun 26 11:39:58 2026","Experimental":"false","GitCommit":"8ec5ab3","GoVersion":"go1.26.4","KernelVersion":"6.12.76-linuxkit","MinAPIVersion":"1.40","Module":"github.com/moby/moby/v2","ModuleVersion":"v2.0.0+unknown","Os":"linux"}},{"Name":"containerd","Version":"v2.2.5","Details":{"GitCommit":"e53c7c1516c3b2bff98eb76f1f4117477e6f4e66"}},{"Name":"runc","Version":"1.3.6","Details":{"GitCommit":"v1.3.6-0-g491b69ba"}},{"Name":"docker-init","Version":"0.19.0","Details":{"GitCommit":"de40ad0"}}],"GitCommit":"8ec5ab3","GoVersion":"go1.26.4","KernelVersion":"6.12.76-linuxkit","BuildTime":"2026-06-26T11:39:58.000000000+00:00"}} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/image-digests.json b/tests/benchmarks/results/2026-07-21T002100Z/image-digests.json new file mode 100644 index 0000000..4b1be90 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/image-digests.json @@ -0,0 +1,2 @@ +[{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-9","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-11","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-14","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:fc8006f22072293e9fa6a3aa8a0581ac502bc8262767c32c01ee019eca56bb38","ContainerName":"runmesh-redpanda-console-1","Repository":"redpandadata/console","Tag":"v2.8.3","Platform":"linux/arm64","Size":73250938,"Created":"2025-02-27T18:10:27.99190098Z","LastTagTime":"2026-07-20T00:48:14.485197626Z"},{"ID":"sha256:24e927bf7658e2ee5913705fe475e0c5f7a800adc3c5bd30114ee79e29df2cce","ContainerName":"runmesh-web-1","Repository":"runmesh-web","Tag":"latest","Platform":"linux/arm64","Size":28920413,"Created":"2026-07-21T00:21:58.384612918Z","LastTagTime":"2026-07-21T00:23:25.613222666Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-19","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-2","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193","ContainerName":"runmesh-postgres-1","Repository":"postgres","Tag":"17-alpine","Platform":"linux/arm64/v8","Size":114981107,"Created":"2026-07-07T17:46:49.401690597Z","LastTagTime":"2026-07-20T00:47:47.860492044Z"},{"ID":"sha256:ef4384fce6e8ad22b95b243d8fc165628cda655376fd50e7850536ad89d71d50","ContainerName":"runmesh-tempo-1","Repository":"grafana/tempo","Tag":"2.6.1","Platform":"linux/arm64/v8","Size":49979919,"Created":"2024-10-15T15:14:02.938965353Z","LastTagTime":"2026-07-20T00:47:57.254291174Z"},{"ID":"sha256:36c35cc213c0f3b64d6e8a3e844dc90822f00725e0e518eaed5b08bcc2231e72","ContainerName":"runmesh-otel-collector-1","Repository":"otel/opentelemetry-collector-contrib","Tag":"0.119.0","Platform":"linux/arm64","Size":72650722,"Created":"2025-02-04T18:02:50.606991596Z","LastTagTime":"2026-07-20T00:47:50.053368504Z"},{"ID":"sha256:640c22768ed5dbc92eacc14502a1b06a1c708fa60431345c78dfc22917062e93","ContainerName":"runmesh-minio-1","Repository":"minio/minio","Tag":"RELEASE.2025-02-07T23-21-09Z","Platform":"linux/arm64","Size":58677008,"Created":"2025-02-08T21:08:53.658721358Z","LastTagTime":"2026-07-20T00:47:58.255328882Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-20","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-6","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-10","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:8af2de1abbdd7aa92b27c9bcc96f0f4140c9096b507c77921ffddf1c6ad6c48f","ContainerName":"runmesh-loki-1","Repository":"grafana/loki","Tag":"3.3.2","Platform":"linux/arm64","Size":30732944,"Created":"2024-12-18T17:12:53.000478115Z","LastTagTime":"2026-07-20T00:48:06.06543197Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-3","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-18","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-8","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d","ContainerName":"runmesh-redpanda-1","Repository":"redpandadata/redpanda","Tag":"v24.3.15","Platform":"linux/arm64/v8","Size":183895553,"Created":"2025-06-18T02:52:15.126275953Z","LastTagTime":"2026-07-20T00:48:16.687951877Z"},{"ID":"sha256:9503ee66dfca19d00e8d09061fcdbac0390ab0b0a06739788d0e036dc9ef3b2e","ContainerName":"runmesh-scheduler-2","Repository":"runmesh-scheduler","Tag":"latest","Platform":"linux/arm64","Size":8457935,"Created":"2026-07-20T23:08:19.62513022Z","LastTagTime":"2026-07-21T00:23:51.893256429Z"},{"ID":"sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99","ContainerName":"runmesh-redis-1","Repository":"redis","Tag":"7.4-alpine","Platform":"linux/arm64/v8","Size":16790804,"Created":"2026-05-07T17:39:54.062865523Z","LastTagTime":"2026-07-20T00:45:12.219804333Z"},{"ID":"sha256:acded13a336f2b6f35fd9ce14a32834cf1e6b2a7887fe5dfc977b3e563eba39e","ContainerName":"runmesh-migrate-1","Repository":"migrate/migrate","Tag":"v4.18.2","Platform":"linux/arm64","Size":18640157,"Created":"2025-01-27T05:15:01.377819434Z","LastTagTime":"2026-07-20T00:45:00.445620092Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-17","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:86ac766dc0e9bc786d4ded8511486c7887d0fad0e8e9fc952c4f68351fe78dfa","ContainerName":"runmesh-control-plane-1","Repository":"runmesh-control-plane","Tag":"latest","Platform":"linux/arm64","Size":10173680,"Created":"2026-07-20T23:08:20.215292553Z","LastTagTime":"2026-07-21T00:26:31.680462377Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-15","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-16","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:be6a86215213145bfb4fb3e2b3ab982a806d00262655abdcf3ffa6a38d241c7c","ContainerName":"runmesh-keycloak-1","Repository":"quay.io/keycloak/keycloak","Tag":"26.1","Platform":"linux/arm64","Size":241302481,"Created":"2025-04-11T08:01:18.363925612Z","LastTagTime":"2026-07-20T00:48:11.796539791Z"},{"ID":"sha256:d8ea37798ccc41061a62ab080f2676dda6bf7815558499f901bdb0f533a456fb","ContainerName":"runmesh-grafana-1","Repository":"grafana/grafana","Tag":"11.4.0","Platform":"linux/arm64","Size":125326597,"Created":"2024-12-04T21:33:24.409833365Z","LastTagTime":"2026-07-20T00:48:13.975356709Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-1","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-4","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d","ContainerName":"runmesh-redpanda-init-1","Repository":"redpandadata/redpanda","Tag":"v24.3.15","Platform":"linux/arm64/v8","Size":183895553,"Created":"2025-06-18T02:52:15.126275953Z","LastTagTime":"2026-07-20T00:48:16.687951877Z"},{"ID":"sha256:cf542692f9276aa5577790889355ea049f949989a467aabe988e1c80771db9d2","ContainerName":"runmesh-worker-python-1","Repository":"runmesh-worker-python","Tag":"latest","Platform":"linux/arm64","Size":60024247,"Created":"2026-07-20T23:22:00.604549877Z","LastTagTime":"2026-07-21T00:23:25.20326868Z"},{"ID":"sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218","ContainerName":"runmesh-prometheus-1","Repository":"prom/prometheus","Tag":"v3.1.0","Platform":"linux/arm64/v8","Size":110853317,"Created":"2025-01-02T14:15:56.472078076Z","LastTagTime":"2026-07-20T00:48:10.946754847Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-13","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:9503ee66dfca19d00e8d09061fcdbac0390ab0b0a06739788d0e036dc9ef3b2e","ContainerName":"runmesh-scheduler-1","Repository":"runmesh-scheduler","Tag":"latest","Platform":"linux/arm64","Size":8457935,"Created":"2026-07-20T23:08:19.62513022Z","LastTagTime":"2026-07-21T00:23:51.893256429Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-5","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-12","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"},{"ID":"sha256:2b554067a84a0fb93d54a8af6de929cc1707e2090b9e8495786ecfca2b65b864","ContainerName":"runmesh-worker-go-7","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:26:31.681039252Z"}] + diff --git a/tests/benchmarks/results/2026-07-21T002100Z/in-flight-before-termination.json b/tests/benchmarks/results/2026-07-21T002100Z/in-flight-before-termination.json new file mode 100644 index 0000000..f4d981a --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/in-flight-before-termination.json @@ -0,0 +1 @@ +{"running_count" : 6, "task_ids" : ["0bc56a56-b0b0-4f2f-8f40-1479c6aa07c3", "2822e4a3-4e53-40be-8022-d3dd3e9e14ea", "51717040-5950-4b5c-9f52-d561947d790f", "aaf4814f-e0a8-477a-9be7-ffa76adc2369", "b8cd0586-5146-436a-b4eb-bb6ce43e2900", "b9a321d8-9511-42f3-8da1-baa0663a8804"]} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/kafka-topology-after.json b/tests/benchmarks/results/2026-07-21T002100Z/kafka-topology-after.json new file mode 100644 index 0000000..978cf60 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/kafka-topology-after.json @@ -0,0 +1 @@ +[{"summary":{"name":"runmesh.tasks","internal":false,"partitions":6,"replicas":1,"error":""},"configs":[{"key":"cleanup.policy","value":"delete","source":"DEFAULT_CONFIG"},{"key":"compression.type","value":"producer","source":"DEFAULT_CONFIG"},{"key":"delete.retention.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"flush.bytes","value":"262144","source":"DEFAULT_CONFIG"},{"key":"flush.ms","value":"100","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"max.message.bytes","value":"1048576","source":"DEFAULT_CONFIG"},{"key":"message.timestamp.type","value":"CreateTime","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.mode","value":"disabled","source":"DEFAULT_CONFIG"},{"key":"redpanda.leaders.preference","value":"none","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.read","value":"false","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.write","value":"false","source":"DEFAULT_CONFIG"},{"key":"retention.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.ms","value":"86400000","source":"DEFAULT_CONFIG"},{"key":"retention.ms","value":"604800000","source":"DEFAULT_CONFIG"},{"key":"segment.bytes","value":"134217728","source":"DEFAULT_CONFIG"},{"key":"segment.ms","value":"1209600000","source":"DEFAULT_CONFIG"},{"key":"write.caching","value":"true","source":"DEFAULT_CONFIG"}],"partitions":[{"partition":0,"leader":0,"epoch":4,"replicas":[0],"log_start_offset":0,"high_watermark":92603},{"partition":1,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":15689},{"partition":2,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":22937},{"partition":3,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":17902},{"partition":4,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":18146},{"partition":5,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":26947}]}] diff --git a/tests/benchmarks/results/2026-07-21T002100Z/kafka-topology-before.json b/tests/benchmarks/results/2026-07-21T002100Z/kafka-topology-before.json new file mode 100644 index 0000000..f2b705e --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/kafka-topology-before.json @@ -0,0 +1 @@ +[{"summary":{"name":"runmesh.tasks","internal":false,"partitions":6,"replicas":1,"error":""},"configs":[{"key":"cleanup.policy","value":"delete","source":"DEFAULT_CONFIG"},{"key":"compression.type","value":"producer","source":"DEFAULT_CONFIG"},{"key":"delete.retention.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"flush.bytes","value":"262144","source":"DEFAULT_CONFIG"},{"key":"flush.ms","value":"100","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"max.message.bytes","value":"1048576","source":"DEFAULT_CONFIG"},{"key":"message.timestamp.type","value":"CreateTime","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.mode","value":"disabled","source":"DEFAULT_CONFIG"},{"key":"redpanda.leaders.preference","value":"none","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.read","value":"false","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.write","value":"false","source":"DEFAULT_CONFIG"},{"key":"retention.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.ms","value":"86400000","source":"DEFAULT_CONFIG"},{"key":"retention.ms","value":"604800000","source":"DEFAULT_CONFIG"},{"key":"segment.bytes","value":"134217728","source":"DEFAULT_CONFIG"},{"key":"segment.ms","value":"1209600000","source":"DEFAULT_CONFIG"},{"key":"write.caching","value":"true","source":"DEFAULT_CONFIG"}],"partitions":[{"partition":0,"leader":0,"epoch":4,"replicas":[0],"log_start_offset":0,"high_watermark":84829},{"partition":1,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":7389},{"partition":2,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":16744},{"partition":3,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":12897},{"partition":4,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":11387},{"partition":5,"leader":0,"epoch":2,"replicas":[0],"log_start_offset":0,"high_watermark":20835}]}] diff --git a/tests/benchmarks/results/2026-07-21T002100Z/lease-recovery-distribution.json b/tests/benchmarks/results/2026-07-21T002100Z/lease-recovery-distribution.json new file mode 100644 index 0000000..d8374ed --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/lease-recovery-distribution.json @@ -0,0 +1 @@ +{"samples" : 6, "p50_ms" : 125144.80649999999, "p95_ms" : 167824.18425, "p99_ms" : 168519.15524999998} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/scheduler-dispatch.json b/tests/benchmarks/results/2026-07-21T002100Z/scheduler-dispatch.json new file mode 100644 index 0000000..4932cea --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/scheduler-dispatch.json @@ -0,0 +1,6 @@ +{ + "scenario": "scheduler_dispatch", + "tasks": 10000, + "elapsed_seconds": 24.243, + "dispatches_per_second": 412.482 +} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/verification.json b/tests/benchmarks/results/2026-07-21T002100Z/verification.json new file mode 100644 index 0000000..07b0ca4 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/verification.json @@ -0,0 +1,21 @@ +{ + "checks": { + "active_workflows": true, + "api_requests": true, + "api_zero_failures": true, + "duplicate_delivery": true, + "duplicate_submission": true, + "end_to_end_zero_task_loss": true, + "lease_recovered": true, + "multi_partition_delivery": true, + "multiple_tasks_in_flight": true, + "six_kafka_partitions": true, + "submission_requests": true, + "submission_zero_failures": true, + "ten_thousand_tasks": true, + "worker_pool_terminated": true, + "zero_task_loss": true + }, + "observed_partitions_with_new_records": 6, + "passed": true +} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/worker-crash-result.json b/tests/benchmarks/results/2026-07-21T002100Z/worker-crash-result.json new file mode 100644 index 0000000..21fa5cf --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/worker-crash-result.json @@ -0,0 +1 @@ +{"scenario" : "worker_termination_and_lease_recovery", "tasks" : 10000, "succeeded" : 10000, "other" : 0, "attempts" : 10006, "recovered_tasks" : 6, "duplicate_attempts" : 6, "recovery_seconds" : 202.867} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/worker-crash-setup.json b/tests/benchmarks/results/2026-07-21T002100Z/worker-crash-setup.json new file mode 100644 index 0000000..c7f6441 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/worker-crash-setup.json @@ -0,0 +1,111 @@ +{ + "idempotency_prefix": "validation-crash-20260721T002324Z", + "preparation_seconds": 0.665, + "run_count": 100, + "run_ids": [ + "928dcfb7-4c94-4468-aa41-43ea9c8fc545", + "a7623c30-9ea2-47da-a9fc-cf3af63269b8", + "599f1d58-cdcc-4fb4-bc09-96ef9c14e180", + "cc1dff30-e50e-4eef-9542-75697be61950", + "6e4b7906-d3e8-4529-bdec-95de2c622e75", + "d92f9b2d-c38c-409a-9b30-03353d5be6ab", + "3afb182a-e6a7-4b52-af4f-22ad9493023c", + "05bfb791-f28e-4b4f-912d-6620f5bc0a75", + "7fd5acd1-5c74-4dcf-a144-cb78354e86d3", + "3b4f3e53-2999-4827-8f24-c05f3fdf70bf", + "412554cb-791b-4167-a962-d3ef1ed65171", + "f80a9771-f1f7-413a-b650-10d6b7f7c94c", + "4f4b810b-0490-4921-af3d-deae31346895", + "9e9c6b5b-665d-4943-833e-fc73e8a23190", + "87aff77d-dd2f-4114-9dfb-fb63c42d5459", + "75080e40-87a8-42a9-bfba-e5f6ae7d6386", + "701c1155-0892-4f31-8517-4f2795bef047", + "643a1df3-d68c-47e3-9dae-c6b866af6d30", + "03ce6504-75eb-4688-b24d-8b61c937f2f3", + "65287565-beec-4adb-a9f4-4f470951c9ad", + "10481af9-64ce-4919-9170-7468e28948fb", + "0b5ca6aa-7b6f-4762-baaf-e7bc0574c914", + "36ef13d6-6b47-4eae-9d99-605106cc9aab", + "daa17ad5-69a8-43a5-ada3-91ad62a2dbfd", + "36298566-2ed4-4690-a1f7-68907de6caed", + "755783b1-1f34-47d5-91e2-4bbc4ed1036d", + "7309b43f-b95e-44af-94c6-62dc4a644744", + "730c29bb-ef01-4c09-a6c6-c05931f5b59a", + "c3858cae-91b1-4635-8c40-457fb70b684d", + "61e40ca9-54de-4753-826d-6c6496776135", + "1db72468-3a9f-4c45-a6e4-ea3cc4ee367d", + "99f6d9cd-37cc-4c2f-af2c-70bb40de267e", + "f9a9899b-0d20-42cf-8600-c9869795d44e", + "578f92c7-1ade-4d0b-89ff-d210c4cca5fb", + "8ce2957d-54a7-44df-8a3b-c08f98b4d9fd", + "72c9def6-3bb8-46ab-98fd-b87d2ee8b95e", + "c81672c9-b9d9-4552-8589-0ea8867d0c2e", + "ebec465f-99bd-4378-8352-9313376a0db0", + "042c6f58-80e1-4055-8c9e-a5a0ba58e01a", + "a43af678-7bba-447e-8f2c-3cd4c418ffb8", + "b8f51c09-40cf-4f4c-b723-2bd99131b7d9", + "adfb6445-b1b9-45c9-bc16-74e267c0e9ca", + "25a59e0c-cd0a-4e55-b218-3da4e1605678", + "fbc9cf0a-97cb-4bb9-93f0-d8100c7190a4", + "252518ec-1f29-4aa5-a9f4-08dee94b191d", + "61fdfe26-2556-4a4c-a499-3977fc78604c", + "9a976cf1-f8e3-4c01-a2a4-a91a16559881", + "61ce12cd-86b5-4836-83c5-38726b809bc4", + "71b3e23b-d601-497a-8e0e-ccae26ffe402", + "aae0f6d1-c16e-4685-a189-56ea95670844", + "9891187c-40c6-4552-b941-9973eb251cb4", + "0f0fd8f8-1bfe-4864-a33c-16c6433a4e0b", + "e5e2c551-63e0-4dd7-a6b4-1d90febe5536", + "1e48ae9e-723b-44a2-8f81-a5e57496a967", + "47abcd5b-372c-4212-8041-8516376723a0", + "9607a10a-c237-4547-be79-bb118f42ee81", + "a9473824-3834-4f56-aa24-1e57447f2139", + "f37e0750-677f-4afe-b12c-cc4a83188f16", + "6b88a173-7ed5-49d4-96ae-889535de60e7", + "46aebc13-d76f-4566-8e54-27f421db693d", + "a8d43f9f-272d-4dc8-866c-47d4d690c951", + "6ce09e2a-4403-4b72-874f-79f9c08c10ac", + "afb6d27c-ff6b-413d-ad75-98f27b2d9bd9", + "a1d9d1b0-5877-4c1a-9ad7-4cf7a69cca9e", + "8467a538-0488-4f49-8970-bb94f202043e", + "66a53963-853c-45d3-9b51-ab29cb18993a", + "2ef068d3-4404-4fa3-8517-ed9c19fff0e6", + "dd33f84b-cf8b-4be3-a413-2d49916c33c5", + "9731103a-ef6a-4b25-8c67-a66f2545509b", + "b5c0253d-6a8a-4e97-93ee-170b114c3226", + "89b1e573-4d14-421f-b8fc-b742720a1afc", + "fcbc3ae7-263c-422f-ab81-4d5834343779", + "8d26d343-2bf9-4614-a498-f022ac9aac67", + "82028503-5a49-428a-b9a0-d5853865a6ab", + "072a8e75-3747-4723-9557-3fa007427605", + "d1ef01de-d3cc-4174-93c0-3700c5930c6c", + "b18dd0a0-a008-406e-9984-5413522471c3", + "472bc683-663b-4f23-b3d8-3984df41ddb6", + "049fd81b-9ed2-4853-a8e9-9e80d61c98bc", + "6453d425-6398-4c30-a14c-cccb85d923c5", + "be39a76d-f169-4c0f-81db-659120264c6f", + "a3ead5c9-74a4-4fcd-83c2-41506eebeba7", + "01aa6232-2e39-4964-b5c1-f9ea9fc3ef80", + "5e9a3c47-f94a-4a73-b587-4c6617824785", + "810cf418-1a10-492d-b772-cf4ec461c6e8", + "1d4ffe22-1e92-4734-a7d5-a1c7c2f17805", + "465c80cf-2e5e-42e4-b6d9-2002ee7a2f41", + "7ba4462e-a18c-4345-bfc9-e415158c7395", + "ad50f4f1-4799-4e18-875d-cf8160eb7c72", + "b3ab4705-b0d9-4a3c-ad30-99fa4838baeb", + "bb1c13b5-9b30-45fa-acff-badc0ade7110", + "f40c9758-105e-4853-88cd-ff43d9491aab", + "061f9acb-ab94-4987-851f-05003f5b02c2", + "878128a9-bb60-401d-9f3f-efa46a38fff4", + "d944ea86-f0c5-40a3-8c27-341042a3de7e", + "50082d41-6586-4463-9a20-86640753829c", + "e4dff4d6-ad53-4518-8541-9b904995891e", + "bec039a5-510e-4238-a4d3-311fc08aaed1", + "0ae91cb2-ad20-42e8-a4d5-4fc39a7f43fe", + "43c61724-8be4-48f5-bb6d-6ce5b402e4ba" + ], + "scenario": "worker_termination_and_lease_recovery", + "task_count": 10000, + "tasks_per_run": 100, + "workflow_id": "19e0a872-4a0c-40a0-a9c5-02f389e486ae" +} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/worker-termination.json b/tests/benchmarks/results/2026-07-21T002100Z/worker-termination.json new file mode 100644 index 0000000..ef1e7fd --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/worker-termination.json @@ -0,0 +1,6 @@ +{ + "terminated_workers": 20, + "idempotency_prefix": "validation-crash-20260721T002324Z", + "in_flight_tasks": 6, + "terminated_at": "2026-07-21T00:26:40.264112+00:00" +} diff --git a/tests/benchmarks/results/2026-07-21T002100Z/workflow-submissions.json b/tests/benchmarks/results/2026-07-21T002100Z/workflow-submissions.json new file mode 100644 index 0000000..e5efac8 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T002100Z/workflow-submissions.json @@ -0,0 +1,142 @@ +{ + "metrics": { + "http_req_failed": { + "passes": 0, + "fails": 1501, + "thresholds": { + "rate==0": false + }, + "value": 0 + }, + "http_req_duration{expected_response:true}": { + "med": 1.56, + "p(90)": 2.489417, + "p(95)": 3.235792, + "p(99)": 13.552917, + "max": 83.349625, + "avg": 2.18551663157895, + "min": 1.005667 + }, + "iterations": { + "count": 1501, + "rate": 50.02632332640791 + }, + "http_req_connecting": { + "p(99)": 0.708417, + "max": 28.160459, + "avg": 0.0671759673550966, + "min": 0, + "med": 0, + "p(90)": 0, + "p(95)": 0.557209 + }, + "checks": { + "passes": 1501, + "fails": 0, + "thresholds": { + "rate==1": false + }, + "value": 1 + }, + "http_req_receiving": { + "min": 0.014875, + "med": 0.033833, + "p(90)": 0.053417, + "p(95)": 0.068583, + "p(99)": 0.112, + "max": 3.321167, + "avg": 0.040390157228514335 + }, + "vus": { + "max": 0, + "value": 0, + "min": 0 + }, + "vus_max": { + "value": 100, + "min": 100, + "max": 100 + }, + "data_sent": { + "count": 2330333, + "rate": 77666.8834884731 + }, + "http_reqs": { + "count": 1501, + "rate": 50.02632332640791 + }, + "http_req_duration": { + "med": 1.56, + "p(90)": 2.489417, + "p(95)": 3.235792, + "p(99)": 13.552917, + "max": 83.349625, + "avg": 2.18551663157895, + "min": 1.005667 + }, + "http_req_blocked": { + "p(90)": 0.011083, + "p(95)": 0.593, + "p(99)": 0.756084, + "max": 28.227125, + "avg": 0.07432470886075929, + "min": 0.0015, + "med": 0.003875 + }, + "iteration_duration": { + "avg": 2.408748073284478, + "min": 1.10475, + "med": 1.7205, + "p(90)": 2.777666, + "p(95)": 3.506042, + "p(99)": 15.65925, + "max": 83.643166 + }, + "data_received": { + "count": 812460, + "rate": 27078.205629429296 + }, + "http_req_tls_handshaking": { + "p(95)": 0, + "p(99)": 0, + "max": 0, + "avg": 0, + "min": 0, + "med": 0, + "p(90)": 0 + }, + "http_req_sending": { + "med": 0.019209, + "p(90)": 0.033334, + "p(95)": 0.043125, + "p(99)": 0.082041, + "max": 0.430667, + "avg": 0.023136125249833422, + "min": 0.008 + }, + "http_req_waiting": { + "max": 83.187833, + "avg": 2.1219903491005976, + "min": 0.974917, + "med": 1.5, + "p(90)": 2.397375, + "p(95)": 3.1365, + "p(99)": 13.396834 + } + }, + "root_group": { + "name": "", + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": {}, + "checks": { + "created": { + "fails": 0, + "name": "created", + "path": "::created", + "id": "e26a6144a84abb127f65c1e5808b420c", + "passes": 1501 + } + } + } +} \ No newline at end of file diff --git a/tests/benchmarks/results/2026-07-21T003600Z/README.md b/tests/benchmarks/results/2026-07-21T003600Z/README.md new file mode 100644 index 0000000..f6e9253 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/README.md @@ -0,0 +1,62 @@ +# Local benchmark evidence — 2026-07-21T003600Z (canonical) + +Source commit: `c4b248638f2ae36c4a7bbaf48df37ee59ac1bf74` + +This is the canonical benchmark run for the current `HEAD`. It supersedes the +[2026-07-20T235240Z](../2026-07-20T235240Z/README.md) and +[2026-07-21T000619Z](../2026-07-21T000619Z/README.md) runs, which predate a +fresh-stack Kafka/Redpanda topic-initialization fix in `compose.yaml` +(`rpk topic describe` could report success before the topic actually existed +on a brand-new stack). It also supersedes +[2026-07-21T002100Z](../2026-07-21T002100Z/README.md), whose source commit +(`0c48987`) came before that fix. The fix itself only changes Compose +initialization ordering on a first-ever `docker compose up`; it does not +change scheduler, worker, or outbox runtime behavior, but this run was +recorded fresh against corrected `HEAD` rather than reused. + +This run was produced on an Apple M4 MacBook Air with 16 GiB RAM using +Docker Desktop 4.82.0. Performance values are report-only local evidence; +correctness, tenant isolation, and zero-loss checks are blocking. + +| Scenario | Recorded result | +| --- | --- | +| Authenticated reads at 100 requests/s | 3,000 requests, 0 failures; 1.701/3.307/4.657 ms p50/p95/p99 | +| Workflow submissions at 50 requests/s | 1,501 requests, 0 failures; 1.587/7.012/220.024 ms p50/p95/p99 | +| Simultaneously active workflows | 1,000 created in 18.224 seconds, 0 failures; 5.340/7.700/8.798 ms creation p50/p95/p99 | +| Queued end-to-end completion | 1,000 tasks in 17.359 seconds; 57.608 tasks/s | +| Scheduler dispatch | 10,000 tasks in 24.546 seconds; 407.403 dispatches/s | +| Worker termination and recovery | 20 workers terminated; 10,000/10,000 succeeded; 7 recovered tasks/duplicate attempts; 0 permanent loss; 185.971-second full drain | +| Recovered-attempt delay | 7 samples; 132.797/140.148/142.414 seconds p50/p95/p99 from expired attempt end to next attempt start | +| Duplicate submission | Same run returned with `201` then `200`; original input preserved | +| Duplicate Kafka delivery | `TestDuplicateDispatchIsLeasedOnce` passed | + +The topology used two scheduler replicas, six Redpanda partitions, 12 workers +for the 1,000-task completion phase, and 20 Go workers for termination/recovery. +The verifier observed new records on all six partitions and passed all 15 +blocking checks. Recovery latency includes queueing behind the 10,000-task +backlog and is reported as a limitation, not a latency target. + +`worker-termination.json` records `in_flight_tasks: 6`, a shell-variable +snapshot captured immediately before the four-tasks-running threshold broke +the readiness loop. The subsequent database query in +`in-flight-before-termination.json` (`running_count: 7`) is more precise and +matches the 7 recovered/duplicate attempts in `worker-crash-result.json`. +Both values satisfy the verifier's `>= 4` threshold; this is a benign +recording-timing artifact in the driver script, not a correctness issue, and +is called out here rather than silently reconciled. + +Raw evidence in this directory is not manually edited: + +- `api-read.json` and `workflow-submissions.json` are k6 summaries. +- `active-workflows*.json`, `end-to-end-completion*.json`, and + `scheduler-dispatch.json` record workload creation and database outcomes. +- `in-flight-before-termination.json`, `worker-termination.json`, + `worker-crash-result.json`, and `lease-recovery-distribution.json` record the + worker-loss experiment. +- `duplicate-submission.json` and `duplicate-delivery-go-test.json` capture the + two idempotency checks. +- `kafka-topology-*.json`, `compose-topology.json`, `hardware.txt`, + `image-digests.json`, and `commit-sha.txt` identify the environment. +- `verification.json` is the machine-readable acceptance result, reproducible + with `python3 tests/benchmarks/verify.py tests/benchmarks/results/2026-07-21T003600Z`. +- `commands.sh` is the exact benchmark driver copied at run start. diff --git a/tests/benchmarks/results/2026-07-21T003600Z/active-workflows-database.json b/tests/benchmarks/results/2026-07-21T003600Z/active-workflows-database.json new file mode 100644 index 0000000..756a691 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/active-workflows-database.json @@ -0,0 +1 @@ +{"active_runs" : 1000, "active_tasks" : 1000} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/active-workflows.json b/tests/benchmarks/results/2026-07-21T003600Z/active-workflows.json new file mode 100644 index 0000000..ccd38f3 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/active-workflows.json @@ -0,0 +1,22 @@ +{ + "create_latency": { + "p50_ms": 5.34, + "p95_ms": 7.7, + "p99_ms": 8.798 + }, + "created": 1000, + "elapsed_seconds": 18.224, + "failure_count": 0, + "failure_rate": 0, + "idempotency_prefix": "validation-active-20260721T003824Z", + "requested": 1000, + "sample_run_ids": [ + "8cdf0332-7862-40d0-893c-05d1fb562193", + "fa82c5bf-3f4c-4b33-a586-1463070351c1", + "281d3cf7-fd60-4d4d-881c-782bb880f374", + "bfa9255d-7752-4226-ac55-eea0745000ea", + "99a22ecf-bb0e-4c21-a63d-d127987a52fc" + ], + "scenario": "simultaneously_active_workflows", + "workflow_id": "8551a8e9-af1b-4d91-9d3f-da7d246e1191" +} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/api-read.json b/tests/benchmarks/results/2026-07-21T003600Z/api-read.json new file mode 100644 index 0000000..1e4dc65 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/api-read.json @@ -0,0 +1,142 @@ +{ + "root_group": { + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": {}, + "checks": { + "authenticated request succeeded": { + "name": "authenticated request succeeded", + "path": "::authenticated request succeeded", + "id": "12f4c1307352dc7d8175954f46790cf7", + "passes": 3000, + "fails": 0 + } + }, + "name": "" + }, + "metrics": { + "checks": { + "passes": 3000, + "fails": 0, + "thresholds": { + "rate==1": false + }, + "value": 1 + }, + "http_req_blocked": { + "p(99)": 0.4646094899999988, + "max": 6.837791, + "avg": 0.019162996999999977, + "min": 0.000833, + "med": 0.0046455, + "p(90)": 0.013838100000000004, + "p(95)": 0.016210099999999995 + }, + "iterations": { + "count": 3000, + "rate": 99.99776463330309 + }, + "http_req_receiving": { + "p(95)": 0.11826664999999995, + "p(99)": 0.16475042, + "max": 0.733708, + "avg": 0.04419059566666669, + "min": 0.009084, + "med": 0.0286455, + "p(90)": 0.09839590000000002 + }, + "http_reqs": { + "count": 3000, + "rate": 99.99776463330309 + }, + "vus_max": { + "max": 40, + "value": 40, + "min": 40 + }, + "http_req_tls_handshaking": { + "p(90)": 0, + "p(95)": 0, + "p(99)": 0, + "max": 0, + "avg": 0, + "min": 0, + "med": 0 + }, + "iteration_duration": { + "p(99)": 5.204668669999997, + "max": 20.168334, + "avg": 2.086265494000004, + "min": 0.519167, + "med": 1.8857499999999998, + "p(90)": 3.2910875, + "p(95)": 3.62079615 + }, + "data_sent": { + "count": 4227000, + "rate": 140896.85036832406 + }, + "http_req_connecting": { + "med": 0, + "p(90)": 0, + "p(95)": 0, + "p(99)": 0.44677249999999963, + "max": 6.756583, + "avg": 0.011823277000000002, + "min": 0 + }, + "http_req_sending": { + "p(99)": 0.07908425, + "max": 1.852791, + "avg": 0.027027973666666687, + "min": 0.003375, + "med": 0.021042, + "p(90)": 0.0469211, + "p(95)": 0.05442739999999997 + }, + "http_req_waiting": { + "max": 16.269417, + "avg": 1.8376189710000015, + "min": 0.47525, + "med": 1.618625, + "p(90)": 2.8969628000000003, + "p(95)": 3.1830332999999995, + "p(99)": 4.32654976 + }, + "vus": { + "value": 0, + "min": 0, + "max": 1 + }, + "http_req_duration": { + "med": 1.7014585, + "p(90)": 3.0005375, + "p(95)": 3.3065610499999996, + "p(99)": 4.65736783, + "max": 16.399334, + "avg": 1.908837540333333, + "min": 0.491084 + }, + "data_received": { + "count": 1395000, + "rate": 46498.96055448594 + }, + "http_req_duration{expected_response:true}": { + "med": 1.7014585, + "p(90)": 3.0005375, + "p(95)": 3.3065610499999996, + "p(99)": 4.65736783, + "max": 16.399334, + "avg": 1.908837540333333, + "min": 0.491084 + }, + "http_req_failed": { + "passes": 0, + "fails": 3000, + "thresholds": { + "rate==0": false + }, + "value": 0 + } + } +} \ No newline at end of file diff --git a/tests/benchmarks/results/2026-07-21T003600Z/commands.sh b/tests/benchmarks/results/2026-07-21T003600Z/commands.sh new file mode 100755 index 0000000..a0917b0 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/commands.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Runs only against the local Compose project. It never provisions cloud +# resources. Raw result directories are immutable: choose a new path to rerun. +result_dir=${1:-tests/benchmarks/results/$(date -u +%Y-%m-%dT%H%M%SZ)} +if [[ -e "$result_dir" ]]; then + echo "result directory already exists: $result_dir" >&2 + exit 1 +fi +mkdir -p "$result_dir" +run_suffix=$(date -u +%Y%m%dT%H%M%SZ) +active_prefix="validation-active-$run_suffix" +crash_prefix="validation-crash-$run_suffix" +duplicate_prefix="validation-duplicate-$run_suffix" + +cp "$0" "$result_dir/commands.sh" +docker compose up -d --build +docker compose up -d --scale scheduler=2 --scale worker-go=1 --scale worker-python=1 scheduler worker-go worker-python +for endpoint in \ + http://localhost:8180/realms/runmesh/.well-known/openid-configuration \ + http://localhost:8080/health/ready; do + ready=0 + for _ in $(seq 1 90); do + if curl -fsS "$endpoint" >/dev/null; then ready=1; break; fi + sleep 2 + done + [[ "$ready" == 1 ]] || { echo "benchmark dependency did not become ready: $endpoint" >&2; exit 1; } +done +benchmark_token=$(python3 tests/benchmarks/local.py token) + +docker compose exec -T redpanda rpk topic describe runmesh.tasks --format json > "$result_dir/kafka-topology-before.json" +read_workflow=$(python3 tests/benchmarks/local.py definition --prefix "validation-read-$run_suffix") +docker run --rm --add-host host.docker.internal:host-gateway \ + -e RUNMESH_API=http://host.docker.internal:8080 \ + -e RUNMESH_READ_PATH="/v1/workflows/$read_workflow" \ + -e RUNMESH_TOKEN="$benchmark_token" -e RUNMESH_RATE=100 -e RUNMESH_DURATION=30s \ + -v "$PWD/tests/load":/scripts:ro -v "$PWD/$result_dir":/results \ + grafana/k6:0.57.0 run --summary-export=/results/api-read.json /scripts/api-read.js + +sleep 3 +submission_workflow=$(python3 tests/benchmarks/local.py definition --prefix "validation-submission-$run_suffix") +docker run --rm --add-host host.docker.internal:host-gateway \ + -e RUNMESH_API=http://host.docker.internal:8080 \ + -e RUNMESH_TOKEN="$benchmark_token" -e RUNMESH_WORKFLOW_ID="$submission_workflow" \ + -e RUNMESH_RATE=50 -e RUNMESH_DURATION=30s \ + -v "$PWD/tests/load":/scripts:ro -v "$PWD/$result_dir":/results \ + grafana/k6:0.57.0 run --summary-export=/results/workflow-submissions.json /scripts/submissions.js + +docker compose stop scheduler worker-python worker-go +python3 tests/benchmarks/local.py active --count 1000 --prefix "$active_prefix" > "$result_dir/active-workflows.json" +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('active_runs',count(*),'active_tasks',sum(task_count)) FROM (SELECT wr.id,count(tr.id) task_count FROM workflow_runs wr JOIN task_runs tr ON tr.workflow_run_id=wr.id WHERE wr.idempotency_key LIKE '$active_prefix-%' AND wr.status='RUNNING' GROUP BY wr.id) rows" \ + > "$result_dir/active-workflows-database.json" + +# Start two schedulers and twelve workers against six partitions, then measure +# drain throughput for the 1,000 distinct workflow keys created above. +completion_start=$(python3 -c 'import time; print(time.time_ns())') +docker compose up -d --scale scheduler=2 scheduler +docker compose up -d --build --scale worker-go=12 worker-go +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM workflow_runs WHERE idempotency_key LIKE '$active_prefix-%' AND status NOT IN ('SUCCEEDED','FAILED','CANCELLED')") != 0 ]]; do sleep 0.2; done +completion_end=$(python3 -c 'import time; print(time.time_ns())') +python3 - "$completion_start" "$completion_end" > "$result_dir/end-to-end-completion.json" <<'PY' +import json, sys +elapsed = (int(sys.argv[2]) - int(sys.argv[1])) / 1_000_000_000 +print(json.dumps({"scenario":"queued_end_to_end_completion","tasks":1000,"elapsed_seconds":round(elapsed,3),"tasks_per_second":round(1000/elapsed,3)}, indent=2)) +PY +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('tasks',count(*),'succeeded',count(*) FILTER (WHERE tr.status='SUCCEEDED'),'other',count(*) FILTER (WHERE tr.status!='SUCCEEDED')) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$active_prefix-%'" \ + > "$result_dir/end-to-end-completion-database.json" + +# Begin at the current topic end so this scenario observes only its own work. +docker compose stop worker-go +docker compose exec -T redpanda rpk group seek runmesh-workers --to end --topics runmesh.tasks >/dev/null +python3 tests/benchmarks/local.py prepare --tasks 10000 --prefix "$crash_prefix" > "$result_dir/worker-crash-setup.json" +dispatch_start=$(python3 -c 'import time; print(time.time_ns())') +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status IN ('READY','BLOCKED')") != 0 ]]; do sleep 0.1; done +dispatch_end=$(python3 -c 'import time; print(time.time_ns())') +python3 - "$dispatch_start" "$dispatch_end" > "$result_dir/scheduler-dispatch.json" <<'PY' +import json, sys +elapsed = (int(sys.argv[2]) - int(sys.argv[1])) / 1_000_000_000 +print(json.dumps({"scenario":"scheduler_dispatch","tasks":10000,"elapsed_seconds":round(elapsed,3),"dispatches_per_second":round(10000/elapsed,3)}, indent=2)) +PY + +# Keep enough tasks running to occupy every available Kafka partition before +# terminating the entire 20-container worker pool. +docker compose exec -T postgres psql -U runmesh -d runmesh -c \ + "WITH selected AS (SELECT tr.id FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' ORDER BY tr.id LIMIT 100) UPDATE task_runs SET input='{\"delay_ms\":5000}'::jsonb WHERE id IN (SELECT id FROM selected);" >/dev/null +docker compose up -d --build --scale worker-go=20 worker-go +deadline=$((SECONDS+60)) +while true; do + running=$(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status='RUNNING'") + [[ "$running" -ge 4 ]] && break + [[ "$SECONDS" -ge "$deadline" ]] && { echo "fewer than four tasks became concurrently RUNNING" >&2; exit 1; } + sleep 0.1 +done +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('running_count',count(*),'task_ids',json_agg(id ORDER BY id)) FROM (SELECT tr.id FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status='RUNNING') active" \ + > "$result_dir/in-flight-before-termination.json" +worker_ids=( $(docker compose ps -q worker-go) ) +docker kill "${worker_ids[@]}" >/dev/null +python3 - "${#worker_ids[@]}" "$crash_prefix" "$running" > "$result_dir/worker-termination.json" <<'PY' +import datetime, json, sys +print(json.dumps({"terminated_workers":int(sys.argv[1]),"idempotency_prefix":sys.argv[2],"in_flight_tasks":int(sys.argv[3]),"terminated_at":datetime.datetime.now(datetime.timezone.utc).isoformat()}, indent=2)) +PY +recovery_start=$(python3 -c 'import time; print(time.time_ns())') +docker compose up -d --scale worker-go=20 worker-go +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM workflow_runs WHERE idempotency_key LIKE '$crash_prefix-%' AND status NOT IN ('SUCCEEDED','FAILED','CANCELLED')") != 0 ]]; do sleep 1; done +recovery_end=$(python3 -c 'import time; print(time.time_ns())') +recovery_seconds=$(python3 -c "print(round(($recovery_end-$recovery_start)/1_000_000_000,3))") +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('scenario','worker_termination_and_lease_recovery','tasks',count(*),'succeeded',count(*) FILTER (WHERE tr.status='SUCCEEDED'),'other',count(*) FILTER (WHERE tr.status!='SUCCEEDED'),'attempts',sum(tr.attempt_count),'recovered_tasks',count(*) FILTER (WHERE tr.attempt_count > 1),'duplicate_attempts',sum(tr.attempt_count)-count(*),'recovery_seconds',$recovery_seconds) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%'" \ + > "$result_dir/worker-crash-result.json" +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "WITH gaps AS (SELECT EXTRACT(EPOCH FROM (next_attempt.started_at-expired.ended_at))*1000 milliseconds FROM task_attempts expired JOIN task_attempts next_attempt ON next_attempt.task_run_id=expired.task_run_id AND next_attempt.attempt_number=expired.attempt_number+1 JOIN task_runs tr ON tr.id=expired.task_run_id JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND expired.error_type='LeaseExpired') SELECT json_build_object('samples',count(*),'p50_ms',percentile_cont(0.50) WITHIN GROUP (ORDER BY milliseconds),'p95_ms',percentile_cont(0.95) WITHIN GROUP (ORDER BY milliseconds),'p99_ms',percentile_cont(0.99) WITHIN GROUP (ORDER BY milliseconds)) FROM gaps" \ + > "$result_dir/lease-recovery-distribution.json" + +python3 tests/benchmarks/local.py duplicate --prefix "$duplicate_prefix" > "$result_dir/duplicate-submission.json" +docker run --rm -v "$PWD":/src -w /src -v /var/run/docker.sock:/var/run/docker.sock \ + -v runmesh-go-mod:/go/pkg/mod -v runmesh-go-build:/root/.cache/go-build \ + -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal golang:1.25 \ + /usr/local/go/bin/go test -tags=integration -count=1 -run TestDuplicateDispatchIsLeasedOnce -json ./tests/integration \ + > "$result_dir/duplicate-delivery-go-test.json" + +docker compose images --format json > "$result_dir/image-digests.json" +docker compose ps --format json > "$result_dir/compose-topology.json" +docker compose exec -T redpanda rpk topic describe runmesh.tasks --format json > "$result_dir/kafka-topology-after.json" +{ + uname -a + sysctl -n machdep.cpu.brand_string 2>/dev/null || true + sysctl -n hw.memsize 2>/dev/null || true + docker version --format '{{json .}}' +} > "$result_dir/hardware.txt" +git rev-parse HEAD > "$result_dir/commit-sha.txt" +python3 tests/benchmarks/verify.py "$result_dir" > "$result_dir/verification.json" + +echo "Benchmark evidence written to $result_dir" diff --git a/tests/benchmarks/results/2026-07-21T003600Z/commit-sha.txt b/tests/benchmarks/results/2026-07-21T003600Z/commit-sha.txt new file mode 100644 index 0000000..4fb3771 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/commit-sha.txt @@ -0,0 +1 @@ +c4b248638f2ae36c4a7bbaf48df37ee59ac1bf74 diff --git a/tests/benchmarks/results/2026-07-21T003600Z/compose-topology.json b/tests/benchmarks/results/2026-07-21T003600Z/compose-topology.json new file mode 100644 index 0000000..ce9a62c --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/compose-topology.json @@ -0,0 +1,35 @@ +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"471f522abee3","Image":"runmesh-control-plane","Labels":"com.docker.compose.config-hash=9fe03b71a2356bebdc17c98f565a44f48023721adb5ec2a83aba50c19bd4f54d,com.docker.compose.container-number=1,com.docker.compose.depends_on=redis:service_healthy:false,minio:service_healthy:false,keycloak:service_started:false,migrate:service_completed_successfully:false,redpanda:service_healthy:false,com.docker.compose.image=sha256:40314e50c50a6d0540790c0c785a527aa8b1c3fddc9faabafe7687880039c07f,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,com.docker.compose.project=runmesh,com.docker.compose.replace=control-plane-1,com.docker.compose.service=control-plane,com.docker.compose.version=5.3.0,desktop.docker.io/ports/7001/tcp=:7001,desktop.docker.io/ports/8080/tcp=:8080","LocalVolumes":"0","Mounts":"","Name":"runmesh-control-plane-1","Names":"runmesh-control-plane-1","Networks":"runmesh_default","Ports":"0.0.0.0:7001-\u003e7001/tcp, [::]:7001-\u003e7001/tcp, 0.0.0.0:8080-\u003e8080/tcp, [::]:8080-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":7001,"PublishedPort":7001,"Protocol":"tcp"},{"URL":"::","TargetPort":7001,"PublishedPort":7001,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8080,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8080,"Protocol":"tcp"}],"RunningFor":"3 minutes ago","Service":"control-plane","Size":"0B","State":"running","Status":"Up 3 minutes (healthy)"} +{"Command":"\"/run.sh\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"","ID":"5644e06da49b","Image":"grafana/grafana:11.4.0","Labels":"desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/1/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/grafana/dashboards,com.docker.compose.container-number=1,com.docker.compose.image=sha256:d8ea37798ccc41061a62ab080f2676dda6bf7815558499f901bdb0f533a456fb,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,desktop.docker.io/binds/1/Target=/var/lib/grafana/dashboards,desktop.docker.io/binds/1/SourceKind=hostFile,desktop.docker.io/ports.scheme=v2,com.docker.compose.service=grafana,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/grafana/provisioning,desktop.docker.io/binds/0/Target=/etc/grafana/provisioning,com.docker.compose.config-hash=ede6cf07da09ed0d5759a176342e1da8e2d7164eeadeb235d2ead6763b183927,com.docker.compose.depends_on=loki:service_started:false,tempo:service_started:false,prometheus:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports/3000/tcp=:3001,maintainer=Grafana Labs \u003chello@grafana.com\u003e,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"/host_mnt/User…,/host_mnt/User…","Name":"runmesh-grafana-1","Names":"runmesh-grafana-1","Networks":"runmesh_default","Ports":"0.0.0.0:3001-\u003e3000/tcp, [::]:3001-\u003e3000/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3000,"PublishedPort":3001,"Protocol":"tcp"},{"URL":"::","TargetPort":3000,"PublishedPort":3001,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"grafana","Size":"0B","State":"running","Status":"Up 5 minutes"} +{"Command":"\"/opt/keycloak/bin/k…\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"","ID":"f78739f08608","Image":"quay.io/keycloak/keycloak:26.1","Labels":"build-date=2025-04-08T13:14:37Z,architecture=aarch64,desktop.docker.io/binds/0/SourceKind=hostFile,distribution-scope=public,org.opencontainers.image.licenses=Apache-2.0,org.opencontainers.image.revision=e7109eeda3e2630bb65640a6a322f0cb6ff6ddd6,com.docker.compose.config-hash=cebace789f94dee1045ba9e8f2f123fa3a9807bdde96bcc4af83324d70c53a7e,com.docker.compose.depends_on=,com.docker.compose.oneoff=False,com.redhat.license_terms=,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/keycloak,desktop.docker.io/ports/8080/tcp=:8180,io.buildah.version=1.39.0-dev,desktop.docker.io/ports.scheme=v2,io.k8s.description=Keycloak Server Image,name=keycloak,org.opencontainers.image.documentation=https://www.keycloak.org/documentation,org.opencontainers.image.source=https://github.com/keycloak-rel/keycloak-rel,com.docker.compose.container-number=1,com.docker.compose.project=runmesh,com.docker.compose.service=keycloak,io.openshift.expose-services=,org.opencontainers.image.description=,url=https://www.keycloak.org/,vendor=https://www.keycloak.org/,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,io.openshift.tags=keycloak security identity,maintainer=https://www.keycloak.org/,org.opencontainers.image.created=2025-04-11T07:58:44.198Z,org.opencontainers.image.url=https://github.com/keycloak-rel/keycloak-rel,org.opencontainers.image.version=26.1.5,release=,com.docker.compose.image=sha256:be6a86215213145bfb4fb3e2b3ab982a806d00262655abdcf3ffa6a38d241c7c,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.redhat.build-host=,com.redhat.component=,description=Keycloak Server Image,org.opencontainers.image.title=keycloak-rel,vcs-ref=,vcs-type=git,desktop.docker.io/binds/0/Target=/opt/keycloak/data/import,io.k8s.display-name=Keycloak Server,summary=Keycloak Server Image,version=26.1.5","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-keycloak-1","Names":"runmesh-keycloak-1","Networks":"runmesh_default","Ports":"0.0.0.0:8180-\u003e8080/tcp, [::]:8180-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8180,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8180,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"keycloak","Size":"0B","State":"running","Status":"Up 5 minutes"} +{"Command":"\"/usr/bin/loki -conf…\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"","ID":"36c9587c7209","Image":"grafana/loki:3.3.2","Labels":"com.docker.compose.project=runmesh,com.docker.compose.service=loki,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=,com.docker.compose.image=sha256:8af2de1abbdd7aa92b27c9bcc96f0f4140c9096b507c77921ffddf1c6ad6c48f,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports/3100/tcp=:3100,com.docker.compose.config-hash=e3121c4e0132494435f1d1cb47ac059e2b1f2f206f8082c2382c39931a5c7960,com.docker.compose.container-number=1,com.docker.compose.oneoff=False","LocalVolumes":"0","Mounts":"","Name":"runmesh-loki-1","Names":"runmesh-loki-1","Networks":"runmesh_default","Ports":"0.0.0.0:3100-\u003e3100/tcp, [::]:3100-\u003e3100/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3100,"PublishedPort":3100,"Protocol":"tcp"},{"URL":"::","TargetPort":3100,"PublishedPort":3100,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"loki","Size":"0B","State":"running","Status":"Up 5 minutes"} +{"Command":"\"/usr/bin/docker-ent…\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"b38c50e9ba66","Image":"minio/minio:RELEASE.2025-02-07T23-21-09Z","Labels":"com.redhat.component=ubi9-micro-container,url=https://www.redhat.com,version=RELEASE.2025-02-07T23-21-09Z,com.docker.compose.oneoff=False,com.docker.compose.version=5.3.0,release=RELEASE.2025-02-07T23-21-09Z,summary=MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service.,desktop.docker.io/ports/9000/tcp=:9000,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=minio,description=MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads.,vendor=MinIO Inc \u003cdev@min.io\u003e,com.docker.compose.config-hash=c2ef697a7845d5cfeaef7a64bd249c55b2816a78d92a7e6efbaf934addfbfbc9,com.redhat.license_terms=https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI,desktop.docker.io/ports.scheme=v2,io.buildah.version=1.38.0-dev,vcs-type=git,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,distribution-scope=public,io.k8s.description=Very small image which doesn't install the package manager.,io.openshift.expose-services=,vcs-ref=41bec8a95f8231b8982164a882f1b491a864dcce,build-date=2025-02-06T04:33:00Z,com.docker.compose.image=sha256:640c22768ed5dbc92eacc14502a1b06a1c708fa60431345c78dfc22917062e93,com.docker.compose.project=runmesh,maintainer=MinIO Inc \u003cdev@min.io\u003e,name=MinIO,architecture=aarch64,desktop.docker.io/ports/9001/tcp=:9001,io.k8s.display-name=Red Hat Universal Base Image 9 Micro","LocalVolumes":"1","Mounts":"runmesh_minio-…","Name":"runmesh-minio-1","Names":"runmesh-minio-1","Networks":"runmesh_default","Ports":"0.0.0.0:9000-9001-\u003e9000-9001/tcp, [::]:9000-9001-\u003e9000-9001/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9000,"PublishedPort":9000,"Protocol":"tcp"},{"URL":"::","TargetPort":9000,"PublishedPort":9000,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":9001,"PublishedPort":9001,"Protocol":"tcp"},{"URL":"::","TargetPort":9001,"PublishedPort":9001,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"minio","Size":"0B","State":"running","Status":"Up 5 minutes (healthy)"} +{"Command":"\"/otelcol-contrib --…\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"","ID":"fce3fc5cc2b8","Image":"otel/opentelemetry-collector-contrib:0.119.0","Labels":"desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/otel-collector.yaml,desktop.docker.io/binds/0/Target=/etc/otelcol/config.yaml,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/8889/tcp=:8889,org.opencontainers.image.created=2025-02-04T18:02:02Z,org.opencontainers.image.source=https://github.com/open-telemetry/opentelemetry-collector-releases,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/ports/4317/tcp=:4317,com.docker.compose.config-hash=30112846559126a16e248b6a9473d1a71f5ceee134a40eea9844fcbe6a58f386,com.docker.compose.container-number=1,com.docker.compose.depends_on=tempo:service_started:false,loki:service_started:false,com.docker.compose.image=sha256:36c35cc213c0f3b64d6e8a3e844dc90822f00725e0e518eaed5b08bcc2231e72,com.docker.compose.oneoff=False,desktop.docker.io/ports/4318/tcp=:4318,org.opencontainers.image.version=0.119.0,com.docker.compose.service=otel-collector,org.opencontainers.image.licenses=Apache-2.0,org.opencontainers.image.name=opentelemetry-collector-releases,org.opencontainers.image.revision=c06573884854d06efc3ff0d91d76004d49c80a53","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-otel-collector-1","Names":"runmesh-otel-collector-1","Networks":"runmesh_default","Ports":"0.0.0.0:4317-4318-\u003e4317-4318/tcp, [::]:4317-4318-\u003e4317-4318/tcp, 0.0.0.0:8889-\u003e8889/tcp, [::]:8889-\u003e8889/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":4317,"PublishedPort":4317,"Protocol":"tcp"},{"URL":"::","TargetPort":4317,"PublishedPort":4317,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":4318,"PublishedPort":4318,"Protocol":"tcp"},{"URL":"::","TargetPort":4318,"PublishedPort":4318,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":8889,"PublishedPort":8889,"Protocol":"tcp"},{"URL":"::","TargetPort":8889,"PublishedPort":8889,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"otel-collector","Size":"0B","State":"running","Status":"Up 5 minutes"} +{"Command":"\"docker-entrypoint.s…\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"ff6c846b4906","Image":"postgres:17-alpine","Labels":"com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/5432/tcp=:5432,com.docker.compose.config-hash=d3a0e466ee146131f19622bff6646dfd87ba3247ac4c064ec21f1013fca34f25,com.docker.compose.image=sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=postgres,com.docker.compose.version=5.3.0","LocalVolumes":"1","Mounts":"runmesh_postgr…","Name":"runmesh-postgres-1","Names":"runmesh-postgres-1","Networks":"runmesh_default","Ports":"0.0.0.0:5432-\u003e5432/tcp, [::]:5432-\u003e5432/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":5432,"PublishedPort":5432,"Protocol":"tcp"},{"URL":"::","TargetPort":5432,"PublishedPort":5432,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"postgres","Size":"0B","State":"running","Status":"Up 5 minutes (healthy)"} +{"Command":"\"/bin/prometheus --c…\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"","ID":"47ac0154a76f","Image":"prom/prometheus:v3.1.0","Labels":"desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/prometheus.yaml,desktop.docker.io/binds/1/Target=/etc/prometheus/rules.yaml,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/9090/tcp=:9090,com.docker.compose.config-hash=cea444590d60d3b6b6325a4da1ec76a5309ba4a70dcc47d4bc24fdbb081da34d,com.docker.compose.container-number=1,com.docker.compose.service=prometheus,desktop.docker.io/binds/1/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/prometheus-rules.yaml,desktop.docker.io/binds/1/SourceKind=hostFile,maintainer=The Prometheus Authors \u003cprometheus-developers@googlegroups.com\u003e,com.docker.compose.depends_on=,com.docker.compose.image=sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218,com.docker.compose.oneoff=False,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/etc/prometheus/prometheus.yml,org.opencontainers.image.source=https://github.com/prometheus/prometheus,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0","LocalVolumes":"1","Mounts":"/host_mnt/User…,/host_mnt/User…,9268734f6fe2c3…","Name":"runmesh-prometheus-1","Names":"runmesh-prometheus-1","Networks":"runmesh_default","Ports":"0.0.0.0:9090-\u003e9090/tcp, [::]:9090-\u003e9090/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9090,"PublishedPort":9090,"Protocol":"tcp"},{"URL":"::","TargetPort":9090,"PublishedPort":9090,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"prometheus","Size":"0B","State":"running","Status":"Up 5 minutes"} +{"Command":"\"docker-entrypoint.s…\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"eee92583b8f9","Image":"redis:7.4-alpine","Labels":"com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=redis,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=681b4575ba55d081dc10ad086d58179ce9a966b8c7ce8dc3a4259491bddf628e,com.docker.compose.depends_on=,com.docker.compose.oneoff=False,desktop.docker.io/ports/6379/tcp=:6379,com.docker.compose.container-number=1,com.docker.compose.image=sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml","LocalVolumes":"1","Mounts":"runmesh_redis-…","Name":"runmesh-redis-1","Names":"runmesh-redis-1","Networks":"runmesh_default","Ports":"0.0.0.0:6379-\u003e6379/tcp, [::]:6379-\u003e6379/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":6379,"PublishedPort":6379,"Protocol":"tcp"},{"URL":"::","TargetPort":6379,"PublishedPort":6379,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"redis","Size":"0B","State":"running","Status":"Up 5 minutes (healthy)"} +{"Command":"\"/entrypoint.sh redp…\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"healthy","ID":"0a57396b332e","Image":"redpandadata/redpanda:v24.3.15","Labels":"com.docker.compose.config-hash=f7c33b45bd5515ce890e71f032b2ebf336077806438d6d3da8e8f3532837bae2,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=redpanda,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/9644/tcp=:9644,org.opencontainers.image.authors=Redpanda Data \u003chi@redpanda.com\u003e,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports/19092/tcp=:19092","LocalVolumes":"1","Mounts":"runmesh_redpan…","Name":"runmesh-redpanda-1","Names":"runmesh-redpanda-1","Networks":"runmesh_default","Ports":"0.0.0.0:9644-\u003e9644/tcp, [::]:9644-\u003e9644/tcp, 0.0.0.0:19092-\u003e19092/tcp, [::]:19092-\u003e19092/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":9644,"PublishedPort":9644,"Protocol":"tcp"},{"URL":"::","TargetPort":9644,"PublishedPort":9644,"Protocol":"tcp"},{"URL":"0.0.0.0","TargetPort":19092,"PublishedPort":19092,"Protocol":"tcp"},{"URL":"::","TargetPort":19092,"PublishedPort":19092,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"redpanda","Size":"0B","State":"running","Status":"Up 5 minutes (healthy)"} +{"Command":"\"./console\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"","ID":"a27fca5e521d","Image":"redpandadata/console:v2.8.3","Labels":"com.docker.compose.config-hash=cb7cc300da1519866e1ef7f83fe7dda15efa317d0575d81fe3db1560604baebb,com.docker.compose.container-number=1,com.docker.compose.image=sha256:fc8006f22072293e9fa6a3aa8a0581ac502bc8262767c32c01ee019eca56bb38,com.docker.compose.oneoff=False,desktop.docker.io/ports/8080/tcp=:8081,com.docker.compose.depends_on=redpanda:service_healthy:false,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=redpanda-console,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-redpanda-console-1","Names":"runmesh-redpanda-console-1","Networks":"runmesh_default","Ports":"0.0.0.0:8081-\u003e8080/tcp, [::]:8081-\u003e8080/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":8080,"PublishedPort":8081,"Protocol":"tcp"},{"URL":"::","TargetPort":8080,"PublishedPort":8081,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"redpanda-console","Size":"0B","State":"running","Status":"Up 5 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"","ID":"7926e3970ace","Image":"runmesh-scheduler","Labels":"desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=c2ec170571edb0eb096521acddff62c757fe8eaa763425d2f41f085b450236ac,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,redis:service_healthy:false,migrate:service_completed_successfully:false,com.docker.compose.image=sha256:d419e46de7ac28bce1a158522dd8f19ecf80e82b46a91f115a8514c55f6e3e22,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.container-number=1,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=scheduler,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-scheduler-1","Names":"runmesh-scheduler-1","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"6 minutes ago","Service":"scheduler","Size":"0B","State":"running","Status":"Up 4 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"","ID":"c7182975e74c","Image":"runmesh-scheduler","Labels":"com.docker.compose.config-hash=c2ec170571edb0eb096521acddff62c757fe8eaa763425d2f41f085b450236ac,com.docker.compose.depends_on=migrate:service_completed_successfully:false,redpanda-init:service_completed_successfully:false,redis:service_healthy:false,com.docker.compose.image=sha256:d419e46de7ac28bce1a158522dd8f19ecf80e82b46a91f115a8514c55f6e3e22,com.docker.compose.oneoff=False,com.docker.compose.service=scheduler,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=2,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-scheduler-2","Names":"runmesh-scheduler-2","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"6 minutes ago","Service":"scheduler","Size":"0B","State":"running","Status":"Up 4 minutes"} +{"Command":"\"/tempo -config.file…\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"","ID":"5ded60cdd2bb","Image":"grafana/tempo:2.6.1","Labels":"com.docker.compose.image=sha256:ef4384fce6e8ad22b95b243d8fc165628cda655376fd50e7850536ad89d71d50,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/binds/0/Source=/Users/samarthvinayaka/Desktop/Projects/RunMesh/deploy/docker/tempo.yaml,desktop.docker.io/binds/0/Target=/etc/tempo.yaml,org.opencontainers.image.url=https://github.com/grafana/tempo,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=4de71b343b413aeaff1ca18f18ac2c9df01f41d4b77c48c3526911fa552ecd98,com.docker.compose.depends_on=,com.docker.compose.oneoff=False,com.docker.compose.version=5.3.0,desktop.docker.io/ports/3200/tcp=:3200,org.opencontainers.image.created=2024-10-15T15:13:57Z,org.opencontainers.image.source=https://github.com/grafana/tempo.git,com.docker.compose.container-number=1,com.docker.compose.project=runmesh,com.docker.compose.service=tempo,org.opencontainers.image.revision=24c5b553df91cead5e3afc63e79a5f4deb702b62","LocalVolumes":"0","Mounts":"/host_mnt/User…","Name":"runmesh-tempo-1","Names":"runmesh-tempo-1","Networks":"runmesh_default","Ports":"0.0.0.0:3200-\u003e3200/tcp, [::]:3200-\u003e3200/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3200,"PublishedPort":3200,"Protocol":"tcp"},{"URL":"::","TargetPort":3200,"PublishedPort":3200,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"tempo","Size":"0B","State":"running","Status":"Up 5 minutes"} +{"Command":"\"/docker-entrypoint.…\"","CreatedAt":"2026-07-20 20:38:52 -0400 EDT","ExitCode":0,"Health":"","ID":"ab9652e33f76","Image":"runmesh-web","Labels":"desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/3000/tcp=:3000,maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e,com.docker.compose.container-number=1,com.docker.compose.depends_on=control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.service=web,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=e2afc81b870dc82be2a88d0dee013a23d9fa6ddf3304077545e218b9fc0b47da,com.docker.compose.image=sha256:c8a8f2bb8339104532bfe93c97f97958e5936f3b977d4502dde6fbbd38a2e665,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh","LocalVolumes":"0","Mounts":"","Name":"runmesh-web-1","Names":"runmesh-web-1","Networks":"runmesh_default","Ports":"0.0.0.0:3000-\u003e3000/tcp, [::]:3000-\u003e3000/tcp","Project":"runmesh","Publishers":[{"URL":"0.0.0.0","TargetPort":3000,"PublishedPort":3000,"Protocol":"tcp"},{"URL":"::","TargetPort":3000,"PublishedPort":3000,"Protocol":"tcp"}],"RunningFor":"6 minutes ago","Service":"web","Size":"0B","State":"running","Status":"Up 5 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"d7ed1d7f0773","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=1,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.replace=worker-go-1,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-1","Names":"runmesh-worker-go-1","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"6dc6b62cbc11","Image":"runmesh-worker-go","Labels":"com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=worker-go-10,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.container-number=10,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-10","Names":"runmesh-worker-go-10","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"38811db67c7f","Image":"runmesh-worker-go","Labels":"com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=worker-go-11,com.docker.compose.service=worker-go,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=11,com.docker.compose.oneoff=False,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-11","Names":"runmesh-worker-go-11","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"7a06de19445b","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=12,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-12,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-12","Names":"runmesh-worker-go-12","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"c5b4d1ef6560","Image":"runmesh-worker-go","Labels":"com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=13,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.oneoff=False,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-13","Names":"runmesh-worker-go-13","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"e04d4127a4d9","Image":"runmesh-worker-go","Labels":"com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=14,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.oneoff=False","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-14","Names":"runmesh-worker-go-14","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"5b71f5d30d0d","Image":"runmesh-worker-go","Labels":"com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=15,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.project=runmesh","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-15","Names":"runmesh-worker-go-15","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"5589d8299557","Image":"runmesh-worker-go","Labels":"com.docker.compose.container-number=16,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-16","Names":"runmesh-worker-go-16","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"01fb4c049e70","Image":"runmesh-worker-go","Labels":"com.docker.compose.service=worker-go,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=17,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-17","Names":"runmesh-worker-go-17","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"89538253b3a5","Image":"runmesh-worker-go","Labels":"com.docker.compose.oneoff=False,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=18,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-18","Names":"runmesh-worker-go-18","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"9590d3244354","Image":"runmesh-worker-go","Labels":"com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=worker-go,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=19,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-19","Names":"runmesh-worker-go-19","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"f75c86ab2da2","Image":"runmesh-worker-go","Labels":"desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=2,com.docker.compose.depends_on=control-plane:service_started:false,redpanda-init:service_completed_successfully:false,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-2,com.docker.compose.oneoff=False,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-2","Names":"runmesh-worker-go-2","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"8e33169d95a0","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=20,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.version=5.3.0,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-20","Names":"runmesh-worker-go-20","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"e56e6328d0e9","Image":"runmesh-worker-go","Labels":"com.docker.compose.container-number=3,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-3,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.oneoff=False,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-3","Names":"runmesh-worker-go-3","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"317a2ba9ae4b","Image":"runmesh-worker-go","Labels":"com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-4,com.docker.compose.service=worker-go,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=4,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.oneoff=False,com.docker.compose.project=runmesh","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-4","Names":"runmesh-worker-go-4","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"2973fb107349","Image":"runmesh-worker-go","Labels":"com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.project=runmesh,com.docker.compose.service=worker-go,com.docker.compose.container-number=5,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-5,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-5","Names":"runmesh-worker-go-5","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"8d1dbb26ef5d","Image":"runmesh-worker-go","Labels":"com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-6,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.container-number=6,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.oneoff=False","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-6","Names":"runmesh-worker-go-6","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"b1e57ea6250c","Image":"runmesh-worker-go","Labels":"com.docker.compose.container-number=7,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-7,com.docker.compose.service=worker-go,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.project=runmesh,com.docker.compose.version=5.3.0,desktop.docker.io/ports.scheme=v2","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-7","Names":"runmesh-worker-go-7","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"747eeecf5cd4","Image":"runmesh-worker-go","Labels":"com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.project=runmesh,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,com.docker.compose.replace=worker-go-8,com.docker.compose.service=worker-go,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=8,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.oneoff=False,com.docker.compose.version=5.3.0","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-8","Names":"runmesh-worker-go-8","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} +{"Command":"\"/app/service\"","CreatedAt":"2026-07-20 20:41:18 -0400 EDT","ExitCode":0,"Health":"","ID":"cae8c34ffb83","Image":"runmesh-worker-go","Labels":"com.docker.compose.service=worker-go,com.docker.compose.version=5.3.0,com.docker.compose.config-hash=d99795a1c3c17fbbe4b8b9696e947c19c2ac64911044388b62ef1e8d10c5c354,com.docker.compose.image=sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380,com.docker.compose.project=runmesh,com.docker.compose.project.working_dir=/Users/samarthvinayaka/Desktop/Projects/RunMesh,desktop.docker.io/ports.scheme=v2,com.docker.compose.container-number=9,com.docker.compose.depends_on=redpanda-init:service_completed_successfully:false,control-plane:service_started:false,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/samarthvinayaka/Desktop/Projects/RunMesh/compose.yaml,com.docker.compose.replace=worker-go-9","LocalVolumes":"0","Mounts":"","Name":"runmesh-worker-go-9","Names":"runmesh-worker-go-9","Networks":"runmesh_default","Ports":"","Project":"runmesh","Publishers":[],"RunningFor":"3 minutes ago","Service":"worker-go","Size":"0B","State":"running","Status":"Up 3 minutes"} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/duplicate-delivery-go-test.json b/tests/benchmarks/results/2026-07-21T003600Z/duplicate-delivery-go-test.json new file mode 100644 index 0000000..c4fecfc --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/duplicate-delivery-go-test.json @@ -0,0 +1,8 @@ +{"Time":"2026-07-21T00:44:34.880923254Z","Action":"start","Package":"github.com/samarth1412/RunMesh/tests/integration"} +{"Time":"2026-07-21T00:44:34.892982962Z","Action":"run","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce"} +{"Time":"2026-07-21T00:44:34.893194504Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Output":"=== RUN TestDuplicateDispatchIsLeasedOnce\n"} +{"Time":"2026-07-21T00:44:51.335437928Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Output":"--- PASS: TestDuplicateDispatchIsLeasedOnce (16.44s)\n"} +{"Time":"2026-07-21T00:44:51.338009428Z","Action":"pass","Package":"github.com/samarth1412/RunMesh/tests/integration","Test":"TestDuplicateDispatchIsLeasedOnce","Elapsed":16.44} +{"Time":"2026-07-21T00:44:51.33810372Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Output":"PASS\n"} +{"Time":"2026-07-21T00:44:51.34270897Z","Action":"output","Package":"github.com/samarth1412/RunMesh/tests/integration","Output":"ok \tgithub.com/samarth1412/RunMesh/tests/integration\t16.461s\n"} +{"Time":"2026-07-21T00:44:51.342806303Z","Action":"pass","Package":"github.com/samarth1412/RunMesh/tests/integration","Elapsed":16.462} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/duplicate-submission.json b/tests/benchmarks/results/2026-07-21T003600Z/duplicate-submission.json new file mode 100644 index 0000000..405b017 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/duplicate-submission.json @@ -0,0 +1,10 @@ +{ + "first_run_id": "063ca9ad-33ea-4ef0-99df-f9e4e6ddaebd", + "first_status": 201, + "original_input_preserved": true, + "passed": true, + "same_run": true, + "scenario": "duplicate_submission", + "second_run_id": "063ca9ad-33ea-4ef0-99df-f9e4e6ddaebd", + "second_status": 200 +} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/end-to-end-completion-database.json b/tests/benchmarks/results/2026-07-21T003600Z/end-to-end-completion-database.json new file mode 100644 index 0000000..d03267f --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/end-to-end-completion-database.json @@ -0,0 +1 @@ +{"tasks" : 1000, "succeeded" : 1000, "other" : 0} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/end-to-end-completion.json b/tests/benchmarks/results/2026-07-21T003600Z/end-to-end-completion.json new file mode 100644 index 0000000..018bd66 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/end-to-end-completion.json @@ -0,0 +1,6 @@ +{ + "scenario": "queued_end_to_end_completion", + "tasks": 1000, + "elapsed_seconds": 17.359, + "tasks_per_second": 57.608 +} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/hardware.txt b/tests/benchmarks/results/2026-07-21T003600Z/hardware.txt new file mode 100644 index 0000000..31d8876 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/hardware.txt @@ -0,0 +1,4 @@ +Darwin samarths-MacBook-Air.local 25.5.0 Darwin Kernel Version 25.5.0: Tue Jun 9 22:26:22 PDT 2026; root:xnu-12377.121.10~1/RELEASE_ARM64_T8132 arm64 +Apple M4 +17179869184 +{"Client":{"Version":"29.6.1","ApiVersion":"1.55","DefaultAPIVersion":"1.55","GitCommit":"8900f1d","GoVersion":"go1.26.4","Os":"darwin","Arch":"arm64","BuildTime":"Fri Jun 26 11:39:35 2026","Context":"desktop-linux"},"Server":{"Platform":{"Name":"Docker Desktop 4.82.0 (233772)"},"Version":"29.6.1","ApiVersion":"1.55","MinAPIVersion":"1.40","Os":"linux","Arch":"arm64","Components":[{"Name":"Engine","Version":"29.6.1","Details":{"ApiVersion":"1.55","Arch":"arm64","BuildTime":"Fri Jun 26 11:39:58 2026","Experimental":"false","GitCommit":"8ec5ab3","GoVersion":"go1.26.4","KernelVersion":"6.12.76-linuxkit","MinAPIVersion":"1.40","Module":"github.com/moby/moby/v2","ModuleVersion":"v2.0.0+unknown","Os":"linux"}},{"Name":"containerd","Version":"v2.2.5","Details":{"GitCommit":"e53c7c1516c3b2bff98eb76f1f4117477e6f4e66"}},{"Name":"runc","Version":"1.3.6","Details":{"GitCommit":"v1.3.6-0-g491b69ba"}},{"Name":"docker-init","Version":"0.19.0","Details":{"GitCommit":"de40ad0"}}],"GitCommit":"8ec5ab3","GoVersion":"go1.26.4","KernelVersion":"6.12.76-linuxkit","BuildTime":"2026-06-26T11:39:58.000000000+00:00"}} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/image-digests.json b/tests/benchmarks/results/2026-07-21T003600Z/image-digests.json new file mode 100644 index 0000000..826904d --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/image-digests.json @@ -0,0 +1,2 @@ +[{"ID":"sha256:d8ea37798ccc41061a62ab080f2676dda6bf7815558499f901bdb0f533a456fb","ContainerName":"runmesh-grafana-1","Repository":"grafana/grafana","Tag":"11.4.0","Platform":"linux/arm64","Size":125326597,"Created":"2024-12-04T21:33:24.409833365Z","LastTagTime":"2026-07-20T00:48:13.975356709Z"},{"ID":"sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218","ContainerName":"runmesh-prometheus-1","Repository":"prom/prometheus","Tag":"v3.1.0","Platform":"linux/arm64/v8","Size":110853317,"Created":"2025-01-02T14:15:56.472078076Z","LastTagTime":"2026-07-20T00:48:10.946754847Z"},{"ID":"sha256:d419e46de7ac28bce1a158522dd8f19ecf80e82b46a91f115a8514c55f6e3e22","ContainerName":"runmesh-scheduler-1","Repository":"runmesh-scheduler","Tag":"latest","Platform":"linux/arm64","Size":8457935,"Created":"2026-07-20T23:08:19.62513022Z","LastTagTime":"2026-07-21T00:38:51.535675553Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-7","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-14","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193","ContainerName":"runmesh-postgres-1","Repository":"postgres","Tag":"17-alpine","Platform":"linux/arm64/v8","Size":114981107,"Created":"2026-07-07T17:46:49.401690597Z","LastTagTime":"2026-07-20T00:47:47.860492044Z"},{"ID":"sha256:acded13a336f2b6f35fd9ce14a32834cf1e6b2a7887fe5dfc977b3e563eba39e","ContainerName":"runmesh-migrate-1","Repository":"migrate/migrate","Tag":"v4.18.2","Platform":"linux/arm64","Size":18640157,"Created":"2025-01-27T05:15:01.377819434Z","LastTagTime":"2026-07-20T00:45:00.445620092Z"},{"ID":"sha256:fca2d6e170320dd31d48fc83a7b1cfd7507fb720b15015f80068dca8467a7dc2","ContainerName":"runmesh-worker-python-1","Repository":"runmesh-worker-python","Tag":"latest","Platform":"linux/arm64","Size":60024247,"Created":"2026-07-20T23:22:00.604549877Z","LastTagTime":"2026-07-21T00:38:25.255146639Z"},{"ID":"sha256:640c22768ed5dbc92eacc14502a1b06a1c708fa60431345c78dfc22917062e93","ContainerName":"runmesh-minio-1","Repository":"minio/minio","Tag":"RELEASE.2025-02-07T23-21-09Z","Platform":"linux/arm64","Size":58677008,"Created":"2025-02-08T21:08:53.658721358Z","LastTagTime":"2026-07-20T00:47:58.255328882Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-17","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-18","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-20","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:8af2de1abbdd7aa92b27c9bcc96f0f4140c9096b507c77921ffddf1c6ad6c48f","ContainerName":"runmesh-loki-1","Repository":"grafana/loki","Tag":"3.3.2","Platform":"linux/arm64","Size":30732944,"Created":"2024-12-18T17:12:53.000478115Z","LastTagTime":"2026-07-20T00:48:06.06543197Z"},{"ID":"sha256:ef4384fce6e8ad22b95b243d8fc165628cda655376fd50e7850536ad89d71d50","ContainerName":"runmesh-tempo-1","Repository":"grafana/tempo","Tag":"2.6.1","Platform":"linux/arm64/v8","Size":49979919,"Created":"2024-10-15T15:14:02.938965353Z","LastTagTime":"2026-07-20T00:47:57.254291174Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-5","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d","ContainerName":"runmesh-redpanda-init-1","Repository":"redpandadata/redpanda","Tag":"v24.3.15","Platform":"linux/arm64/v8","Size":183895553,"Created":"2025-06-18T02:52:15.126275953Z","LastTagTime":"2026-07-20T00:48:16.687951877Z"},{"ID":"sha256:fc8006f22072293e9fa6a3aa8a0581ac502bc8262767c32c01ee019eca56bb38","ContainerName":"runmesh-redpanda-console-1","Repository":"redpandadata/console","Tag":"v2.8.3","Platform":"linux/arm64","Size":73250938,"Created":"2025-02-27T18:10:27.99190098Z","LastTagTime":"2026-07-20T00:48:14.485197626Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-19","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-16","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-8","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-6","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:c8a8f2bb8339104532bfe93c97f97958e5936f3b977d4502dde6fbbd38a2e665","ContainerName":"runmesh-web-1","Repository":"runmesh-web","Tag":"latest","Platform":"linux/arm64","Size":28920413,"Created":"2026-07-21T00:21:58.384612918Z","LastTagTime":"2026-07-21T00:38:25.724895639Z"},{"ID":"sha256:40314e50c50a6d0540790c0c785a527aa8b1c3fddc9faabafe7687880039c07f","ContainerName":"runmesh-control-plane-1","Repository":"runmesh-control-plane","Tag":"latest","Platform":"linux/arm64","Size":10173680,"Created":"2026-07-20T23:08:20.215292553Z","LastTagTime":"2026-07-21T00:41:17.921079177Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-12","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99","ContainerName":"runmesh-redis-1","Repository":"redis","Tag":"7.4-alpine","Platform":"linux/arm64/v8","Size":16790804,"Created":"2026-05-07T17:39:54.062865523Z","LastTagTime":"2026-07-20T00:45:12.219804333Z"},{"ID":"sha256:d419e46de7ac28bce1a158522dd8f19ecf80e82b46a91f115a8514c55f6e3e22","ContainerName":"runmesh-scheduler-2","Repository":"runmesh-scheduler","Tag":"latest","Platform":"linux/arm64","Size":8457935,"Created":"2026-07-20T23:08:19.62513022Z","LastTagTime":"2026-07-21T00:38:51.535675553Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-13","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-3","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-4","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:f7d45bc15c220fdb811f34577958cd6f776a494f5449bd440fce9df70bcdc68d","ContainerName":"runmesh-redpanda-1","Repository":"redpandadata/redpanda","Tag":"v24.3.15","Platform":"linux/arm64/v8","Size":183895553,"Created":"2025-06-18T02:52:15.126275953Z","LastTagTime":"2026-07-20T00:48:16.687951877Z"},{"ID":"sha256:be6a86215213145bfb4fb3e2b3ab982a806d00262655abdcf3ffa6a38d241c7c","ContainerName":"runmesh-keycloak-1","Repository":"quay.io/keycloak/keycloak","Tag":"26.1","Platform":"linux/arm64","Size":241302481,"Created":"2025-04-11T08:01:18.363925612Z","LastTagTime":"2026-07-20T00:48:11.796539791Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-9","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-2","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-11","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-1","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-10","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:a3f64f288b3b718d254105ba468aa0e626f1c83616ad53eb5b43154584bb9380","ContainerName":"runmesh-worker-go-15","Repository":"runmesh-worker-go","Tag":"latest","Platform":"linux/arm64","Size":6607930,"Created":"2026-07-20T23:22:06.150297047Z","LastTagTime":"2026-07-21T00:41:17.92145726Z"},{"ID":"sha256:36c35cc213c0f3b64d6e8a3e844dc90822f00725e0e518eaed5b08bcc2231e72","ContainerName":"runmesh-otel-collector-1","Repository":"otel/opentelemetry-collector-contrib","Tag":"0.119.0","Platform":"linux/arm64","Size":72650722,"Created":"2025-02-04T18:02:50.606991596Z","LastTagTime":"2026-07-20T00:47:50.053368504Z"}] + diff --git a/tests/benchmarks/results/2026-07-21T003600Z/in-flight-before-termination.json b/tests/benchmarks/results/2026-07-21T003600Z/in-flight-before-termination.json new file mode 100644 index 0000000..2fdaa61 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/in-flight-before-termination.json @@ -0,0 +1 @@ +{"running_count" : 7, "task_ids" : ["00420be4-f7de-4d0d-9aa8-2b76ed09b7c5", "0248a1e0-b7de-4d1f-ba0e-5dfad27aaf99", "1dabee46-eda4-427d-a19d-919511c192c3", "c63a23ab-0ee1-43ae-a14d-bc46652e44ae", "ce4ebf1e-9882-4630-9e38-bf69a5c10ca8", "d9c581ea-e4fd-4831-8c8c-93133a1527bc", "daca5b33-e3b3-46a0-9000-431d587ddfd0"]} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/kafka-topology-after.json b/tests/benchmarks/results/2026-07-21T003600Z/kafka-topology-after.json new file mode 100644 index 0000000..91a0fff --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/kafka-topology-after.json @@ -0,0 +1 @@ +[{"summary":{"name":"runmesh.tasks","internal":false,"partitions":6,"replicas":1,"error":""},"configs":[{"key":"cleanup.policy","value":"delete","source":"DEFAULT_CONFIG"},{"key":"compression.type","value":"producer","source":"DEFAULT_CONFIG"},{"key":"delete.retention.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"flush.bytes","value":"262144","source":"DEFAULT_CONFIG"},{"key":"flush.ms","value":"100","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"max.message.bytes","value":"1048576","source":"DEFAULT_CONFIG"},{"key":"message.timestamp.type","value":"CreateTime","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.mode","value":"disabled","source":"DEFAULT_CONFIG"},{"key":"redpanda.leaders.preference","value":"none","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.read","value":"false","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.write","value":"false","source":"DEFAULT_CONFIG"},{"key":"retention.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.ms","value":"86400000","source":"DEFAULT_CONFIG"},{"key":"retention.ms","value":"604800000","source":"DEFAULT_CONFIG"},{"key":"segment.bytes","value":"134217728","source":"DEFAULT_CONFIG"},{"key":"segment.ms","value":"1209600000","source":"DEFAULT_CONFIG"},{"key":"write.caching","value":"true","source":"DEFAULT_CONFIG"}],"partitions":[{"partition":0,"leader":0,"epoch":5,"replicas":[0],"log_start_offset":0,"high_watermark":97613},{"partition":1,"leader":0,"epoch":3,"replicas":[0],"log_start_offset":0,"high_watermark":22058},{"partition":2,"leader":0,"epoch":3,"replicas":[0],"log_start_offset":0,"high_watermark":31522},{"partition":3,"leader":0,"epoch":3,"replicas":[0],"log_start_offset":0,"high_watermark":24777},{"partition":4,"leader":0,"epoch":3,"replicas":[0],"log_start_offset":0,"high_watermark":25034},{"partition":5,"leader":0,"epoch":3,"replicas":[0],"log_start_offset":0,"high_watermark":33366}]}] diff --git a/tests/benchmarks/results/2026-07-21T003600Z/kafka-topology-before.json b/tests/benchmarks/results/2026-07-21T003600Z/kafka-topology-before.json new file mode 100644 index 0000000..739e5f4 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/kafka-topology-before.json @@ -0,0 +1 @@ +[{"summary":{"name":"runmesh.tasks","internal":false,"partitions":6,"replicas":1,"error":""},"configs":[{"key":"cleanup.policy","value":"delete","source":"DEFAULT_CONFIG"},{"key":"compression.type","value":"producer","source":"DEFAULT_CONFIG"},{"key":"delete.retention.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"flush.bytes","value":"262144","source":"DEFAULT_CONFIG"},{"key":"flush.ms","value":"100","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"initial.retention.local.target.ms","value":"-1","source":"DEFAULT_CONFIG"},{"key":"max.message.bytes","value":"1048576","source":"DEFAULT_CONFIG"},{"key":"message.timestamp.type","value":"CreateTime","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.iceberg.mode","value":"disabled","source":"DEFAULT_CONFIG"},{"key":"redpanda.leaders.preference","value":"none","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.delete","value":"true","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.read","value":"false","source":"DEFAULT_CONFIG"},{"key":"redpanda.remote.write","value":"false","source":"DEFAULT_CONFIG"},{"key":"retention.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.bytes","value":"-1","source":"DEFAULT_CONFIG"},{"key":"retention.local.target.ms","value":"86400000","source":"DEFAULT_CONFIG"},{"key":"retention.ms","value":"604800000","source":"DEFAULT_CONFIG"},{"key":"segment.bytes","value":"134217728","source":"DEFAULT_CONFIG"},{"key":"segment.ms","value":"1209600000","source":"DEFAULT_CONFIG"},{"key":"write.caching","value":"true","source":"DEFAULT_CONFIG"}],"partitions":[{"partition":0,"leader":0,"epoch":5,"replicas":[0],"log_start_offset":0,"high_watermark":92603},{"partition":1,"leader":0,"epoch":3,"replicas":[0],"log_start_offset":0,"high_watermark":15689},{"partition":2,"leader":0,"epoch":3,"replicas":[0],"log_start_offset":0,"high_watermark":22937},{"partition":3,"leader":0,"epoch":3,"replicas":[0],"log_start_offset":0,"high_watermark":17902},{"partition":4,"leader":0,"epoch":3,"replicas":[0],"log_start_offset":0,"high_watermark":18146},{"partition":5,"leader":0,"epoch":3,"replicas":[0],"log_start_offset":0,"high_watermark":26947}]}] diff --git a/tests/benchmarks/results/2026-07-21T003600Z/lease-recovery-distribution.json b/tests/benchmarks/results/2026-07-21T003600Z/lease-recovery-distribution.json new file mode 100644 index 0000000..584ec05 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/lease-recovery-distribution.json @@ -0,0 +1 @@ +{"samples" : 7, "p50_ms" : 132797.236, "p95_ms" : 140147.6218, "p99_ms" : 142413.68356} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/scheduler-dispatch.json b/tests/benchmarks/results/2026-07-21T003600Z/scheduler-dispatch.json new file mode 100644 index 0000000..defe630 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/scheduler-dispatch.json @@ -0,0 +1,6 @@ +{ + "scenario": "scheduler_dispatch", + "tasks": 10000, + "elapsed_seconds": 24.546, + "dispatches_per_second": 407.403 +} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/verification.json b/tests/benchmarks/results/2026-07-21T003600Z/verification.json new file mode 100644 index 0000000..07b0ca4 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/verification.json @@ -0,0 +1,21 @@ +{ + "checks": { + "active_workflows": true, + "api_requests": true, + "api_zero_failures": true, + "duplicate_delivery": true, + "duplicate_submission": true, + "end_to_end_zero_task_loss": true, + "lease_recovered": true, + "multi_partition_delivery": true, + "multiple_tasks_in_flight": true, + "six_kafka_partitions": true, + "submission_requests": true, + "submission_zero_failures": true, + "ten_thousand_tasks": true, + "worker_pool_terminated": true, + "zero_task_loss": true + }, + "observed_partitions_with_new_records": 6, + "passed": true +} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/worker-crash-result.json b/tests/benchmarks/results/2026-07-21T003600Z/worker-crash-result.json new file mode 100644 index 0000000..f5fc410 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/worker-crash-result.json @@ -0,0 +1 @@ +{"scenario" : "worker_termination_and_lease_recovery", "tasks" : 10000, "succeeded" : 10000, "other" : 0, "attempts" : 10007, "recovered_tasks" : 7, "duplicate_attempts" : 7, "recovery_seconds" : 185.971} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/worker-crash-setup.json b/tests/benchmarks/results/2026-07-21T003600Z/worker-crash-setup.json new file mode 100644 index 0000000..cad16f1 --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/worker-crash-setup.json @@ -0,0 +1,111 @@ +{ + "idempotency_prefix": "validation-crash-20260721T003824Z", + "preparation_seconds": 0.693, + "run_count": 100, + "run_ids": [ + "75af6c01-793b-434a-98a3-2abe6e9ea1b7", + "0e33d18e-8b5c-4297-b815-668b738a5bb3", + "30c57e35-58ae-43ff-a690-eb84ea326f04", + "2f955aea-261e-46d3-8326-e23d8c084d87", + "daa69059-4236-4156-a585-bcc073d662f8", + "9ae952d5-dc37-41a8-bae3-c766584b53b9", + "0a2e0f86-5b65-48ed-89c6-705e8c071d5b", + "35fd798e-f62c-445c-a4d6-af37c0afbff3", + "b64727a0-ae1e-4d97-bbd9-3e11c9149e41", + "2af9d9fb-0264-4aeb-bbe1-03d7c3282534", + "1a456986-1cf4-4a66-b4fb-3b44b431dcc4", + "a525157f-8b00-4d23-85ae-142ce3a92225", + "d3a3946e-3eef-4f8e-9fb1-b2886fee8e30", + "e28e9901-7f76-4f22-bce8-f4d564b2d916", + "634c6db6-5521-4abc-8dc8-448f28f3a259", + "24f4ba87-bcf6-4093-90d1-3a30c1dd4ee7", + "01db0c12-6fe3-4d9f-a0f7-213d493de812", + "0e57ea50-a6ae-4c82-970a-b0edc8435d73", + "f591af42-a433-463c-ac38-3feb3316853f", + "72b4d4fa-4380-4c40-bacd-21902f294ce7", + "569fc9d8-1fef-4802-aa70-fe1cd2ca547a", + "31bada0f-ba38-4bea-9433-1633f0f23c5e", + "6e4d1087-3947-4055-ae8a-9462f9f04c56", + "8afbeeee-a81a-4970-9eea-93d34821394a", + "8986b290-fdc9-4238-bf88-88571e291a3e", + "ca1a5b97-b477-4fa3-838d-81628a7309f6", + "ff292ce3-c6b8-48d0-8392-1d16d4bd5e05", + "8a281d83-9627-4a1b-a6c4-4d90b721968b", + "5f54a59a-179a-4ed8-893b-9f3ca560a00c", + "d0b616eb-bb91-4d52-914c-f97c3270944e", + "9fcf86dd-7537-4853-96ec-0ce87e4f4b01", + "10570be9-fe63-451b-b4b5-7499b72a4124", + "e08fe21b-a7df-4b51-94cd-5a3f4fd81e9e", + "d494bcf8-0292-4df5-ad52-86023a31a72d", + "47b43b18-f729-45ce-b8ac-4c74223d2c52", + "acba00b2-7e1e-44f5-895e-cddbe3a3c665", + "0b1b2372-571a-42c3-b46c-22e7f5bbf7e8", + "c26983dd-045e-46be-8ef1-dda5694d10d0", + "0068b183-aa92-49a5-b52c-525e66221ef5", + "b628447a-3fe5-4648-832a-e801138a3574", + "a2810ed1-79aa-4dc6-8cb8-b3e6a94b9338", + "1bd0bb25-49ce-4b1e-ba8e-37bbe7e61b55", + "67f37ffe-a628-48de-9185-cc882a2a4a9a", + "de8e5b73-4411-4ef2-ba2e-258de18b2026", + "53e93cc1-4c06-4c36-8bb7-946d4c6282be", + "42625dab-14d2-451d-a41d-29e791213d54", + "0d46f288-6e0e-48fe-b19f-050d01a2618b", + "07487f33-e356-49b9-962c-ec2c81360fff", + "ed02825f-c11f-4932-a807-9884617808d9", + "4f635fcd-6803-4c90-a403-ae731b7b830d", + "c052ef5e-92fa-4111-8500-1dfafe7d348a", + "78c0f618-3b93-417a-962e-46900361a815", + "b66cd32a-a432-4743-804b-a1968f2a232d", + "f61098e3-c07d-4cc4-8e35-b6748a33a0f2", + "57ef1dd1-4df4-4d6e-a1bf-0e12eed78271", + "9e1cd3fe-a1d7-483c-9b76-d31c64ac63b4", + "e21325bd-0cce-4c0a-b835-0fe31918c54d", + "38f0e407-8ccd-4651-b3be-1f9141470e1c", + "22f0abfb-2069-4597-9435-a130b3bf559f", + "210ad388-d213-4178-bc05-000262d7c9a6", + "c73ff05e-df79-42fe-9210-5f63c8e93d79", + "e1fe0dea-4352-4d8c-a62c-7671f424d228", + "0ef465eb-f4f6-4ca5-98b7-a409615bb54b", + "eb134829-84d2-4ae5-95a1-1ad3b2d55934", + "0f554869-f3c8-43cf-819c-3a98daf99ce4", + "d2795605-b230-450d-81ba-6612020420e3", + "5f3b00d2-9c98-45dc-a1af-de81d3829db8", + "57ad7f68-e602-4e4e-a3ee-05d1585bbbe3", + "f701fb3f-10c5-4d1e-b5dd-89dfee26c665", + "eab0ef10-f5ab-4388-97b4-20d501823a0d", + "0910053a-b4b1-42bb-8ec2-38649113b2ab", + "00f46915-5783-40f3-90dc-00b746cef450", + "3d5aaf92-119f-4be7-b5da-3c7c6a93250a", + "1c6fe9ef-c38d-4b03-bf4c-67e5344f69c1", + "c438fd39-7d8c-409d-bf26-b3718b71b907", + "45c35990-0a81-4f72-a074-f52ea5e6afe4", + "d1a60043-6add-48cc-a02e-917e0c6f705f", + "c756bb04-9817-4d35-9285-d12e92e29762", + "c94cdc42-a225-4eb3-94d1-bc6933c05893", + "d0e4539f-933d-4391-812a-ddce7b016ba8", + "504db0d5-c5f5-48f4-bca6-e70845c1541b", + "5d68ecc8-6590-4c32-aeda-8c0d9698ec4f", + "305800e9-ac76-493d-a09b-5b9e29cf40d8", + "75b17ed1-89d6-4fb0-b7bb-367f1cc6ed61", + "b2583205-6501-4928-b01c-0cb3255c47ae", + "0e3c8c83-f889-449d-bdce-1ce0f09c6fd0", + "0040d3e6-8a1a-4987-87b3-e6fcfb0561c2", + "f7b28b8a-5817-41b1-8377-607f69ba6a41", + "cdb34033-0a0f-4d35-a9a1-334a20937e9a", + "05703357-fd11-44e1-b11d-58a8d26ad628", + "408b0d8b-10af-482f-a7a6-438b040145e9", + "59ad2a7a-b02a-4f74-9c42-1297118d59df", + "37f2e9d5-d8d7-42f9-9ad1-a7e267820df4", + "2975c522-f593-4b9c-926b-c8aaee8017d3", + "410cf03b-1adf-43d4-a286-1f833f0d2b21", + "2d87e41e-a4b7-4677-b8f6-458ebaa926e3", + "7698f470-4c0e-4469-ae98-2daef6343370", + "d7556bf7-4a84-4cbc-80ce-aa455a162117", + "7d1ae902-2574-4268-8387-1d64b32f71cc", + "7412b1ae-2491-40e4-8b25-1b44502a2b9a" + ], + "scenario": "worker_termination_and_lease_recovery", + "task_count": 10000, + "tasks_per_run": 100, + "workflow_id": "254e09dc-8074-492e-a3f6-c67939c31d50" +} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/worker-termination.json b/tests/benchmarks/results/2026-07-21T003600Z/worker-termination.json new file mode 100644 index 0000000..7c0999d --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/worker-termination.json @@ -0,0 +1,6 @@ +{ + "terminated_workers": 20, + "idempotency_prefix": "validation-crash-20260721T003824Z", + "in_flight_tasks": 6, + "terminated_at": "2026-07-21T00:41:26.236449+00:00" +} diff --git a/tests/benchmarks/results/2026-07-21T003600Z/workflow-submissions.json b/tests/benchmarks/results/2026-07-21T003600Z/workflow-submissions.json new file mode 100644 index 0000000..e02e9cf --- /dev/null +++ b/tests/benchmarks/results/2026-07-21T003600Z/workflow-submissions.json @@ -0,0 +1,142 @@ +{ + "root_group": { + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": {}, + "checks": { + "created": { + "name": "created", + "path": "::created", + "id": "e26a6144a84abb127f65c1e5808b420c", + "passes": 1501, + "fails": 0 + } + }, + "name": "" + }, + "metrics": { + "data_received": { + "count": 812456, + "rate": 27079.08538102827 + }, + "vus": { + "value": 0, + "min": 0, + "max": 13 + }, + "checks": { + "passes": 1501, + "fails": 0, + "thresholds": { + "rate==1": false + }, + "value": 1 + }, + "http_reqs": { + "count": 1501, + "rate": 50.02819495077079 + }, + "http_req_duration": { + "p(99)": 220.024417, + "max": 688.186375, + "avg": 8.304694437041961, + "min": 0.999625, + "med": 1.587208, + "p(90)": 3.284625, + "p(95)": 7.011625 + }, + "iteration_duration": { + "avg": 8.665801076615596, + "min": 1.068708, + "med": 1.723208, + "p(90)": 3.71275, + "p(95)": 7.773292, + "p(99)": 249.570958, + "max": 689.255459 + }, + "http_req_tls_handshaking": { + "avg": 0, + "min": 0, + "med": 0, + "p(90)": 0, + "p(95)": 0, + "p(99)": 0, + "max": 0 + }, + "http_req_receiving": { + "min": 0.006417, + "med": 0.030917, + "p(90)": 0.058666, + "p(95)": 0.08575, + "p(99)": 0.173875, + "max": 15.600667, + "avg": 0.06613877415056635 + }, + "vus_max": { + "value": 100, + "min": 100, + "max": 100 + }, + "http_req_waiting": { + "p(90)": 3.181084, + "p(95)": 6.825375, + "p(99)": 219.939958, + "max": 684.846584, + "avg": 8.212280045303142, + "min": 0.959917, + "med": 1.535708 + }, + "http_req_connecting": { + "avg": 0.059911614257161905, + "min": 0, + "med": 0, + "p(90)": 0, + "p(95)": 0.520917, + "p(99)": 0.716375, + "max": 22.150542 + }, + "http_req_duration{expected_response:true}": { + "max": 688.186375, + "avg": 8.304694437041961, + "min": 0.999625, + "med": 1.587208, + "p(90)": 3.284625, + "p(95)": 7.011625, + "p(99)": 220.024417 + }, + "iterations": { + "count": 1501, + "rate": 50.02819495077079 + }, + "data_sent": { + "count": 2330313, + "rate": 77669.12262512694 + }, + "http_req_failed": { + "passes": 0, + "fails": 1501, + "thresholds": { + "rate==0": false + }, + "value": 0 + }, + "http_req_blocked": { + "avg": 0.06917285809460354, + "min": 0.0015, + "med": 0.003666, + "p(90)": 0.016042, + "p(95)": 0.567292, + "p(99)": 0.791583, + "max": 22.1775 + }, + "http_req_sending": { + "min": 0.007583, + "med": 0.0185, + "p(90)": 0.033459, + "p(95)": 0.047084, + "p(99)": 0.102375, + "max": 3.236916, + "avg": 0.026275617588274455 + } + } +} \ No newline at end of file diff --git a/tests/benchmarks/run-local.sh b/tests/benchmarks/run-local.sh index fb3a743..a0917b0 100755 --- a/tests/benchmarks/run-local.sh +++ b/tests/benchmarks/run-local.sh @@ -1,22 +1,51 @@ #!/usr/bin/env bash set -euo pipefail -# Run against the disposable local Compose stack. This script never provisions -# cloud resources and intentionally leaves the stack available for inspection. -result_dir=${1:-tests/benchmarks/results/$(date +%F)} +# Runs only against the local Compose project. It never provisions cloud +# resources. Raw result directories are immutable: choose a new path to rerun. +result_dir=${1:-tests/benchmarks/results/$(date -u +%Y-%m-%dT%H%M%SZ)} +if [[ -e "$result_dir" ]]; then + echo "result directory already exists: $result_dir" >&2 + exit 1 +fi mkdir -p "$result_dir" run_suffix=$(date -u +%Y%m%dT%H%M%SZ) active_prefix="validation-active-$run_suffix" crash_prefix="validation-crash-$run_suffix" duplicate_prefix="validation-duplicate-$run_suffix" -benchmark_token=$(python3 tests/benchmarks/local.py token) cp "$0" "$result_dir/commands.sh" +docker compose up -d --build +docker compose up -d --scale scheduler=2 --scale worker-go=1 --scale worker-python=1 scheduler worker-go worker-python +for endpoint in \ + http://localhost:8180/realms/runmesh/.well-known/openid-configuration \ + http://localhost:8080/health/ready; do + ready=0 + for _ in $(seq 1 90); do + if curl -fsS "$endpoint" >/dev/null; then ready=1; break; fi + sleep 2 + done + [[ "$ready" == 1 ]] || { echo "benchmark dependency did not become ready: $endpoint" >&2; exit 1; } +done +benchmark_token=$(python3 tests/benchmarks/local.py token) -docker run --rm --network host \ +docker compose exec -T redpanda rpk topic describe runmesh.tasks --format json > "$result_dir/kafka-topology-before.json" +read_workflow=$(python3 tests/benchmarks/local.py definition --prefix "validation-read-$run_suffix") +docker run --rm --add-host host.docker.internal:host-gateway \ + -e RUNMESH_API=http://host.docker.internal:8080 \ + -e RUNMESH_READ_PATH="/v1/workflows/$read_workflow" \ -e RUNMESH_TOKEN="$benchmark_token" -e RUNMESH_RATE=100 -e RUNMESH_DURATION=30s \ -v "$PWD/tests/load":/scripts:ro -v "$PWD/$result_dir":/results \ - grafana/k6:0.57.0 run --summary-export=/results/api-100-rps.json /scripts/api-read.js + grafana/k6:0.57.0 run --summary-export=/results/api-read.json /scripts/api-read.js + +sleep 3 +submission_workflow=$(python3 tests/benchmarks/local.py definition --prefix "validation-submission-$run_suffix") +docker run --rm --add-host host.docker.internal:host-gateway \ + -e RUNMESH_API=http://host.docker.internal:8080 \ + -e RUNMESH_TOKEN="$benchmark_token" -e RUNMESH_WORKFLOW_ID="$submission_workflow" \ + -e RUNMESH_RATE=50 -e RUNMESH_DURATION=30s \ + -v "$PWD/tests/load":/scripts:ro -v "$PWD/$result_dir":/results \ + grafana/k6:0.57.0 run --summary-export=/results/workflow-submissions.json /scripts/submissions.js docker compose stop scheduler worker-python worker-go python3 tests/benchmarks/local.py active --count 1000 --prefix "$active_prefix" > "$result_dir/active-workflows.json" @@ -24,51 +53,78 @@ docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ "SELECT json_build_object('active_runs',count(*),'active_tasks',sum(task_count)) FROM (SELECT wr.id,count(tr.id) task_count FROM workflow_runs wr JOIN task_runs tr ON tr.workflow_run_id=wr.id WHERE wr.idempotency_key LIKE '$active_prefix-%' AND wr.status='RUNNING' GROUP BY wr.id) rows" \ > "$result_dir/active-workflows-database.json" -# Remove the completed active-count fixture before measuring dispatch throughput. -docker compose exec -T postgres psql -U runmesh -d runmesh -c \ - "UPDATE task_runs SET status='CANCELLED' WHERE workflow_run_id IN (SELECT id FROM workflow_runs WHERE idempotency_key LIKE '$active_prefix-%'); UPDATE workflow_runs SET status='CANCELLED',completed_at=now() WHERE idempotency_key LIKE '$active_prefix-%';" >/dev/null +# Start two schedulers and twelve workers against six partitions, then measure +# drain throughput for the 1,000 distinct workflow keys created above. +completion_start=$(python3 -c 'import time; print(time.time_ns())') +docker compose up -d --scale scheduler=2 scheduler +docker compose up -d --build --scale worker-go=12 worker-go +while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM workflow_runs WHERE idempotency_key LIKE '$active_prefix-%' AND status NOT IN ('SUCCEEDED','FAILED','CANCELLED')") != 0 ]]; do sleep 0.2; done +completion_end=$(python3 -c 'import time; print(time.time_ns())') +python3 - "$completion_start" "$completion_end" > "$result_dir/end-to-end-completion.json" <<'PY' +import json, sys +elapsed = (int(sys.argv[2]) - int(sys.argv[1])) / 1_000_000_000 +print(json.dumps({"scenario":"queued_end_to_end_completion","tasks":1000,"elapsed_seconds":round(elapsed,3),"tasks_per_second":round(1000/elapsed,3)}, indent=2)) +PY +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('tasks',count(*),'succeeded',count(*) FILTER (WHERE tr.status='SUCCEEDED'),'other',count(*) FILTER (WHERE tr.status!='SUCCEEDED')) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$active_prefix-%'" \ + > "$result_dir/end-to-end-completion-database.json" -# Begin at the current topic end so a repeat run measures only its own work. +# Begin at the current topic end so this scenario observes only its own work. +docker compose stop worker-go docker compose exec -T redpanda rpk group seek runmesh-workers --to end --topics runmesh.tasks >/dev/null python3 tests/benchmarks/local.py prepare --tasks 10000 --prefix "$crash_prefix" > "$result_dir/worker-crash-setup.json" -dispatch_start=$(date +%s) -docker compose up -d scheduler +dispatch_start=$(python3 -c 'import time; print(time.time_ns())') while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status IN ('READY','BLOCKED')") != 0 ]]; do sleep 0.1; done -dispatch_end=$(date +%s) +dispatch_end=$(python3 -c 'import time; print(time.time_ns())') python3 - "$dispatch_start" "$dispatch_end" > "$result_dir/scheduler-dispatch.json" <<'PY' import json, sys -elapsed = max(1, int(sys.argv[2]) - int(sys.argv[1])) +elapsed = (int(sys.argv[2]) - int(sys.argv[1])) / 1_000_000_000 print(json.dumps({"scenario":"scheduler_dispatch","tasks":10000,"elapsed_seconds":round(elapsed,3),"dispatches_per_second":round(10000/elapsed,3)}, indent=2)) PY -# Make the first dispatch long enough to guarantee an in-flight lease when the -# entire worker pool is terminated; the other 9,999 tasks remain short. +# Keep enough tasks running to occupy every available Kafka partition before +# terminating the entire 20-container worker pool. docker compose exec -T postgres psql -U runmesh -d runmesh -c \ - "UPDATE task_runs SET input='{\"delay_ms\":5000}'::jsonb WHERE id=(SELECT tr.id FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id JOIN outbox_events o ON o.aggregate_id=tr.id AND o.event_type='task.dispatch' WHERE wr.idempotency_key LIKE '$crash_prefix-%' ORDER BY o.created_at,o.id LIMIT 1);" >/dev/null + "WITH selected AS (SELECT tr.id FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' ORDER BY tr.id LIMIT 100) UPDATE task_runs SET input='{\"delay_ms\":5000}'::jsonb WHERE id IN (SELECT id FROM selected);" >/dev/null docker compose up -d --build --scale worker-go=20 worker-go -while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status='RUNNING'") == 0 ]]; do sleep 0.1; done +deadline=$((SECONDS+60)) +while true; do + running=$(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status='RUNNING'") + [[ "$running" -ge 4 ]] && break + [[ "$SECONDS" -ge "$deadline" ]] && { echo "fewer than four tasks became concurrently RUNNING" >&2; exit 1; } + sleep 0.1 +done +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "SELECT json_build_object('running_count',count(*),'task_ids',json_agg(id ORDER BY id)) FROM (SELECT tr.id FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND tr.status='RUNNING') active" \ + > "$result_dir/in-flight-before-termination.json" worker_ids=( $(docker compose ps -q worker-go) ) -docker kill "${worker_ids[@]}" -python3 - "${#worker_ids[@]}" "$crash_prefix" > "$result_dir/worker-termination.json" <<'PY' +docker kill "${worker_ids[@]}" >/dev/null +python3 - "${#worker_ids[@]}" "$crash_prefix" "$running" > "$result_dir/worker-termination.json" <<'PY' import datetime, json, sys -print(json.dumps({"terminated_workers":int(sys.argv[1]),"idempotency_prefix":sys.argv[2],"terminated_at":datetime.datetime.now(datetime.timezone.utc).isoformat()}, indent=2)) +print(json.dumps({"terminated_workers":int(sys.argv[1]),"idempotency_prefix":sys.argv[2],"in_flight_tasks":int(sys.argv[3]),"terminated_at":datetime.datetime.now(datetime.timezone.utc).isoformat()}, indent=2)) PY +recovery_start=$(python3 -c 'import time; print(time.time_ns())') docker compose up -d --scale worker-go=20 worker-go -recovery_start=$(date +%s) while [[ $(docker compose exec -T postgres psql -U runmesh -d runmesh -Atc "SELECT count(*) FROM workflow_runs WHERE idempotency_key LIKE '$crash_prefix-%' AND status NOT IN ('SUCCEEDED','FAILED','CANCELLED')") != 0 ]]; do sleep 1; done -recovery_end=$(date +%s) +recovery_end=$(python3 -c 'import time; print(time.time_ns())') +recovery_seconds=$(python3 -c "print(round(($recovery_end-$recovery_start)/1_000_000_000,3))") docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ - "SELECT json_build_object('scenario','worker_termination_and_lease_recovery','tasks',count(*),'succeeded',count(*) FILTER (WHERE tr.status='SUCCEEDED'),'other',count(*) FILTER (WHERE tr.status!='SUCCEEDED'),'attempts',sum(tr.attempt_count),'recovered_tasks',count(*) FILTER (WHERE tr.attempt_count > 1),'recovery_seconds',$((recovery_end-recovery_start))) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%'" \ + "SELECT json_build_object('scenario','worker_termination_and_lease_recovery','tasks',count(*),'succeeded',count(*) FILTER (WHERE tr.status='SUCCEEDED'),'other',count(*) FILTER (WHERE tr.status!='SUCCEEDED'),'attempts',sum(tr.attempt_count),'recovered_tasks',count(*) FILTER (WHERE tr.attempt_count > 1),'duplicate_attempts',sum(tr.attempt_count)-count(*),'recovery_seconds',$recovery_seconds) FROM task_runs tr JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%'" \ > "$result_dir/worker-crash-result.json" +docker compose exec -T postgres psql -U runmesh -d runmesh -Atc \ + "WITH gaps AS (SELECT EXTRACT(EPOCH FROM (next_attempt.started_at-expired.ended_at))*1000 milliseconds FROM task_attempts expired JOIN task_attempts next_attempt ON next_attempt.task_run_id=expired.task_run_id AND next_attempt.attempt_number=expired.attempt_number+1 JOIN task_runs tr ON tr.id=expired.task_run_id JOIN workflow_runs wr ON wr.id=tr.workflow_run_id WHERE wr.idempotency_key LIKE '$crash_prefix-%' AND expired.error_type='LeaseExpired') SELECT json_build_object('samples',count(*),'p50_ms',percentile_cont(0.50) WITHIN GROUP (ORDER BY milliseconds),'p95_ms',percentile_cont(0.95) WITHIN GROUP (ORDER BY milliseconds),'p99_ms',percentile_cont(0.99) WITHIN GROUP (ORDER BY milliseconds)) FROM gaps" \ + > "$result_dir/lease-recovery-distribution.json" python3 tests/benchmarks/local.py duplicate --prefix "$duplicate_prefix" > "$result_dir/duplicate-submission.json" -docker run --rm \ - -v "$PWD":/src -w /src -v /var/run/docker.sock:/var/run/docker.sock \ +docker run --rm -v "$PWD":/src -w /src -v /var/run/docker.sock:/var/run/docker.sock \ + -v runmesh-go-mod:/go/pkg/mod -v runmesh-go-build:/root/.cache/go-build \ -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal golang:1.25 \ - sh -c 'go test -tags=integration -count=1 -run TestDuplicateDispatchIsLeasedOnce -json ./tests/integration' \ + /usr/local/go/bin/go test -tags=integration -count=1 -run TestDuplicateDispatchIsLeasedOnce -json ./tests/integration \ > "$result_dir/duplicate-delivery-go-test.json" docker compose images --format json > "$result_dir/image-digests.json" +docker compose ps --format json > "$result_dir/compose-topology.json" +docker compose exec -T redpanda rpk topic describe runmesh.tasks --format json > "$result_dir/kafka-topology-after.json" { uname -a sysctl -n machdep.cpu.brand_string 2>/dev/null || true diff --git a/tests/benchmarks/verify.py b/tests/benchmarks/verify.py index 85002a2..5a55e0f 100755 --- a/tests/benchmarks/verify.py +++ b/tests/benchmarks/verify.py @@ -19,26 +19,40 @@ def main() -> None: args = parser.parse_args() root = args.result_dir - api = load(root / "api-100-rps.json") + api = load(root / "api-read.json") + submissions = load(root / "workflow-submissions.json") active = load(root / "active-workflows.json") active_db = load(root / "active-workflows-database.json") + completion = load(root / "end-to-end-completion-database.json") crash = load(root / "worker-crash-result.json") termination = load(root / "worker-termination.json") + recovery = load(root / "lease-recovery-distribution.json") duplicate = load(root / "duplicate-submission.json") + kafka_before = load(root / "kafka-topology-before.json")[0] + kafka_after = load(root / "kafka-topology-after.json")[0] delivery_events = [json.loads(line) for line in (root / "duplicate-delivery-go-test.json").read_text(encoding="utf-8").splitlines()] + before_offsets = {item["partition"]: item["high_watermark"] for item in kafka_before["partitions"]} + used_partitions = sum(item["high_watermark"] > before_offsets.get(item["partition"], 0) for item in kafka_after["partitions"]) + checks = { "api_requests": api["metrics"]["http_reqs"]["count"] >= 3000, "api_zero_failures": api["metrics"]["http_req_failed"]["value"] == 0, + "submission_requests": submissions["metrics"]["http_reqs"]["count"] >= 1400, + "submission_zero_failures": submissions["metrics"]["http_req_failed"]["value"] == 0, "active_workflows": active["created"] == active["requested"] == active_db["active_runs"] == 1000, + "end_to_end_zero_task_loss": completion["tasks"] == completion["succeeded"] == 1000 and completion["other"] == 0, + "six_kafka_partitions": kafka_after["summary"]["partitions"] == 6, + "multi_partition_delivery": used_partitions >= 4, "worker_pool_terminated": termination["terminated_workers"] == 20, + "multiple_tasks_in_flight": termination["in_flight_tasks"] >= 4, "ten_thousand_tasks": crash["tasks"] == crash["succeeded"] == 10_000, "zero_task_loss": crash["other"] == 0, - "lease_recovered": crash["attempts"] > crash["tasks"] and crash["recovered_tasks"] > 0, + "lease_recovered": crash["attempts"] > crash["tasks"] and crash["recovered_tasks"] >= 4 and recovery["samples"] >= 4, "duplicate_submission": duplicate["passed"] is True, "duplicate_delivery": any(event.get("Action") == "pass" and event.get("Test") == "TestDuplicateDispatchIsLeasedOnce" for event in delivery_events), } - output = {"passed": all(checks.values()), "checks": checks} + output = {"passed": all(checks.values()), "checks": checks, "observed_partitions_with_new_records": used_partitions} print(json.dumps(output, indent=2, sort_keys=True)) if not output["passed"]: raise SystemExit(1) diff --git a/tests/deployment/kind-dependencies.yaml b/tests/deployment/kind-dependencies.yaml index 3d20ead..823a1e4 100644 --- a/tests/deployment/kind-dependencies.yaml +++ b/tests/deployment/kind-dependencies.yaml @@ -59,3 +59,26 @@ apiVersion: v1 kind: Service metadata: {name: redpanda} spec: {selector: {app: redpanda}, ports: [{port: 9092}]} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: {name: minio} +spec: + selector: {matchLabels: {app: minio}} + template: + metadata: {labels: {app: minio}} + spec: + containers: + - name: minio + image: minio/minio:RELEASE.2025-02-07T23-21-09Z + args: [server, /data] + env: + - {name: MINIO_ROOT_USER, value: runmesh} + - {name: MINIO_ROOT_PASSWORD, value: runmesh-development} + ports: [{containerPort: 9000}] + readinessProbe: {httpGet: {path: /minio/health/live, port: 9000}, periodSeconds: 2, failureThreshold: 30} +--- +apiVersion: v1 +kind: Service +metadata: {name: minio} +spec: {selector: {app: minio}, ports: [{port: 9000}]} diff --git a/tests/deployment/kind-rolling-upgrade.sh b/tests/deployment/kind-rolling-upgrade.sh index 790ea10..e69c90d 100755 --- a/tests/deployment/kind-rolling-upgrade.sh +++ b/tests/deployment/kind-rolling-upgrade.sh @@ -44,7 +44,7 @@ if [[ "${RUNMESH_SKIP_BUILD:-0}" != "1" ]]; then docker build -t runmesh/control-plane:rolling --build-arg SERVICE=control-plane . docker build -t runmesh/scheduler:rolling --build-arg SERVICE=scheduler . docker build -t runmesh/worker-go:rolling --build-arg SERVICE=worker-go . - docker build -t runmesh/web:rolling -f web/Dockerfile --build-arg VITE_DEV_AUTH=true . + docker build -t runmesh/web:rolling -f web/Dockerfile --build-arg WEB_DEVELOPMENT_MODE=true . fi kind load docker-image --name "$cluster" runmesh/control-plane:rolling runmesh/scheduler:rolling runmesh/worker-go:rolling runmesh/web:rolling @@ -53,6 +53,7 @@ kubectl -n "$namespace" apply -f tests/deployment/kind-dependencies.yaml kubectl -n "$namespace" rollout status deployment/postgres --timeout=180s kubectl -n "$namespace" rollout status deployment/redis --timeout=180s kubectl -n "$namespace" rollout status deployment/redpanda --timeout=240s +kubectl -n "$namespace" rollout status deployment/minio --timeout=180s kubectl -n "$namespace" create configmap runmesh-migrations --from-file=migrations kubectl -n "$namespace" apply -f - <<'YAML' diff --git a/tests/deployment/kind-values.yaml b/tests/deployment/kind-values.yaml index 2588514..e7768b8 100644 --- a/tests/deployment/kind-values.yaml +++ b/tests/deployment/kind-values.yaml @@ -12,11 +12,20 @@ config: oidcIssuer: "" oidcJwksURL: "" corsAllowlist: http://127.0.0.1:18080 + artifactEndpoint: http://minio:9000 + artifactPublicEndpoint: http://minio:9000 + artifactPathStyle: true + artifactCreateBucket: true + +secret: + artifactAccessKey: runmesh + artifactSecretKey: runmesh-development workerPools: - name: default-go enabled: true type: go + groupID: runmesh-workers replicas: 2 image: {repository: "", tag: "", pullPolicy: ""} credentialSecretName: "" diff --git a/tests/integration/stack_test.go b/tests/integration/stack_test.go index e000dd6..57d1237 100644 --- a/tests/integration/stack_test.go +++ b/tests/integration/stack_test.go @@ -21,13 +21,13 @@ import ( toxiproxyclient "github.com/Shopify/toxiproxy/v2/client" "github.com/jackc/pgx/v5" "github.com/prometheus/client_golang/prometheus" - "github.com/runmesh/runmesh/internal/artifact" - "github.com/runmesh/runmesh/internal/auth" - "github.com/runmesh/runmesh/internal/messaging" - "github.com/runmesh/runmesh/internal/ratelimit" - "github.com/runmesh/runmesh/internal/scheduler" - "github.com/runmesh/runmesh/internal/storage" - "github.com/runmesh/runmesh/internal/workflow" + "github.com/samarth1412/RunMesh/internal/artifact" + "github.com/samarth1412/RunMesh/internal/auth" + "github.com/samarth1412/RunMesh/internal/messaging" + "github.com/samarth1412/RunMesh/internal/ratelimit" + "github.com/samarth1412/RunMesh/internal/scheduler" + "github.com/samarth1412/RunMesh/internal/storage" + "github.com/samarth1412/RunMesh/internal/workflow" "github.com/segmentio/kafka-go" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/redpanda" @@ -141,69 +141,69 @@ func TestArtifactOwnershipPresigningAndSlowUploadRecovery(t *testing.T) { func TestPostgresIdempotencyAndConcurrentClaims(t *testing.T) { ctx := context.Background() store := postgresStore(t, ctx) + const tenantOne = "00000000-0000-0000-0000-000000000001" + const userOne = "00000000-0000-0000-0000-000000000001" + const tenantTwo = "00000000-0000-0000-0000-000000000020" + const userTwo = "00000000-0000-0000-0000-000000000020" + const taskCount = 240 dag := workflow.DAG{Tasks: map[string]workflow.TaskSpec{}} - for i := 0; i < 40; i++ { - key := fmt.Sprintf("task-%02d", i) + for i := 0; i < taskCount; i++ { + key := fmt.Sprintf("task-%03d", i) dag.Tasks[key] = workflow.TaskSpec{Handler: "test.handle"} } - definition, err := store.CreateWorkflow(ctx, "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000001", "claims", dag) + definition, err := store.CreateWorkflow(ctx, tenantOne, userOne, "claims", dag) if err != nil { t.Fatal(err) } - first, created, err := store.CreateRun(ctx, "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000001", definition.ID, "same-key", json.RawMessage(`{"value":1}`)) + first, created, err := store.CreateRun(ctx, tenantOne, userOne, definition.ID, "same-key", json.RawMessage(`{"value":1}`)) if err != nil || !created { t.Fatalf("first submission: created=%v err=%v", created, err) } - second, created, err := store.CreateRun(ctx, "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000001", definition.ID, "same-key", json.RawMessage(`{"value":2}`)) + second, created, err := store.CreateRun(ctx, tenantOne, userOne, definition.ID, "same-key", json.RawMessage(`{"value":2}`)) if err != nil || created || second.ID != first.ID { t.Fatalf("duplicate submission: first=%s second=%s created=%v err=%v", first.ID, second.ID, created, err) } + if _, err = store.Pool.Exec(ctx, `INSERT INTO tenants(id,name,plan) VALUES($1,'Idempotency tenant','development')`, tenantTwo); err != nil { + t.Fatal(err) + } + if _, err = store.Pool.Exec(ctx, `INSERT INTO users(id,tenant_id,oidc_subject,email,role) VALUES($1,$2,'idempotency-user','idempotency@example.com','admin')`, userTwo, tenantTwo); err != nil { + t.Fatal(err) + } + secondDefinition, err := store.CreateWorkflow(ctx, tenantTwo, userTwo, "claims", workflow.DAG{Tasks: map[string]workflow.TaskSpec{"only": {Handler: "test.handle"}}}) + if err != nil { + t.Fatal(err) + } + crossTenant, created, err := store.CreateRun(ctx, tenantTwo, userTwo, secondDefinition.ID, "same-key", json.RawMessage(`{"value":3}`)) + if err != nil || !created || crossTenant.ID == first.ID { + t.Fatalf("tenant-scoped idempotency: first=%s cross_tenant=%s created=%v err=%v", first.ID, crossTenant.ID, created, err) + } - claimed := map[string]bool{} - var mu sync.Mutex + services := make([]scheduler.Service, 4) + for i := range services { + services[i] = scheduler.Service{Store: store} + } var wg sync.WaitGroup - for i := 0; i < 8; i++ { + for i := range services { wg.Add(1) - go func() { + go func(service *scheduler.Service) { defer wg.Done() - for { - tx, e := store.Pool.BeginTx(ctx, pgx.TxOptions{}) - if e != nil { - t.Error(e) - return - } - var id string - e = tx.QueryRow(ctx, `SELECT id FROM task_runs WHERE workflow_run_id=$1 AND status='READY' FOR UPDATE SKIP LOCKED LIMIT 1`, first.ID).Scan(&id) - if e == pgx.ErrNoRows { - _ = tx.Rollback(ctx) - return - } - if e != nil { - _ = tx.Rollback(ctx) - t.Error(e) - return - } - if _, e = tx.Exec(ctx, `UPDATE task_runs SET status='DISPATCHING' WHERE id=$1`, id); e != nil { - _ = tx.Rollback(ctx) - t.Error(e) - return - } - if e = tx.Commit(ctx); e != nil { - t.Error(e) - return - } - mu.Lock() - if claimed[id] { - t.Errorf("task %s claimed twice", id) - } - claimed[id] = true - mu.Unlock() + if runErr := service.RunOnce(ctx); runErr != nil { + t.Error(runErr) } - }() + }(&services[i]) } wg.Wait() - if len(claimed) != 40 { - t.Fatalf("claimed %d tasks, want 40", len(claimed)) + var dispatched, dispatchEvents, distinctDispatches, maxDispatchesPerTask int + if err = store.Pool.QueryRow(ctx, `SELECT + count(*) FILTER (WHERE status='DISPATCHING'), + (SELECT count(*) FROM outbox_events WHERE event_type='task.dispatch' AND payload->>'workflow_run_id'=$1::text), + (SELECT count(DISTINCT aggregate_id) FROM outbox_events WHERE event_type='task.dispatch' AND payload->>'workflow_run_id'=$1::text), + COALESCE((SELECT max(event_count) FROM (SELECT count(*) event_count FROM outbox_events WHERE event_type='task.dispatch' AND payload->>'workflow_run_id'=$1::text GROUP BY aggregate_id) counts),0) + FROM task_runs WHERE workflow_run_id=$1::uuid`, first.ID).Scan(&dispatched, &dispatchEvents, &distinctDispatches, &maxDispatchesPerTask); err != nil { + t.Fatal(err) + } + if dispatched != taskCount || dispatchEvents != taskCount || distinctDispatches != taskCount || maxDispatchesPerTask != 1 { + t.Fatalf("concurrent scheduler claims: dispatched=%d events=%d distinct=%d max_per_task=%d", dispatched, dispatchEvents, distinctDispatches, maxDispatchesPerTask) } } @@ -254,6 +254,24 @@ func TestProductionAPIKeysAndWorkerTenantIsolation(t *testing.T) { if err = store.TaskBelongsToTenant(ctx, taskID, tenantTwo); !errors.Is(err, storage.ErrNotFound) { t.Fatalf("cross-tenant task lookup error=%v, want not found", err) } + if err = store.ActiveTaskLeaseBelongsToWorker(ctx, taskID, tenantOne, "worker-one"); !errors.Is(err, storage.ErrLeaseLost) { + t.Fatalf("undispatched task active lease error=%v, want lease lost", err) + } + if err = (&scheduler.Service{Store: store}).RunOnce(ctx); err != nil { + t.Fatal(err) + } + if _, err = store.LeaseTask(ctx, taskID, "worker-one", time.Minute); err != nil { + t.Fatal(err) + } + if err = store.ActiveTaskLeaseBelongsToWorker(ctx, taskID, tenantOne, "worker-one"); err != nil { + t.Fatalf("active worker lease: %v", err) + } + if err = store.ActiveTaskLeaseBelongsToWorker(ctx, taskID, tenantOne, "worker-two"); !errors.Is(err, storage.ErrLeaseLost) { + t.Fatalf("wrong worker active lease error=%v, want lease lost", err) + } + if err = store.ActiveTaskLeaseBelongsToWorker(ctx, taskID, tenantTwo, "worker-one"); !errors.Is(err, storage.ErrNotFound) { + t.Fatalf("cross-tenant active lease error=%v, want not found", err) + } if err = store.WorkerHeartbeat(ctx, tenantOne, "shared-worker-id", []string{"test.secure"}, 1, nil); err != nil { t.Fatal(err) } @@ -524,10 +542,11 @@ func TestRedisRedpandaAndMinIOContainers(t *testing.T) { func TestWorkerCrashLeaseRecovery(t *testing.T) { ctx := context.Background() store := postgresStore(t, ctx) - - dag := workflow.DAG{Tasks: map[string]workflow.TaskSpec{ - "recover": {Handler: "test.recover", MaximumAttempts: 3}, - }} + const taskCount = 12 + dag := workflow.DAG{Tasks: make(map[string]workflow.TaskSpec, taskCount)} + for i := 0; i < taskCount; i++ { + dag.Tasks[fmt.Sprintf("recover-%02d", i)] = workflow.TaskSpec{Handler: "test.recover", MaximumAttempts: 3} + } definition, err := store.CreateWorkflow(ctx, "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000001", "worker-crash", dag) if err != nil { t.Fatal(err) @@ -542,61 +561,93 @@ func TestWorkerCrashLeaseRecovery(t *testing.T) { t.Fatal(err) } run, err = store.GetRun(ctx, "00000000-0000-0000-0000-000000000001", run.ID) - if err != nil || len(run.Tasks) != 1 || run.Tasks[0].Status != "DISPATCHING" { - t.Fatalf("task was not dispatched: run=%+v err=%v", run, err) + if err != nil || len(run.Tasks) != taskCount { + t.Fatalf("tasks were not dispatched: count=%d err=%v", len(run.Tasks), err) } - taskID := run.Tasks[0].ID - first, err := store.LeaseTask(ctx, taskID, "worker-crashed", 150*time.Millisecond) - if err != nil { - t.Fatal(err) + workerByTask := make(map[string]string, taskCount) + for i, task := range run.Tasks { + if task.Status != "DISPATCHING" { + t.Fatalf("task %q status=%s, want DISPATCHING", task.TaskKey, task.Status) + } + workerID := fmt.Sprintf("worker-crashed-%02d", i) + workerByTask[task.ID] = workerID + if _, err = store.LeaseTask(ctx, task.ID, workerID, 150*time.Millisecond); err != nil { + t.Fatal(err) + } + if _, err = store.StartTask(ctx, task.ID, workerID); err != nil { + t.Fatal(err) + } } - if _, err = store.StartTask(ctx, taskID, "worker-crashed"); err != nil { - t.Fatal(err) + for _, task := range run.Tasks { + waitForLeaseExpiry(t, ctx, store, task.ID) } - waitForLeaseExpiry(t, ctx, store, taskID) + recoveryStarted := time.Now() if err = service.RunOnce(ctx); err != nil { t.Fatal(err) } + recoveryDuration := time.Since(recoveryStarted) recovered, err := store.GetRun(ctx, "00000000-0000-0000-0000-000000000001", run.ID) if err != nil { t.Fatal(err) } - task := recovered.Tasks[0] - if task.Status != "RETRY_WAIT" || task.AttemptCount != 1 || task.LeaseOwner != nil || task.LeaseExpiresAt != nil { - t.Fatalf("expired lease was not recovered: %+v", task) - } - if task.AvailableAt.Before(time.Now().Add(time.Second)) { - t.Fatalf("retry backoff was not applied: available_at=%s", task.AvailableAt) - } - if _, _, err = store.HeartbeatTask(ctx, taskID, "worker-crashed", time.Minute); !errors.Is(err, storage.ErrLeaseLost) { - t.Fatalf("stale worker heartbeat error=%v, want ErrLeaseLost", err) + latestAvailability := time.Time{} + for _, task := range recovered.Tasks { + if task.Status != "RETRY_WAIT" || task.AttemptCount != 1 || task.LeaseOwner != nil || task.LeaseExpiresAt != nil { + t.Fatalf("expired lease was not recovered: %+v", task) + } + if task.AvailableAt.After(latestAvailability) { + latestAvailability = task.AvailableAt + } + staleWorker := workerByTask[task.ID] + if _, _, heartbeatErr := store.HeartbeatTask(ctx, task.ID, staleWorker, time.Minute); !errors.Is(heartbeatErr, storage.ErrLeaseLost) { + t.Fatalf("stale worker heartbeat task=%s error=%v, want ErrLeaseLost", task.ID, heartbeatErr) + } + if _, completeErr := store.CompleteTask(ctx, task.ID, staleWorker, json.RawMessage(`{"late":true}`), ""); !errors.Is(completeErr, storage.ErrLeaseLost) { + t.Fatalf("stale worker completion task=%s error=%v, want ErrLeaseLost", task.ID, completeErr) + } + assertAttempt(t, ctx, store, task.ID, 1, staleWorker, "RETRY_WAIT", "LeaseExpired") + assertOutboxEvent(t, ctx, store, task.ID, "task.retry_wait") } - assertAttempt(t, ctx, store, taskID, 1, "worker-crashed", "RETRY_WAIT", "LeaseExpired") - assertOutboxEvent(t, ctx, store, taskID, "task.retry_wait") + t.Logf("recovered %d simultaneously expired leases in %s", taskCount, recoveryDuration) - time.Sleep(time.Until(task.AvailableAt) + 25*time.Millisecond) + time.Sleep(time.Until(latestAvailability) + 25*time.Millisecond) if err = service.RunOnce(ctx); err != nil { t.Fatal(err) } - second, err := store.LeaseTask(ctx, taskID, "worker-replacement", time.Minute) - if err != nil { - t.Fatal(err) + for i, task := range recovered.Tasks { + workerID := fmt.Sprintf("worker-replacement-%02d", i) + second, leaseErr := store.LeaseTask(ctx, task.ID, workerID, time.Minute) + if leaseErr != nil { + t.Fatal(leaseErr) + } + if second.AttemptCount != 2 { + t.Fatalf("replacement attempt=%d, want 2", second.AttemptCount) + } + if _, err = store.StartTask(ctx, task.ID, workerID); err != nil { + t.Fatal(err) + } + if _, err = store.CompleteTask(ctx, task.ID, workerID, json.RawMessage(`{"recovered":true}`), ""); err != nil { + t.Fatal(err) + } } - if second.AttemptCount != first.AttemptCount+1 { - t.Fatalf("replacement attempt=%d, want %d", second.AttemptCount, first.AttemptCount+1) + completed, err := store.GetRun(ctx, "00000000-0000-0000-0000-000000000001", run.ID) + if err != nil || completed.Status != "SUCCEEDED" || len(completed.Tasks) != taskCount { + t.Fatalf("replacement worker did not complete run: run=%+v err=%v", completed, err) } - if _, err = store.StartTask(ctx, taskID, "worker-replacement"); err != nil { - t.Fatal(err) + for i, task := range completed.Tasks { + if task.Status != "SUCCEEDED" || task.AttemptCount != 2 { + t.Fatalf("recovered task final state: %+v", task) + } + assertAttempt(t, ctx, store, task.ID, 2, fmt.Sprintf("worker-replacement-%02d", i), "SUCCEEDED", "") } - if _, err = store.CompleteTask(ctx, taskID, "worker-replacement", json.RawMessage(`{"recovered":true}`), ""); err != nil { + var totalAttempts, recoveredTasks int + if err = store.Pool.QueryRow(ctx, `SELECT count(*),count(DISTINCT task_run_id) FILTER (WHERE attempt_number=2) FROM task_attempts WHERE task_run_id IN (SELECT id FROM task_runs WHERE workflow_run_id=$1)`, run.ID).Scan(&totalAttempts, &recoveredTasks); err != nil { t.Fatal(err) } - completed, err := store.GetRun(ctx, "00000000-0000-0000-0000-000000000001", run.ID) - if err != nil || completed.Status != "SUCCEEDED" || completed.Tasks[0].Status != "SUCCEEDED" { - t.Fatalf("replacement worker did not complete run: run=%+v err=%v", completed, err) + if totalAttempts != taskCount*2 || recoveredTasks != taskCount { + t.Fatalf("attempt history: total=%d recovered_tasks=%d", totalAttempts, recoveredTasks) } - assertAttempt(t, ctx, store, taskID, 2, "worker-replacement", "SUCCEEDED", "") } func TestDuplicateDispatchIsLeasedOnce(t *testing.T) { @@ -875,6 +926,214 @@ func TestTransactionalOutboxRecoversAfterBrokerOutage(t *testing.T) { } } +func TestOutboxPublishesWithoutHoldingDatabaseLocks(t *testing.T) { + ctx := context.Background() + store := postgresStore(t, ctx) + publisher := &blockingPublisher{started: make(chan struct{}), release: make(chan struct{})} + service := scheduler.Service{Store: store, Publisher: publisher} + tenantID := "00000000-0000-0000-0000-000000000001" + userID := "00000000-0000-0000-0000-000000000001" + definition, err := store.CreateWorkflow(ctx, tenantID, userID, "outbox-lock-window", workflow.DAG{Tasks: map[string]workflow.TaskSpec{"only": {Handler: "test.only"}}}) + if err != nil { + t.Fatal(err) + } + run, created, err := store.CreateRun(ctx, tenantID, userID, definition.ID, "outbox-lock-window", json.RawMessage(`{}`)) + if err != nil || !created { + t.Fatalf("create run: created=%v err=%v", created, err) + } + var eventID string + if err = store.Pool.QueryRow(ctx, `SELECT id FROM outbox_events WHERE aggregate_id=$1`, run.ID).Scan(&eventID); err != nil { + t.Fatal(err) + } + + publishDone := make(chan error, 1) + go func() { publishDone <- service.PublishOnce(ctx) }() + select { + case <-publisher.started: + case <-time.After(5 * time.Second): + t.Fatal("publisher was not called") + } + + tx, err := store.Pool.Begin(ctx) + if err != nil { + t.Fatal(err) + } + var lockedID string + if err = tx.QueryRow(ctx, `SELECT id FROM outbox_events WHERE id=$1 FOR UPDATE NOWAIT`, eventID).Scan(&lockedID); err != nil { + _ = tx.Rollback(ctx) + t.Fatalf("Kafka publish retained a PostgreSQL row lock: %v", err) + } + if err = tx.Rollback(ctx); err != nil { + t.Fatal(err) + } + close(publisher.release) + if err = <-publishDone; err != nil { + t.Fatal(err) + } + + var published bool + if err = store.Pool.QueryRow(ctx, `SELECT published_at IS NOT NULL FROM outbox_events WHERE id=$1`, eventID).Scan(&published); err != nil || !published { + t.Fatalf("event acknowledgement: published=%v err=%v", published, err) + } +} + +func TestOutboxRepublishesAfterDatabaseAcknowledgementFailure(t *testing.T) { + ctx := context.Background() + store := postgresStore(t, ctx) + _, broker := redpandaContainer(t, ctx) + topic := "outbox-ack-failure" + createTopic(t, ctx, broker, topic) + publisher := messaging.NewPublisher([]string{broker}, topic) + t.Cleanup(func() { _ = publisher.Close() }) + service := scheduler.Service{Store: store, Publisher: publisher} + tenantID := "00000000-0000-0000-0000-000000000001" + userID := "00000000-0000-0000-0000-000000000001" + definition, err := store.CreateWorkflow(ctx, tenantID, userID, "outbox-ack-failure", workflow.DAG{Tasks: map[string]workflow.TaskSpec{"only": {Handler: "test.only"}}}) + if err != nil { + t.Fatal(err) + } + run, created, err := store.CreateRun(ctx, tenantID, userID, definition.ID, "outbox-ack-failure", json.RawMessage(`{}`)) + if err != nil || !created { + t.Fatalf("create run: created=%v err=%v", created, err) + } + var eventID string + if err = store.Pool.QueryRow(ctx, `SELECT id FROM outbox_events WHERE aggregate_id=$1`, run.ID).Scan(&eventID); err != nil { + t.Fatal(err) + } + + if _, err = store.Pool.Exec(ctx, `CREATE FUNCTION reject_outbox_ack() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF OLD.published_at IS NULL AND NEW.published_at IS NOT NULL THEN RAISE EXCEPTION 'injected acknowledgement failure'; END IF; RETURN NEW; END $$`); err != nil { + t.Fatal(err) + } + if _, err = store.Pool.Exec(ctx, `CREATE TRIGGER reject_outbox_ack BEFORE UPDATE ON outbox_events FOR EACH ROW EXECUTE FUNCTION reject_outbox_ack()`); err != nil { + t.Fatal(err) + } + if err = service.PublishOnce(ctx); err == nil { + t.Fatal("publication unexpectedly acknowledged while the database rejected the update") + } + if _, err = store.Pool.Exec(ctx, `DROP TRIGGER reject_outbox_ack ON outbox_events`); err != nil { + t.Fatal(err) + } + if err = service.PublishOnce(ctx); err != nil { + t.Fatal(err) + } + + reader := kafka.NewReader(kafka.ReaderConfig{Brokers: []string{broker}, Topic: topic, Partition: 0, MinBytes: 1, MaxBytes: 1e6, StartOffset: kafka.FirstOffset}) + t.Cleanup(func() { _ = reader.Close() }) + readContext, cancelRead := context.WithTimeout(ctx, 10*time.Second) + defer cancelRead() + for i := 0; i < 2; i++ { + message, readErr := reader.FetchMessage(readContext) + if readErr != nil { + t.Fatal(readErr) + } + if got := headerValue(message.Headers, "event_id"); got != eventID { + t.Fatalf("publication %d event_id=%q, want duplicate %q", i+1, got, eventID) + } + } + var published bool + var attempts int + if err = store.Pool.QueryRow(ctx, `SELECT published_at IS NOT NULL,publish_attempts FROM outbox_events WHERE id=$1`, eventID).Scan(&published, &attempts); err != nil { + t.Fatal(err) + } + if !published || attempts != 2 { + t.Fatalf("acknowledgement recovery: published=%v publish_attempts=%d", published, attempts) + } +} + +func TestConcurrentOutboxPublishersPreserveWorkflowOrderingAcrossPartitions(t *testing.T) { + ctx := context.Background() + store := postgresStore(t, ctx) + _, broker := redpandaContainer(t, ctx) + topic := "outbox-concurrent" + createTopic(t, ctx, broker, topic, 6) + publisherA := messaging.NewPublisher([]string{broker}, topic) + publisherB := messaging.NewPublisher([]string{broker}, topic) + t.Cleanup(func() { _ = publisherA.Close() }) + t.Cleanup(func() { _ = publisherB.Close() }) + serviceA := scheduler.Service{Store: store, Publisher: publisherA, OutboxBatchSize: 1} + serviceB := scheduler.Service{Store: store, Publisher: publisherB, OutboxBatchSize: 1} + tenantID := "00000000-0000-0000-0000-000000000001" + userID := "00000000-0000-0000-0000-000000000001" + definition, err := store.CreateWorkflow(ctx, tenantID, userID, "outbox-concurrent", workflow.DAG{Tasks: map[string]workflow.TaskSpec{"only": {Handler: "test.only"}}}) + if err != nil { + t.Fatal(err) + } + const runCount = 24 + for i := 0; i < runCount; i++ { + if _, created, createErr := store.CreateRun(ctx, tenantID, userID, definition.ID, fmt.Sprintf("outbox-concurrent-%02d", i), json.RawMessage(`{}`)); createErr != nil || !created { + t.Fatalf("create run %d: created=%v err=%v", i, created, createErr) + } + } + if err = serviceA.RunOnce(ctx); err != nil { + t.Fatal(err) + } + + start := make(chan struct{}) + errorsByPublisher := make(chan error, 2) + for _, service := range []*scheduler.Service{&serviceA, &serviceB} { + go func(service *scheduler.Service) { + <-start + errorsByPublisher <- service.PublishOnce(ctx) + }(service) + } + close(start) + for i := 0; i < 2; i++ { + if publishErr := <-errorsByPublisher; publishErr != nil { + t.Fatal(publishErr) + } + } + + reader := kafka.NewReader(kafka.ReaderConfig{Brokers: []string{broker}, Topic: topic, GroupID: "outbox-concurrent-reader", MinBytes: 1, MaxBytes: 1e6}) + t.Cleanup(func() { _ = reader.Close() }) + readContext, cancelRead := context.WithTimeout(ctx, 15*time.Second) + defer cancelRead() + seenEvents := make(map[string]bool, runCount*2) + eventsByRun := make(map[string][]string, runCount) + partitionByRun := make(map[string]int, runCount) + usedPartitions := make(map[int]bool) + for i := 0; i < runCount*2; i++ { + message, readErr := reader.FetchMessage(readContext) + if readErr != nil { + t.Fatal(readErr) + } + eventID := headerValue(message.Headers, "event_id") + if seenEvents[eventID] { + t.Fatalf("event %s was published more than once", eventID) + } + seenEvents[eventID] = true + var payload struct { + WorkflowRunID string `json:"workflow_run_id"` + } + if err = json.Unmarshal(message.Value, &payload); err != nil { + t.Fatal(err) + } + if string(message.Key) != payload.WorkflowRunID { + t.Fatalf("partition key=%q workflow_run_id=%q", message.Key, payload.WorkflowRunID) + } + if previous, ok := partitionByRun[payload.WorkflowRunID]; ok && previous != message.Partition { + t.Fatalf("workflow %s moved from partition %d to %d", payload.WorkflowRunID, previous, message.Partition) + } + partitionByRun[payload.WorkflowRunID] = message.Partition + usedPartitions[message.Partition] = true + eventsByRun[payload.WorkflowRunID] = append(eventsByRun[payload.WorkflowRunID], headerValue(message.Headers, "event_type")) + } + if len(usedPartitions) < 2 { + t.Fatalf("workflow keys used only %d Kafka partition", len(usedPartitions)) + } + for runID, events := range eventsByRun { + if len(events) != 2 || events[0] != "workflow.run.created" || events[1] != "task.dispatch" { + t.Fatalf("workflow %s event order=%v", runID, events) + } + } + var unpublished, claimed int + if err = store.Pool.QueryRow(ctx, `SELECT count(*) FILTER (WHERE published_at IS NULL),count(*) FILTER (WHERE claim_token IS NOT NULL) FROM outbox_events`).Scan(&unpublished, &claimed); err != nil { + t.Fatal(err) + } + if unpublished != 0 || claimed != 0 { + t.Fatalf("outbox after concurrent publication: unpublished=%d claimed=%d", unpublished, claimed) + } +} + func TestTransactionalOutboxRecoversAfterKafkaLatency(t *testing.T) { ctx := context.Background() store := postgresStore(t, ctx) @@ -1460,18 +1719,38 @@ func kafkaProxy(t *testing.T, ctx context.Context, broker *redpanda.Container) ( return net.JoinHostPort(host, port), proxy } -func createTopic(t *testing.T, ctx context.Context, broker, topic string) { +func createTopic(t *testing.T, ctx context.Context, broker, topic string, partitions ...int) { t.Helper() + partitionCount := 1 + if len(partitions) > 0 { + partitionCount = partitions[0] + } connection, err := kafka.DialContext(ctx, "tcp", broker) if err != nil { t.Fatal(err) } defer connection.Close() - if err = connection.CreateTopics(kafka.TopicConfig{Topic: topic, NumPartitions: 1, ReplicationFactor: 1}); err != nil { + if err = connection.CreateTopics(kafka.TopicConfig{Topic: topic, NumPartitions: partitionCount, ReplicationFactor: 1}); err != nil { t.Fatal(err) } } +type blockingPublisher struct { + started chan struct{} + release chan struct{} + once sync.Once +} + +func (p *blockingPublisher) Publish(ctx context.Context, _, _ []byte, _ map[string]string) error { + p.once.Do(func() { close(p.started) }) + select { + case <-p.release: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + func headerValue(headers []kafka.Header, key string) string { for _, header := range headers { if header.Key == key { diff --git a/tests/load/api-read.js b/tests/load/api-read.js index 801c84d..3bf1891 100644 --- a/tests/load/api-read.js +++ b/tests/load/api-read.js @@ -2,6 +2,7 @@ import http from 'k6/http'; import { check } from 'k6'; export const options = { + summaryTrendStats: ['avg', 'min', 'med', 'p(90)', 'p(95)', 'p(99)', 'max'], scenarios: { api_reads: { executor: 'constant-arrival-rate', @@ -17,9 +18,10 @@ export const options = { }; const api = __ENV.RUNMESH_API || 'http://localhost:8080'; +const path = __ENV.RUNMESH_READ_PATH || '/v1/workflows'; const token = __ENV.RUNMESH_TOKEN; export default function () { - const response = http.get(`${api}/v1/workflows`, { headers: { Authorization: `Bearer ${token}` } }); + const response = http.get(`${api}${path}`, { headers: { Authorization: `Bearer ${token}` } }); check(response, { 'authenticated request succeeded': value => value.status === 200 }); } diff --git a/tests/load/submissions.js b/tests/load/submissions.js index 67a6c30..ec869e0 100644 --- a/tests/load/submissions.js +++ b/tests/load/submissions.js @@ -1,12 +1,26 @@ import http from 'k6/http'; -import { check, sleep } from 'k6'; -import { randomUUID } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; +import { check } from 'k6'; -export const options = { scenarios: { submissions: { executor: 'constant-arrival-rate', rate: 100, timeUnit: '1s', duration: '30s', preAllocatedVUs: 100, maxVUs: 300 } }, thresholds: { http_req_duration: ['p(95)<150'], http_req_failed: ['rate<0.01'] } }; +export const options = { + summaryTrendStats: ['avg', 'min', 'med', 'p(90)', 'p(95)', 'p(99)', 'max'], + scenarios: { + submissions: { + executor: 'constant-arrival-rate', + rate: Number(__ENV.RUNMESH_RATE || 50), + timeUnit: '1s', + duration: __ENV.RUNMESH_DURATION || '30s', + preAllocatedVUs: 100, + maxVUs: 300, + }, + }, + // Latency is evidence, not a merge gate. Correctness remains blocking. + thresholds: { checks: ['rate==1'], http_req_failed: ['rate==0'] }, +}; const api = __ENV.RUNMESH_API || 'http://localhost:8080'; const workflow = __ENV.RUNMESH_WORKFLOW_ID; const token = __ENV.RUNMESH_TOKEN; export default function () { - const response = http.post(`${api}/v1/workflows/${workflow}/runs`, JSON.stringify({ input: { value: Math.random() } }), { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() } }); - check(response, { 'created': r => r.status === 201 }); sleep(0.01); + const key = `benchmark-${__VU}-${__ITER}-${Date.now()}`; + const response = http.post(`${api}/v1/workflows/${workflow}/runs`, JSON.stringify({ input: { value: Math.random() } }), { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'Idempotency-Key': key } }); + check(response, { 'created': r => r.status === 201 }); } diff --git a/web/Dockerfile b/web/Dockerfile index 22d395d..9f7a080 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,18 +1,21 @@ FROM node:22-alpine AS build WORKDIR /app COPY web/package*.json ./ -RUN npm install +RUN npm ci COPY web . -ARG VITE_DEV_AUTH=true +ARG WEB_DEVELOPMENT_MODE=true ARG VITE_OIDC_AUTHORITY=http://localhost:8180/realms/runmesh ARG VITE_OIDC_CLIENT_ID=runmesh-web -ENV VITE_DEV_AUTH=$VITE_DEV_AUTH VITE_OIDC_AUTHORITY=$VITE_OIDC_AUTHORITY VITE_OIDC_CLIENT_ID=$VITE_OIDC_CLIENT_ID -RUN npm run build +RUN VITE_DEV_AUTH="$WEB_DEVELOPMENT_MODE" \ + VITE_OIDC_AUTHORITY="$VITE_OIDC_AUTHORITY" \ + VITE_OIDC_CLIENT_ID="$VITE_OIDC_CLIENT_ID" \ + npm run build -FROM nginx:1.27-alpine +FROM nginx:1.28-alpine COPY web/nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/dist /usr/share/nginx/html -RUN chown -R nginx:nginx /var/cache/nginx /usr/share/nginx/html \ +RUN apk upgrade --no-cache libcrypto3 libssl3 \ + && chown -R nginx:nginx /var/cache/nginx /usr/share/nginx/html \ && sed -i 's/user nginx;/user nginx;/' /etc/nginx/nginx.conf \ && sed -i 's|pid .*;|pid /tmp/nginx.pid;|' /etc/nginx/nginx.conf USER 101:101 diff --git a/web/demo/capture.spec.ts b/web/demo/capture.spec.ts new file mode 100644 index 0000000..1250036 --- /dev/null +++ b/web/demo/capture.spec.ts @@ -0,0 +1,73 @@ +import http from 'node:http' +import { expect, test } from '@playwright/test' +import { apiURL, authorization, createRun, createWorkflow, login, token, waitForRun } from '../e2e/helpers' + +type Container = { Id: string; Names: string[] } + +function dockerRequest(method: string, path: string): Promise { + return new Promise((resolve, reject) => { + const request = http.request({ socketPath: '/var/run/docker.sock', method, path }, response => { + const chunks: Buffer[] = [] + response.on('data', chunk => chunks.push(Buffer.from(chunk))) + response.on('end', () => response.statusCode && response.statusCode < 300 + ? resolve(Buffer.concat(chunks)) + : reject(new Error(`Docker API ${method} ${path} returned ${response.statusCode}: ${Buffer.concat(chunks)}`))) + }) + request.on('error', reject) + request.end() + }) +} + +async function terminateWorkerProcesses(): Promise { + const raw = await dockerRequest('GET', '/containers/json') + const containers = (JSON.parse(raw.toString()) as Container[]).filter(container => + container.Names.some(name => name.includes('runmesh-worker-go') || name.includes('runmesh-worker-python'))) + await Promise.all(containers.map(container => dockerRequest('POST', `/containers/${container.Id}/kill`))) + return containers +} + +async function restartWorkerProcesses(containers: Container[]): Promise { + await Promise.all(containers.map(container => dockerRequest('POST', `/containers/${container.Id}/start`))) +} + +test('capture the real dashboard and worker lease recovery', async ({ page, request }) => { + await login(page) + await page.waitForTimeout(4_000) + await page.screenshot({ path: '../docs/demo/dashboard-overview.png', fullPage: true }) + + const created = page.waitForResponse(response => response.url().endsWith('/v1/workflows') && response.request().method() === 'POST') + await page.getByRole('button', { name: 'New workflow' }).click() + const workflowName = ((await created).json() as Promise<{ name: string }>).then(workflow => workflow.name) + await page.locator('.workflow-card').filter({ hasText: await workflowName }).getByRole('button', { name: 'Launch' }).click() + await expect(page.locator('.drawer .run-meta .status')).toHaveText('SUCCEEDED', { timeout: 20_000 }) + await page.screenshot({ path: '../docs/demo/run-detail.png' }) + await page.getByRole('button', { name: 'Close run detail' }).click() + await expect(page.getByRole('dialog')).toBeHidden() + + const bearer = await token(request) + const workflow = await createWorkflow(request, bearer, 'examples.slow', 3) + const runID = await createRun(request, bearer, workflow, { delay_ms: 5_000 }) + let running = false + for (let attempt = 0; attempt < 40; attempt++) { + const response = await request.get(`${apiURL}/v1/runs/${runID}`, { headers: authorization(bearer) }) + const run = await response.json() as { tasks?: { status: string }[] } + if (run.tasks?.some(task => task.status === 'RUNNING')) { running = true; break } + await page.waitForTimeout(250) + } + expect(running).toBeTruthy() + + await page.getByRole('button', { name: 'Overview' }).click() + await page.getByLabel('Search runs').fill(runID) + await page.getByText(runID.slice(0, 8), { exact: true }).click() + await expect(page.locator('.drawer').getByText('RUNNING').first()).toBeVisible() + const terminatedWorkers = await terminateWorkerProcesses() + expect(terminatedWorkers.length).toBeGreaterThanOrEqual(2) + await page.waitForTimeout(8_000) + await restartWorkerProcesses(terminatedWorkers) + + await waitForRun(request, bearer, runID, 'SUCCEEDED', 60_000) + await expect(page.locator('.drawer .run-meta .status')).toHaveText('SUCCEEDED', { timeout: 5_000 }) + await expect(page.getByText('Attempt 2')).toBeVisible() + await page.screenshot({ path: '../docs/demo/lease-recovery.png', fullPage: true }) + await page.waitForTimeout(15_000) +}) diff --git a/web/e2e/dashboard.spec.ts b/web/e2e/dashboard.spec.ts index b6005cf..2602f4f 100644 --- a/web/e2e/dashboard.spec.ts +++ b/web/e2e/dashboard.spec.ts @@ -10,7 +10,7 @@ test('Keycloak login, workflow execution, live updates, cancellation, and dead-l await expect(page.getByRole('heading', { name: 'Workflows' })).toBeVisible() await page.locator('.workflow-card').filter({ hasText: await workflowName }).getByRole('button', { name: 'Launch' }).click() await expect(page.getByText('RUN DETAIL')).toBeVisible() - await expect(page.locator('.drawer').getByText('SUCCEEDED').first()).toBeVisible({ timeout: 15_000 }) + await expect(page.locator('.drawer .run-meta .status')).toHaveText('SUCCEEDED', { timeout: 15_000 }) await page.locator('.drawer .icon-button').click() const bearer = await token(request) diff --git a/web/package-lock.json b/web/package-lock.json index 1a89e21..be95513 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,27 +8,28 @@ "name": "runmesh-web", "version": "0.1.0", "dependencies": { - "@vitejs/plugin-react": "latest", - "lucide-react": "latest", + "@vitejs/plugin-react": "^6.0.3", + "lucide-react": "^1.25.0", "oidc-client-ts": "3.5.0", - "react": "latest", - "react-dom": "latest" + "react": "^19.2.7", + "react-dom": "^19.2.7" }, "devDependencies": { - "@eslint/js": "latest", + "@eslint/js": "^10.0.1", "@playwright/test": "1.56.1", - "@testing-library/react": "latest", - "@types/react": "latest", - "@types/react-dom": "latest", - "eslint": "latest", - "eslint-plugin-react-hooks": "latest", - "eslint-plugin-react-refresh": "latest", - "globals": "latest", - "jsdom": "latest", - "typescript": "latest", - "typescript-eslint": "latest", - "vite": "latest", - "vitest": "latest" + "@testing-library/react": "^16.3.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitest/coverage-v8": "^4.1.10", + "eslint": "^10.7.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.7.0", + "jsdom": "^29.1.1", + "typescript": "^6.0.3", + "typescript-eslint": "^8.64.0", + "vite": "^8.1.5", + "vitest": "^4.1.10" } }, "node_modules/@asamuzakjp/css-color": { @@ -332,6 +333,16 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -1470,6 +1481,37 @@ } } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.10", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", @@ -1669,6 +1711,25 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -2291,6 +2352,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -2321,6 +2392,13 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2378,6 +2456,45 @@ "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2821,6 +2938,47 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -3285,6 +3443,19 @@ "dev": true, "license": "MIT" }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", diff --git a/web/package.json b/web/package.json index dbc4639..1f975ac 100644 --- a/web/package.json +++ b/web/package.json @@ -8,29 +8,31 @@ "build": "tsc -b && vite build", "lint": "eslint .", "test": "vitest", + "test:coverage": "vitest --run --coverage", "test:e2e": "playwright test" }, "dependencies": { - "@vitejs/plugin-react": "latest", - "lucide-react": "latest", + "@vitejs/plugin-react": "^6.0.3", + "lucide-react": "^1.25.0", "oidc-client-ts": "3.5.0", - "react": "latest", - "react-dom": "latest" + "react": "^19.2.7", + "react-dom": "^19.2.7" }, "devDependencies": { "@playwright/test": "1.56.1", - "@eslint/js": "latest", - "@testing-library/react": "latest", - "@types/react": "latest", - "@types/react-dom": "latest", - "eslint": "latest", - "eslint-plugin-react-hooks": "latest", - "eslint-plugin-react-refresh": "latest", - "globals": "latest", - "jsdom": "latest", - "typescript": "latest", - "typescript-eslint": "latest", - "vite": "latest", - "vitest": "latest" + "@vitest/coverage-v8": "^4.1.10", + "@eslint/js": "^10.0.1", + "@testing-library/react": "^16.3.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "eslint": "^10.7.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.7.0", + "jsdom": "^29.1.1", + "typescript": "^6.0.3", + "typescript-eslint": "^8.64.0", + "vite": "^8.1.5", + "vitest": "^4.1.10" } } diff --git a/web/playwright.demo.config.ts b/web/playwright.demo.config.ts new file mode 100644 index 0000000..d894a37 --- /dev/null +++ b/web/playwright.demo.config.ts @@ -0,0 +1,15 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './demo', + timeout: 90_000, + workers: 1, + reporter: 'line', + outputDir: '../docs/demo/test-output', + use: { + baseURL: 'http://localhost:3000', + video: { mode: 'on', size: { width: 1280, height: 720 } }, + viewport: { width: 1280, height: 720 }, + ...devices['Desktop Chrome'], + }, +}) diff --git a/web/scripts/forward-host.mjs b/web/scripts/forward-host.mjs new file mode 100644 index 0000000..e910219 --- /dev/null +++ b/web/scripts/forward-host.mjs @@ -0,0 +1,11 @@ +import net from 'node:net' + +for (const port of [3000, 8080, 8180, 9000]) { + net.createServer((client) => { + const upstream = net.connect(port, 'host.docker.internal') + client.on('error', () => upstream.destroy()) + upstream.on('error', () => client.destroy()) + client.pipe(upstream) + upstream.pipe(client) + }).listen(port, '127.0.0.1') +} diff --git a/web/src/App.test.tsx b/web/src/App.test.tsx index dd02d93..81fe4e1 100644 --- a/web/src/App.test.tsx +++ b/web/src/App.test.tsx @@ -1,19 +1,126 @@ -import { act } from 'react' -import { createRoot } from 'react-dom/client' +import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import App from './App' +const now = new Date().toISOString() +const run = { + id: 'run-12345678', + workflow_definition_id: 'workflow-1', + workflow_version: 3, + status: 'FAILED', + idempotency_key: 'tenant-order-42', + created_at: now, +} + +function response(body: unknown, status = 200) { + return new Response(JSON.stringify(body), { + status, + statusText: status === 401 ? 'Unauthorized' : 'OK', + headers: { 'Content-Type': 'application/json' }, + }) +} + describe('operations dashboard', () => { - let container: HTMLDivElement beforeEach(() => { - container = document.createElement('div') - document.body.appendChild(container) - vi.stubGlobal('fetch', vi.fn(async () => ({ ok: true, status: 200, json: async () => ({ items: [] }) }))) - }) - afterEach(() => { container.remove(); vi.unstubAllGlobals() }) - it('renders the live operations overview', async () => { - await act(async () => { createRoot(container).render() }) - expect(container.textContent).toContain('Your workflows, moving.') - expect(container.textContent).toContain('No executions yet') + vi.stubGlobal('fetch', vi.fn(async (input: string | URL | Request) => { + const path = String(input) + if (path.endsWith('/v1/runs')) return response({ items: [run] }) + return response({ items: [] }) + })) + }) + + afterEach(() => { + cleanup() + vi.restoreAllMocks() + vi.unstubAllGlobals() + }) + + it('moves from the loading state to the live operations overview', async () => { + render() + expect(screen.getByText('Loading dashboard')).toBeTruthy() + expect(await screen.findByText('Your workflows, moving.')).toBeTruthy() + expect(screen.getByText('run-1234')).toBeTruthy() + expect(screen.getByText('Live updates reconnecting')).toBeTruthy() + }) + + it('filters executions by a tenant-scoped idempotency key', async () => { + render() + await screen.findByText('run-1234') + fireEvent.change(screen.getByLabelText('Search runs'), { target: { value: 'missing' } }) + expect(screen.getByText('No matching executions')).toBeTruthy() + fireEvent.change(screen.getByLabelText('Search runs'), { target: { value: 'order-42' } }) + expect(screen.getByText('run-1234')).toBeTruthy() + }) + + it('shows task dependencies, worker identity, and failed attempt history', async () => { + vi.mocked(fetch).mockImplementation(async (input: string | URL | Request) => { + const path = String(input) + if (path.endsWith('/v1/runs/run-12345678')) return response({ + ...run, + tasks: [{ + id: 'task-2', + task_key: 'notify', + handler: 'examples.notify', + status: 'DEAD', + attempt_count: 2, + maximum_attempts: 2, + available_at: now, + depends_on: ['extract'], + attempts: [{ + attempt_number: 2, + worker_id: 'python-worker-2', + scheduled_at: now, + started_at: now, + ended_at: now, + exit_status: 'FAILED', + error_type: 'TimeoutError', + error_message: 'handler exceeded deadline', + log_artifact_download_url: 'http://artifacts.example/log', + }], + }], + }) + if (path.endsWith('/v1/runs')) return response({ items: [run] }) + return response({ items: [] }) + }) + + render() + fireEvent.click(await screen.findByText('run-1234')) + expect(await screen.findByRole('dialog', { name: /run run-12345678/i })).toBeTruthy() + expect(screen.getByText('depends on extract')).toBeTruthy() + expect(screen.getByText('python-worker-2')).toBeTruthy() + expect(screen.getByText('TimeoutError: handler exceeded deadline')).toBeTruthy() + expect(screen.getByRole('link', { name: 'Log' }).getAttribute('href')).toBe('http://artifacts.example/log') + }) + + it('does not reopen a closed drawer when an older refresh finishes', async () => { + let detailRequests = 0 + let resolveRefresh!: (value: Response) => void + const pendingRefresh = new Promise((resolve) => { resolveRefresh = resolve }) + vi.mocked(fetch).mockImplementation(async (input: string | URL | Request) => { + const path = String(input) + if (path.endsWith('/v1/runs/run-12345678')) { + detailRequests += 1 + if (detailRequests >= 2) return pendingRefresh + return response({ ...run, tasks: [] }) + } + if (path.endsWith('/v1/runs')) return response({ items: [run] }) + return response({ items: [] }) + }) + + render() + fireEvent.click(await screen.findByText('run-1234')) + await screen.findByRole('dialog') + fireEvent.click(screen.getByRole('button', { name: 'Refresh dashboard' })) + await waitFor(() => expect(detailRequests).toBeGreaterThanOrEqual(2)) + fireEvent.click(screen.getByRole('button', { name: 'Close run detail' })) + resolveRefresh(response({ ...run, tasks: [] })) + await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull()) + }) + + it('renders an actionable unauthorized state', async () => { + vi.mocked(fetch).mockResolvedValue(response({ error: 'invalid token' }, 401)) + render() + await waitFor(() => expect(screen.getByRole('alert').textContent).toContain('session is no longer authorized')) + expect(screen.getByRole('button', { name: 'Sign in' })).toBeTruthy() }) }) diff --git a/web/src/App.tsx b/web/src/App.tsx index 3a42910..8c96473 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,12 +1,41 @@ import { useCallback, useEffect, useMemo, useState } from 'react' -import { Activity, AlertTriangle, Boxes, Braces, ChevronRight, CirclePlay, Clock3, GitBranch, LayoutDashboard, Plus, Radio, RefreshCw, Search, Server, Workflow as WorkflowIcon, X } from 'lucide-react' -import { api, subscribeToEvents } from './api' -import type { Run, TaskRun, Worker, Workflow } from './types' +import { + Activity, + AlertTriangle, + Boxes, + Braces, + ChevronRight, + CirclePlay, + Clock3, + GitBranch, + LayoutDashboard, + Plus, + Radio, + RefreshCw, + Search, + Server, + Workflow as WorkflowIcon, + X, +} from 'lucide-react' +import { APIError, api, subscribeToEvents } from './api' +import type { Run, TaskAttempt, TaskRun, Worker, Workflow } from './types' type Page = 'Overview' | 'Workflows' | 'Workers' | 'Dead letter' -const nav: [Page, typeof Activity][] = [['Overview', LayoutDashboard], ['Workflows', WorkflowIcon], ['Workers', Server], ['Dead letter', AlertTriangle]] +type DashboardError = { message: string; status?: number } + +const nav: [Page, typeof Activity][] = [ + ['Overview', LayoutDashboard], + ['Workflows', WorkflowIcon], + ['Workers', Server], + ['Dead letter', AlertTriangle], +] const terminal = new Set(['SUCCEEDED', 'FAILED', 'CANCELLED']) +function dashboardError(value: unknown): DashboardError { + if (value instanceof APIError) return { message: value.message, status: value.status } + return { message: value instanceof Error ? value.message : 'Unable to reach the control plane' } +} + export default function App() { const [page, setPage] = useState('Overview') const [workflows, setWorkflows] = useState([]) @@ -14,17 +43,37 @@ export default function App() { const [workers, setWorkers] = useState([]) const [dead, setDead] = useState([]) const [selected, setSelected] = useState(null) - const [error, setError] = useState('') + const [error, setError] = useState(null) const [busy, setBusy] = useState(false) + const [loading, setLoading] = useState(true) + const [query, setQuery] = useState('') const [streamConnected, setStreamConnected] = useState(false) const selectedId = selected?.id const refresh = useCallback(async () => { try { - const [wf, rs, wk, dl] = await Promise.all([api.workflows(), api.runs(), api.workers(), api.dead()]) - setWorkflows(wf.items); setRuns(rs.items); setWorkers(wk.items.filter(worker => Date.now() - Date.parse(worker.last_seen_at) < 30_000)); setDead(dl.items); setError('') - if (selectedId) setSelected(await api.run(selectedId)) - } catch (e) { setError(e instanceof Error ? e.message : 'Unable to reach control plane') } + const [workflowResult, runResult, workerResult, deadResult] = await Promise.all([ + api.workflows(), + api.runs(), + api.workers(), + api.dead(), + ]) + setWorkflows(workflowResult.items) + setRuns(runResult.items) + setWorkers(workerResult.items.filter((worker) => Date.now() - Date.parse(worker.last_seen_at) < 30_000)) + setDead(deadResult.items) + if (selectedId) { + const detail = await api.run(selectedId) + // A refresh that started before the drawer closed must not reopen it, + // and an older response must not replace a newly selected run. + setSelected((current) => current?.id === selectedId ? detail : current) + } + setError(null) + } catch (value) { + setError(dashboardError(value)) + } finally { + setLoading(false) + } }, [selectedId]) useEffect(() => { @@ -36,47 +85,105 @@ export default function App() { refreshTimer = window.setTimeout(() => void refresh(), 150) }, setStreamConnected) return () => { - clearTimeout(initial); clearInterval(fallback); unsubscribe() - if (refreshTimer !== undefined) clearTimeout(refreshTimer) + window.clearTimeout(initial) + window.clearInterval(fallback) + unsubscribe() + if (refreshTimer !== undefined) window.clearTimeout(refreshTimer) } }, [refresh]) - const create = async () => { setBusy(true); try { await api.createSample(); await refresh(); setPage('Workflows') } catch (e) { setError(String(e)) } finally { setBusy(false) } } - const launch = async (id: string) => { setBusy(true); try { const run = await api.launch(id); setSelected(await api.run(run.id)); await refresh() } catch (e) { setError(String(e)) } finally { setBusy(false) } } - const stats = useMemo(() => ({ active: runs.filter(r => !terminal.has(r.status)).length, succeeded: runs.filter(r => r.status === 'SUCCEEDED').length, failed: runs.filter(r => r.status === 'FAILED').length }), [runs]) + + const perform = async (operation: () => Promise) => { + setBusy(true) + try { + await operation() + setError(null) + } catch (value) { + setError(dashboardError(value)) + } finally { + setBusy(false) + } + } + const create = () => perform(async () => { + await api.createSample() + await refresh() + setPage('Workflows') + }) + const launch = (id: string) => perform(async () => { + const run = await api.launch(id) + setSelected(await api.run(run.id)) + await refresh() + }) + const openRun = (run: Run) => perform(async () => setSelected(await api.run(run.id))) + const replay = (id: string) => perform(async () => { + await api.replay(id) + await refresh() + }) + + const stats = useMemo(() => ({ + active: runs.filter((run) => !terminal.has(run.status)).length, + succeeded: runs.filter((run) => run.status === 'SUCCEEDED').length, + failed: runs.filter((run) => run.status === 'FAILED').length, + }), [runs]) + const visibleRuns = useMemo(() => { + const needle = query.trim().toLowerCase() + if (!needle) return runs + return runs.filter((run) => `${run.id} ${run.idempotency_key} ${run.status}`.toLowerCase().includes(needle)) + }, [query, runs]) return
-

OPERATIONS / {page.toUpperCase()}

{page}

- {error &&
{error}
} -
- {page === 'Overview' && setSelected(await api.run(r.id))}/>} - {page === 'Workflows' && } - {page === 'Workers' && } - {page === 'Dead letter' && { await api.replay(id); await refresh() }}/>} +

OPERATIONS / {page.toUpperCase()}

{page}

{page === 'Overview' && }
+ {error &&
{error.status === 401 ? 'Your session is no longer authorized. Sign in again to continue.' : error.message}{error.status === 401 && }
} +
+ {loading ? : <> + {page === 'Overview' && void openRun(run)}/>} + {page === 'Workflows' && void launch(id)}/>} + {page === 'Workers' && } + {page === 'Dead letter' && void replay(id)}/>} + }
- {selected && setSelected(null)} onCancel={async () => { await api.cancel(selected.id); setSelected(await api.run(selected.id)); await refresh() }}/>} + {selected && setSelected(null)} onCancel={() => void perform(async () => { await api.cancel(selected.id); setSelected(await api.run(selected.id)); await refresh() })}/>}
} -function Overview({ stats, runs, workers, workflows, onRun }: { stats: {active:number;succeeded:number;failed:number}; runs:Run[]; workers:Worker[]; workflows:Workflow[]; onRun:(r:Run)=>void }) { - return <>
LIVE CONTROL PLANE

Your workflows, moving.

At-least-once delivery with leases, retries, and complete attempt history.

+function LoadingState() { + return
Loading dashboard{Array.from({ length: 5 }, (_, index) => )}
+} + +function Overview({ stats, runs, workers, workflows, onRun }: { stats: {active:number;succeeded:number;failed:number}; runs:Run[]; workers:Worker[]; workflows:Workflow[]; onRun:(run:Run)=>void }) { + return <>
LIVE CONTROL PLANE

Your workflows, moving.

At-least-once delivery with leases, retries, and complete attempt history.

} -function Stat({icon:Icon,label,value,detail,tone}:{icon:typeof Activity;label:string;value:number;detail:string;tone?:string}){return
{label}
{value.toLocaleString()}{detail}
} -function PanelTitle({title,subtitle}:{title:string;subtitle:string}){return

{title}

{subtitle}

} -function RunTable({runs,onRun}:{runs:Run[];onRun:(r:Run)=>void}){if(!runs.length)return ;return
RUN IDSTATUSVERSIONSTARTED
{runs.slice(0,8).map(r=>)}
} -function Workflows({workflows,onLaunch}:{workflows:Workflow[];onLaunch:(id:string)=>void}){return
{workflows.map(w=>

{w.name}

Version {w.version} · {Object.keys(w.dag.tasks).length} tasks

{Object.keys(w.dag.tasks).slice(0,4).map((key,i)=>{i>0&&}{key.slice(0,1).toUpperCase()})}
Updated {ago(w.created_at)}
)}{!workflows.length&&
}
} -function Workers({workers}:{workers:Worker[]}){return
{workers.map(w=>

{w.worker_id}

{w.handlers.join(' · ') || 'No handlers'}

{w.active_tasks} active
)}{!workers.length&&}
} -function DeadLetter({tasks,replay}:{tasks:TaskRun[];replay:(id:string)=>void}){return
{tasks.length?
TASKHANDLERATTEMPTSAVAILABLE
{tasks.map(t=>
{t.task_key}{t.handler}{t.attempt_count}/{t.maximum_attempts}{ago(t.available_at)}
)}
:}
} -function RunDrawer({run,onClose,onCancel}:{run:Run;onClose:()=>void;onCancel:()=>void}){return
} -function Status({value}:{value:string}){return {value.replace('_',' ')}} -function Empty({icon:Icon,title,text}:{icon:typeof Activity;title:string;text:string}){return

{title}

{text}

} -function ago(date:string){const seconds=Math.floor((Date.now()-Date.parse(date))/1000);if(seconds<5)return 'just now';if(seconds<60)return `${seconds}s ago`;if(seconds<3600)return `${Math.floor(seconds/60)}m ago`;if(seconds<86400)return `${Math.floor(seconds/3600)}h ago`;return `${Math.floor(seconds/86400)}d ago`} +function Stat({icon:Icon,label,value,detail,tone}:{icon:typeof Activity;label:string;value:number;detail:string;tone?:string}) { return
{label}
{value.toLocaleString()}{detail}
} +function PanelTitle({title,subtitle}:{title:string;subtitle:string}) { return

{title}

{subtitle}

} +function RunTable({runs,onRun}:{runs:Run[];onRun:(run:Run)=>void}) { if (!runs.length) return ; return
RUN IDSTATUSVERSIONSTARTED
{runs.slice(0, 8).map((run) => )}
} +function Workflows({workflows,onLaunch}:{workflows:Workflow[];onLaunch:(id:string)=>void}) { return
{workflows.map((workflow) =>

{workflow.name}

Version {workflow.version} · {Object.keys(workflow.dag.tasks).length} tasks

{Object.entries(workflow.dag.tasks).slice(0, 4).map(([key, task]) => {key.slice(0,1).toUpperCase()}{task.depends_on?.length ? {task.depends_on.length} : null})}
Updated {ago(workflow.created_at)}
)}{!workflows.length &&
}
} +function Workers({workers}:{workers:Worker[]}) { return
{workers.map((worker) =>

{worker.worker_id}

{worker.handlers.join(' · ') || 'No handlers'}

{worker.active_tasks} active
)}{!workers.length && }
} +function DeadLetter({tasks,replay}:{tasks:TaskRun[];replay:(id:string)=>void}) { return
{tasks.length ?
TASKHANDLERATTEMPTSAVAILABLE
{tasks.map((task) =>
{task.task_key}{task.handler}{task.attempt_count}/{task.maximum_attempts}{ago(task.available_at)}
)}
: }
} + +function RunDrawer({run,onClose,onCancel}:{run:Run;onClose:()=>void;onCancel:()=>void}) { + useEffect(() => { + const closeOnEscape = (event: KeyboardEvent) => { if (event.key === 'Escape') onClose() } + window.addEventListener('keydown', closeOnEscape) + return () => window.removeEventListener('keydown', closeOnEscape) + }, [onClose]) + return
+} + +function AttemptRow({attempt}:{attempt:TaskAttempt}) { + const failure = [attempt.error_type, attempt.error_message].filter(Boolean).join(': ') + return
Attempt {attempt.attempt_number}{attempt.worker_id}
{failure &&

{failure}

}{attempt.artifact_download_url && Output}{attempt.log_artifact_download_url && Log}
+} + +function Status({value}:{value:string}) { return {value.replace('_',' ')} } +function Empty({icon:Icon,title,text}:{icon:typeof Activity;title:string;text:string}) { return

{title}

{text}

} +function ago(date:string) { const seconds = Math.floor((Date.now() - Date.parse(date)) / 1000); if (seconds < 5) return 'just now'; if (seconds < 60) return `${seconds}s ago`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; return `${Math.floor(seconds / 86400)}d ago` } +function duration(start:string,end:string) { const milliseconds = Math.max(0, Date.parse(end) - Date.parse(start)); return milliseconds < 1000 ? `${milliseconds}ms` : `${(milliseconds / 1000).toFixed(1)}s` } diff --git a/web/src/api.ts b/web/src/api.ts index 6cf995a..089cef8 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -3,10 +3,16 @@ import { bearerToken } from './auth' import { runtimeConfig } from './runtime' const base = runtimeConfig.apiUrl ?? import.meta.env.VITE_API_URL ?? '' +export class APIError extends Error { + constructor(public readonly status: number, message: string) { + super(message) + this.name = 'APIError' + } +} async function request(path: string, init?: RequestInit): Promise { const token = await bearerToken() const response = await fetch(base + path, { ...init, headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}), ...init?.headers } }) - if (!response.ok) throw new Error((await response.json().catch(() => null))?.error ?? `${response.status} ${response.statusText}`) + if (!response.ok) throw new APIError(response.status, (await response.json().catch(() => null))?.error ?? `${response.status} ${response.statusText}`) return response.status === 204 ? (undefined as T) : response.json() } export const api = { diff --git a/web/src/dashboard.css b/web/src/dashboard.css new file mode 100644 index 0000000..bc44721 --- /dev/null +++ b/web/src/dashboard.css @@ -0,0 +1,226 @@ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.auth-state { + min-height: 100vh; + display: grid; + place-content: center; + gap: 8px; + padding: 24px; + text-align: center; +} + +.auth-state p { + color: var(--muted); +} + +.system-dot.reconnecting { + background: var(--amber); + box-shadow: 0 0 10px #e9ad5588; + animation: pulse 1.4s ease-in-out infinite; +} + +.loading-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; +} + +.loading-grid i { + height: 120px; + border: 1px solid var(--line); + border-radius: 9px; + background: linear-gradient(100deg, var(--panel) 20%, var(--panel2) 40%, var(--panel) 60%); + background-size: 300% 100%; + animation: shimmer 1.4s linear infinite; +} + +.loading-grid i:last-child { + grid-column: 1 / -1; + height: 260px; +} + +.error.unauthorized { + background: #302514; + border-color: #796030; + color: #ffd391; +} + +.error .quiet { + color: inherit; + border: 1px solid currentcolor; + border-radius: 6px; + padding: 6px 10px; +} + +.dag-mini span { + position: relative; +} + +.dag-mini span + span::before { + content: ''; + display: block; + width: 25px; + height: 1px; + background: #345243; +} + +.dag-mini small { + position: absolute; + right: -3px; + top: -5px; + display: grid; + place-items: center; + width: 14px; + height: 14px; + border-radius: 50%; + background: #224b36; + color: #b9f4d2; + font: 7px 'DM Mono'; +} + +.task-block { + border-bottom: 1px solid #18271f; +} + +.task-block:last-child { + border-bottom: 0; +} + +.dependencies { + font-size: 8px; + color: #7f9389; +} + +.dependencies.root { + color: #537264; +} + +.attempt-history { + margin: 0 0 12px 28px; + border-left: 1px solid #2a3d33; + padding-left: 12px; +} + +.attempt-row { + display: grid; + grid-template-columns: 62px minmax(70px, 1fr) 80px minmax(0, 1.4fr) 52px; + gap: 8px; + align-items: center; + padding: 7px 0; + color: #73877d; + font-size: 8px; +} + +.attempt-row strong { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: #9bb0a6; +} + +.attempt-detail { + display: flex; + gap: 8px; + min-width: 0; +} + +.attempt-detail a { + color: var(--green); +} + +.attempt-row p { + margin: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: #e89a9b; +} + +.attempt-row time { + text-align: right; + font-family: 'DM Mono'; +} + +.drawer { + overflow-y: auto; +} + +.icon-button:focus-visible, +.primary:focus-visible, +.launch:focus-visible, +.quiet:focus-visible, +.danger:focus-visible, +nav button:focus-visible, +.tr:focus-visible, +input:focus-visible { + outline: 2px solid var(--green); + outline-offset: 2px; +} + +@keyframes shimmer { + 0% { background-position: 100% 0; } + 100% { background-position: -100% 0; } +} + +@keyframes pulse { + 50% { opacity: 0.4; } +} + +@media (prefers-reduced-motion: reduce) { + * { + scroll-behavior: auto !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +@media (max-width: 760px) { + body { min-width: 0; } + .shell { display: block; } + .shell > aside { position: static; width: auto; height: auto; padding: 14px; } + .brand { padding-bottom: 12px; } + .workspace { display: none; } + nav { flex-direction: row; overflow-x: auto; margin: 0 -4px; } + nav button { min-width: max-content; } + .aside-bottom { padding: 12px 4px 0; margin-top: 8px; } + .aside-bottom small { display: inline; margin-left: 6px; } + main { display: block; } + header { height: auto; min-height: 86px; padding: 16px; align-items: flex-start; gap: 14px; } + .header-actions { flex-wrap: wrap; justify-content: flex-end; } + .search { display: none; } + .content { padding: 18px 14px 40px; } + .error { margin: 14px; } + .hero { height: auto; min-height: 170px; padding: 26px 22px; } + .hero h2 { font-size: 25px; } + .mesh { display: none; } + .stats { grid-template-columns: repeat(2, 1fr); } + .cards { grid-template-columns: 1fr; } + .table { overflow-x: auto; } + .tr { min-width: 650px; } + .worker { grid-template-columns: 42px minmax(120px, 1fr) 70px; } + .worker time { display: none; } + .drawer { width: 100%; padding: 20px 16px; } + .attempt-row { grid-template-columns: 58px 1fr 70px; } + .attempt-row p { grid-column: 1 / -1; } + .attempt-row time { display: none; } +} + +@media (max-width: 430px) { + header { display: block; } + .header-actions { margin-top: 14px; justify-content: flex-start; } + .stats { grid-template-columns: 1fr; } + .hero h2 { font-size: 23px; } + .primary { padding: 0 10px; } + .task-row { grid-template-columns: 24px 1fr 78px 28px; } +} diff --git a/web/src/main.tsx b/web/src/main.tsx index e429828..97e064f 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -2,15 +2,16 @@ import { StrictMode, useEffect, useState } from 'react' import { createRoot } from 'react-dom/client' import App from './App' import './styles.css' +import './dashboard.css' import { authenticate } from './auth' -function AuthenticatedApp() { +export function AuthenticatedApp() { const [ready, setReady] = useState(false) const [error, setError] = useState('') useEffect(() => { void authenticate().then(() => setReady(true)).catch(value => setError(String(value))) }, []) - if (error) return

Authentication failed

{error}

- if (!ready) return

Signing in…

+ if (error) return

Authentication failed

{error}

+ if (!ready) return

Signing in…

return } diff --git a/web/src/types.ts b/web/src/types.ts index 5b70613..96f3828 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -1,5 +1,6 @@ export type TaskSpec = { handler: string; depends_on?: string[]; maximum_attempts?: number; timeout_seconds?: number } export type Workflow = { id: string; name: string; version: number; dag: { tasks: Record }; created_at: string } -export type TaskRun = { id: string; task_key: string; handler: string; status: string; attempt_count: number; maximum_attempts: number; available_at: string } +export type TaskAttempt = { attempt_number: number; worker_id: string; scheduled_at: string; executing_at?: string; started_at: string; ended_at?: string; exit_status?: string; error_type?: string; error_message?: string; trace_id?: string; artifact_uri?: string; log_artifact_uri?: string; artifact_download_url?: string; log_artifact_download_url?: string } +export type TaskRun = { id: string; task_key: string; handler: string; status: string; attempt_count: number; maximum_attempts: number; available_at: string; lease_owner?: string; lease_expires_at?: string; depends_on?: string[]; attempts?: TaskAttempt[]; output_artifact_download_url?: string; log_artifact_download_url?: string } export type Run = { id: string; workflow_definition_id: string; workflow_version: number; status: string; idempotency_key: string; created_at: string; started_at?: string; completed_at?: string; tasks?: TaskRun[] } export type Worker = { worker_id: string; handlers: string[]; active_tasks: number; metadata: Record; last_seen_at: string } diff --git a/web/vite.config.ts b/web/vite.config.ts index 42f6b1e..6b8f689 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -4,5 +4,16 @@ import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { proxy: { '/v1': 'http://control-plane:8080', '/health': 'http://control-plane:8080' } }, - test: { environment: 'jsdom', globals: true, exclude: [...configDefaults.exclude, 'e2e/**'] }, + test: { + environment: 'jsdom', + globals: true, + exclude: [...configDefaults.exclude, 'e2e/**', 'demo/**'], + coverage: { + provider: 'v8', + include: ['src/**/*.{ts,tsx}'], + exclude: ['src/vite-env.d.ts', 'src/main.tsx'], + reporter: ['text', 'json-summary'], + thresholds: { lines: 50, functions: 50, statements: 50, branches: 45 }, + }, + }, })