Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CONTRIBUTING.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,9 +28,10 @@ docs/ Methodology, ADRs, style guide

1. **Open an issue** with the [📊 Propose a benchmark template](https://github.com/OpenChainBench/OpenChainBench/issues/new?template=new-benchmark.yml). Sketch the metric, providers, methodology, hosting plan — get feedback before you write code. The issue lands in the `Requested` column of the roadmap.
2. **Write the spec.** Drop a YAML at `benchmarks/<slug>.yml`. The format is described in [`benchmarks/README.md`](./benchmarks/README.md) and validated by `src/lib/spec-schema.ts`.
3. **Build the harness** at `harnesses/<slug>/`. Anything that pushes Prometheus metrics with the labels your spec references. See [`harnesses/README.md`](./harnesses/README.md) for the contract and the existing Go harnesses as reference implementations.
4. **Open a PR** referencing the issue (`Closes #N`). CI runs `pnpm validate` (schema lint), `pnpm typecheck`, `pnpm lint`, and `pnpm build`. Once green and reviewed, merge → site picks up the spec on next ISR cycle (≤60 s).
5. **Wire the harness on Railway** (maintainer task). Light harnesses run on the project's shared Railway. Heavier harnesses (wallets, signing) are hosted by the contributor and push to a publicly-reachable Prom endpoint.
3. **Build the harness** at `harnesses/<slug>/`. A harness is a data producer only — it exposes `/metrics` over HTTP with the metric names and labels your spec references. See [`harnesses/README.md`](./harnesses/README.md) for the full contract and the existing Go harnesses as reference implementations.
4. **Append a scrape job** to [`infrastructure/prometheus/prometheus.yml`](./infrastructure/prometheus/prometheus.yml) so the shared Prometheus picks up your harness. Format documented in [`infrastructure/README.md`](./infrastructure/README.md).
5. **Open a PR** referencing the issue (`Closes #N`). CI runs `pnpm validate` (schema lint), `pnpm typecheck`, `pnpm lint`, and `pnpm build`. Once green and reviewed, merge → site picks up the spec on next ISR cycle (≤60 s).
6. **Wire the harness on Railway** (maintainer task). Light harnesses run on the project's shared Railway. Heavier harnesses (wallets, signing) are hosted by the contributor and expose `/metrics` on a publicly-reachable URL — the central Prometheus scrapes it identically.

## Local development

Expand Down
61 changes: 34 additions & 27 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,11 +16,13 @@ benchmarks/ Spec files — one YAML per published benchmark
└── README.md Spec format reference + submission guide

harnesses/ The runners that produce the metrics
├── aggregator-head-lag/ Go service: WebSocket monitor for indexation lag
├── bridge-quote/ Go service: 4-bridge quote latency + fees
├── bridge-fee/ Go service: cost-percent comparison
├── aggregator-head-lag/ Go service: WebSocket monitor (exposes :2112/metrics)
├── bridge-monitor/ Go service: 4-bridge quote loop + execution (:9090/metrics)
└── README.md Contract for new harnesses

infrastructure/ Shared services every harness depends on
└── prometheus/ Single shared Prometheus that scrapes all harnesses

src/ Next.js 16 site (App Router, ISR, Tailwind v4)
├── app/ Pages — overview, benchmarks index, [slug] reports
├── components/ time-series-chart, ledger-table, region-grid, …
Expand All@@ -34,34 +36,38 @@ docs/ Methodology, ADRs, style guide
## How a benchmark gets data

```
[harness] ──── push metrics ────▶ [Prometheus]
▲ │
│ │ PromQL queries
│ runs 24/7 on Railway │ (defined in YAML)
│ ▼
│ [benchmarks/<slug>.yml]
│ │
│ │ resolved server-side
│ │ at request time
│ ▼
│ [Next.js site] ── ISR 60s
│ │
│ ▼
│ openchainbench.xyz/benchmarks/<slug>
└──── source code lives in harnesses/<slug>/, deployed from this repo
Railway (OpenChainBench project)
┌───────────────────────────────────────────────────────────┐
│ │
│ harnesses/aggregator-head-lag → :2112/metrics ─┐ │
│ harnesses/bridge-monitor → :9090/metrics ─┼─▶ prometheus
│ harnesses/<future> → :????/metrics ─┘ (scrape every 15s,
│ 365d retention)
└────────────────────────────────────────┬──────────────────┘
│ HTTPS public URL
│ /api/v1/query
┌─────────────────────┐
│ Next.js site │
│ on Vercel │
│ ISR 60s │
└──────────┬──────────┘
openchainbench.xyz/benchmarks/<slug>
```

The harness is the source of truth: it calls real provider endpoints, measures latency / cost / success, and pushes Prometheus metrics with the labels declared in the spec. The site never fakes numbers — if the harness stops emitting, the affected percentiles disappear from the page rather than fall back to placeholders.
The harnesses are data producers — they call real provider endpoints, measure latency / cost / success, and expose Prometheus metrics on `/metrics`. A single shared Prometheus scrapes every harness over Railway's internal DNS and aggregates everything into one queryable instance. The Next.js site queries that single Prometheus URL declared in each YAML spec via the standard Prometheus HTTP API (`/api/v1/query` and `/api/v1/query_range`); ISR caches the response on Vercel's edge for 60 s.

## Architecture

| Layer | Where it runs | Why |
|---|---|---|
| Site (Next.js, ISR) | Vercel | Static pages with 60s revalidate, edge cache |
| Prometheus | Railway | Time-series DB, 24/7 |
| Prometheus | Railway | Single shared instance scraping every harness |
| Harnesses (Go) | Railway | Long-running WebSockets, schedulers, on-chain signing |

Vercel and Railway are intentionally split: Vercel can't host long-lived WebSocket connections or sign on-chain transactions; Railway can't serve a globally cached Next.js site at the same cost. They communicate over HTTPS — the site queries Prom URLs declared in each YAML spec.
Vercel and Railway are intentionally split: Vercel can't host long-lived WebSocket connections or sign on-chain transactions; Railway can't serve a globally cached Next.js site at the same cost. They communicate over HTTPS — the site queries the shared Prometheus URL declared in each YAML spec.

## Running the site locally

Expand All@@ -80,26 +86,27 @@ pnpm build # production build

## Running a harness locally

Each harness has its own README with run instructions. They are independent Go programs (one per benchmark) that you can build with `go run ./cmd/...` or via the included Dockerfile.
Each harness is a standalone Go binary that exposes `/metrics` on a documented port (`:2112` for aggregator, `:9090` for bridge). They have no Prometheus / Grafana dependencies — that lives in [`infrastructure/`](./infrastructure/) and is shared.

```bash
cd harnesses/aggregator-head-lag
cp .env.example .env # fill in API keys
docker-compose up -d # local Prom + monitor + Grafana
go run ./cmd/script/ # or: docker build -t hh . && docker run -p 2112:2112 hh
```

Set `prom_url` in the corresponding YAML to your local Prom (`http://localhost:9090`) to render the site against your own data.
To render the site against your local harness, run a local Prometheus scraping `localhost:<port>` (the [`infrastructure/prometheus/README.md`](./infrastructure/prometheus/README.md) has notes) and point the corresponding YAML's `prom_url` at it.

## Adding a benchmark

Full guide in [CONTRIBUTING.md](./CONTRIBUTING.md). Short version:

1. **Open an issue** with the [📊 Propose a benchmark template](https://github.com/OpenChainBench/OpenChainBench/issues/new?template=new-benchmark.yml). Sketch the metric, providers, methodology — get feedback before you build. Want to brainstorm first? Use [Discussions → Ideas](https://github.com/OpenChainBench/OpenChainBench/discussions/categories/ideas) instead.
2. **Write the spec** at `benchmarks/<slug>.yml`. Format documented in [`benchmarks/README.md`](./benchmarks/README.md), validated by `src/lib/spec-schema.ts`.
3. **Build the harness** in `harnesses/<slug>/`. Any language works as long as it pushes Prometheus metrics with the labels your spec references. See the existing harnesses as reference.
4. **Open a PR.** CI runs schema validation, typecheck, lint, and build. Once green and merged: the site picks up the new spec automatically; a maintainer wires the harness into Railway (one-time setup per benchmark).
3. **Build the harness** in `harnesses/<slug>/`. Any language works as long as it exposes `/metrics` over HTTP with the metric names and labels your spec references. The harness is a data producer only — no Prometheus, Grafana, or Alertmanager packaging.
4. **Add a scrape entry** to `infrastructure/prometheus/prometheus.yml` so the shared Prometheus picks up your harness.
5. **Open a PR.** CI runs schema validation, typecheck, lint, and build. Once merged: the site picks up the new spec automatically; a maintainer creates the Railway service for the harness and redeploys Prometheus to apply the new scrape job.

Hosting trade-off: light harnesses (one HTTP poll loop, no secrets) can be deployed onto the OpenChainBench Railway. Harnesses that hold wallets, sign transactions, or otherwise represent capital must run from infra owned by the contributor — they push metrics to a public Prom endpoint and the site queries it the same way.
Hosting trade-off: light harnesses (one HTTP poll loop, no secrets) are deployed onto the OpenChainBench Railway. Harnesses that hold wallets, sign transactions, or otherwise represent capital are hosted by the contributor — they expose `/metrics` on a publicly-reachable URL and the central Prometheus scrapes it the same way as project-hosted harnesses.

## Editorial conventions

Expand Down
31 changes: 15 additions & 16 deletions harnesses/README.md
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,50 @@
# Harnesses

Each subdirectory holds the runner for one or more benchmarks — the long-running process that calls providers, measures latency / cost / success, and pushes metrics to Prometheus. The OpenChainBench site queries that same Prometheus and renders the data.
Each subdirectory holds the runner for one or more benchmarks — the long-running process that calls providers, measures latency / cost / success, and exposes Prometheus metrics on `/metrics`. The shared OpenChainBench Prometheus (see [`/infrastructure/prometheus`](../infrastructure/prometheus)) scrapes those endpoints, and the site queries Prometheus over HTTPS.

```
harnesses/
├── aggregator-head-lag/ Bench № 001 — WebSocket monitor (Go)
└── bridge-monitor/ Bench № 002 + № 003 — quote loop + execution (Go)
├── aggregator-head-lag/ Bench № 001 — WebSocket monitor (Go), exposes :2112/metrics
└── bridge-monitor/ Bench № 002 + № 003 — quote loop + execution (Go), exposes :9090/metrics
```

A single harness can serve multiple benchmarks when the same set of measurements is consumed by more than one spec — `bridge-monitor` is the canonical example, producing both `bridge_quote_latency_ms` (read by `bridge-quote-latency.yml`) and `bridge_cost_percent` (read by `bridge-fee.yml`).

## Contract

A harness can be written in any language. The contract it must satisfy is small:
A harness is a **data producer**, nothing more. It must:

| Concern | Requirement |
| --- | --- |
| Inputs | Read provider API keys / wallet keys from env vars, never commit them |
| Loop | Run continuously and push the same metric set every iteration |
| Loop | Run continuously and update the same metric set every iteration |
| Metric names | Match the names referenced in the matching `benchmarks/<slug>.yml` exactly |
| Labels | Include `provider` (or equivalent) and `region` at minimum; chain/route labels encouraged |
| Push target | Scrape endpoint exposed for Prometheus, or push to a remote-write endpoint |
| Endpoint | Expose `/metrics` over HTTP on a documented port (e.g. `:2112`, `:9090`). No auth — the central Prometheus scrapes over Railway's internal network |
| Timeouts | Documented; failures fail closed (counted toward success rate, excluded from latency aggregates) |
| Reproducibility | README explains how to run it locally with one command |
| Reproducibility | README explains how to run locally with one command |
| License | MIT, same as the rest of the repo |

## Subdirectory layout
A harness does **not** ship its own Prometheus, Grafana, Alertmanager, or `docker-compose.yml`. The shared infrastructure handles all of that.

A harness is expected to ship at minimum:
## Subdirectory layout

```
harnesses/<slug>/
├── README.md What it measures, providers, labels, env vars, how to run
├── README.md What it measures, providers, labels, env vars, port
├── Dockerfile Container image for the runner
├── .env.example Every env var the runner reads, with placeholders
└── … Source files in whatever language fits
```

If the harness ships a full local stack (Prometheus + Grafana + Alertmanager) it should include a `docker-compose.yml` and a `Makefile` for the common targets (`make run`, `make logs`, `make stop`). The two existing harnesses do.

## Hosting

Two paths exist for getting a harness into production:

1. **OpenChainBench Railway.** Light harnesses (one HTTP loop, no wallets, no signing) can be deployed onto the project's shared Railway. A maintainer wires the service after the PR is merged.
2. **Contributor-hosted.** Harnesses that hold wallets, sign transactions, or otherwise represent capital must run from infrastructure owned by the contributor. They push metrics to a publicly-reachable Prometheus endpoint and the site queries it the same way as the project-hosted harnesses.
1. **OpenChainBench Railway.** Light harnesses (one HTTP loop, no wallets, no signing) are deployed onto the project's shared Railway. A maintainer creates the service after the PR is merged and adds a corresponding scrape entry in [`infrastructure/prometheus/prometheus.yml`](../infrastructure/prometheus/prometheus.yml).
2. **Contributor-hosted.** Harnesses that hold wallets, sign transactions, or otherwise represent capital must run from infrastructure owned by the contributor. They expose `/metrics` on a publicly-reachable URL and the central Prometheus scrapes it the same way as project-hosted harnesses (the scrape job uses a public hostname instead of `*.railway.internal`).

Either way the YAML spec's `prom_url` decides which Prometheus the site reads from — the data path is identical.
Either way the YAML spec's `prom_url` always points to the shared OpenChainBench Prometheus — the contributor's hosting choice is invisible to the site.

## Submitting a new harness

Expand All@@ -55,4 +53,5 @@ See [`/CONTRIBUTING.md`](../CONTRIBUTING.md) for the full submission flow. Short
1. Open an issue with the new-benchmark template (sketch the metric, providers, methodology).
2. Write the spec at `benchmarks/<slug>.yml`.
3. Build the harness here at `harnesses/<slug>/`.
4. Open a PR.
4. Append a scrape entry to `infrastructure/prometheus/prometheus.yml`.
5. Open a PR.
Loading
Loading