perfgate-server
Centralized baseline management for teams that run benchmarks across multiple CI runners.
Why
Performance baselines live on individual CI runners. When different runners execute benchmarks, they each see different baselines -- or none at all. Promoting, versioning, and auditing baselines becomes a manual chore that does not scale.
perfgate-server is a REST API that stores baselines centrally so every CI job, repository, and team member works from the same source of truth. It ships as a single binary with built-in storage, auth, and a web dashboard.
Quick start
# Shared SQLite server
# Local single-user sandbox
Feature highlights
| Feature | Details |
|---|---|
| Storage backends | In-memory, SQLite, PostgreSQL |
| Auth | API keys (scoped to project + benchmark regex), JWT (HS256), GitHub Actions OIDC, GitLab OIDC, custom OIDC |
| Role-based access | Viewer, Contributor, Promoter, Admin |
| Web dashboard | Embedded SPA served at / with baseline, verdict, flakiness, and audit views |
| Fleet analytics | Dependency-change impact tracking and cross-project alerts |
| Verdict history | Record and query pass/warn/fail verdicts over time |
| Observability | Structured JSON logging, request IDs, /health, /metrics |
| Graceful shutdown | Handles SIGTERM / Ctrl-C cleanly |
REST API
All data endpoints live under /api/v1. The health check and dashboard are at the root.
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/health |
-- | Health check with storage status |
GET |
/metrics |
-- | Prometheus metrics |
GET |
/ |
-- | Web dashboard |
GET |
/api/v1/info |
-- | Server info and local-mode status |
POST |
/api/v1/projects/{project}/baselines |
Y | Upload a baseline |
GET |
/api/v1/projects/{project}/baselines |
Y | List baselines (filterable) |
GET |
/api/v1/projects/{project}/baselines/{bench}/latest |
Y | Get latest baseline |
GET |
/api/v1/projects/{project}/baselines/{bench}/versions/{ver} |
Y | Get specific version |
DELETE |
/api/v1/projects/{project}/baselines/{bench}/versions/{ver} |
Y | Soft-delete a version |
POST |
/api/v1/projects/{project}/baselines/{bench}/promote |
Y | Promote a version |
POST |
/api/v1/projects/{project}/verdicts |
Y | Submit a verdict |
GET |
/api/v1/projects/{project}/verdicts |
Y | List verdicts |
POST |
/api/v1/projects/{project}/decisions |
Y | Upload a performance decision |
GET |
/api/v1/projects/{project}/decisions |
Y | List performance decisions |
GET |
/api/v1/projects/{project}/decisions/latest |
Y | Get latest performance decision |
POST |
/api/v1/projects/{project}/decisions/prune |
Y | Prune old performance decisions |
GET |
/api/v1/audit |
Y | List audit events |
POST |
/api/v1/keys |
Y | Create an API key |
GET |
/api/v1/keys |
Y | List API keys |
DELETE |
/api/v1/keys/{id} |
Y | Revoke an API key |
DELETE |
/api/v1/admin/cleanup |
Y | Run artifact cleanup |
POST |
/api/v1/fleet/dependency-event |
Y | Record dependency change events |
GET |
/api/v1/fleet/alerts |
Y | List fleet-wide alerts |
GET |
/api/v1/fleet/dependency/{dep}/impact |
Y | Query dependency impact |
Authentication
Pass an API key as a Bearer token (Authorization: Bearer pg_live_<32-char-random>).
Keys are scoped to a project and optionally restricted by benchmark regex:
For GitHub Actions CI, use OIDC (--github-oidc org/repo:project-id:contributor).
GitLab OIDC and custom OIDC providers are also supported. JWT tokens (HS256)
are supported via --jwt-secret.
You can also load API-key policy documents from exactly one external source:
--api-keys-env, --api-keys-file, or --api-keys-command. These documents
may be JSON/TOML arrays or wrapped under keys / api_keys.
--api-keys-command runs through PowerShell on Windows and sh elsewhere.
Loaded policy id, role, project, optional benchmark_regex, and optional
expires_at are preserved by the runtime auth path.
Configuration
| Flag | Default | Description |
|---|---|---|
--bind |
0.0.0.0 |
Bind address |
--port |
8080 |
Port |
--storage-type |
memory |
memory, sqlite, or postgres |
--database-url |
-- | DB path (SQLite) or connection string (Postgres) |
--pg-max-connections |
10 |
Maximum PostgreSQL pool connections |
--pg-min-connections |
2 |
Minimum idle PostgreSQL pool connections |
--pg-idle-timeout |
300 |
Idle connection timeout in seconds |
--pg-max-lifetime |
1800 |
Maximum connection lifetime in seconds |
--pg-acquire-timeout |
5 |
Timeout for acquiring a pooled connection in seconds |
--pg-statement-timeout |
30 |
PostgreSQL statement timeout set on new connections in seconds |
--api-keys |
-- | role:key[:project[:benchmark_regex]] (repeatable) |
--api-keys-env |
-- | env var containing one API-key policy document |
--api-keys-file |
-- | file containing one API-key policy document |
--api-keys-command |
-- | command that prints one API-key policy document |
--github-oidc |
-- | org/repo:project_id:role (repeatable) |
--gitlab-oidc |
-- | group/project:project_id:role (repeatable) |
--oidc-provider |
-- | custom OIDC issuer/JWKS/audience mapping |
--jwt-secret |
-- | HS256 secret for JWT auth |
--no-cors |
false |
Disable CORS |
--timeout |
30 |
Request timeout (seconds) |
--log-level |
info |
trace, debug, info, warn, error |
--log-format |
json |
json or pretty |
--retention-days |
0 |
Artifact retention period; 0 disables background cleanup |
--cleanup-interval-hours |
1 |
Interval between background artifact cleanup passes |
Storage backends
| Backend | Use case | Persistence | Setup |
|---|---|---|---|
| memory | Tests / short-lived demos | None | Zero config |
| sqlite | Single-node production | Disk | --database-url ./perfgate.db |
| postgres | Multi-node / HA | Disk | --database-url postgresql://host/db |
SQLite file databases are opened with journal_mode=WAL and a 5 second
busy_timeout on every server-managed connection. This keeps readers from
blocking writers in normal single-node deployments. In-memory SQLite databases
skip WAL because SQLite reports journal_mode=memory for those connections.
PostgreSQL storage uses a sqlx connection pool. The pool pings connections
before reuse, applies statement_timeout on new connections, retries transient
connection failures, and exposes pool occupancy from /health. If storage is
unhealthy, /health includes a coarse non-secret storage.detail code while
the full storage error stays in server logs:
For artifact object storage, embedded deployments can set
ServerConfig::artifacts_url to an object_store URL such as s3://...,
gs://..., az://..., or file://.... The server binary exposes retention
cadence flags, but cleanup only runs when an artifact store is configured. When
using S3, GCS, Azure, or another managed object store, configure provider-side
lifecycle policies as the durable retention backstop and use perfgate cleanup
as application-level hygiene.
Metrics
/metrics exposes Prometheus text output for request volume/latency and the
server operations that matter when the service becomes CI-critical:
perfgate_server_requests_total
perfgate_server_request_duration_seconds
perfgate_baselines_total
perfgate_verdicts_total
perfgate_upload_failures_total
perfgate_auth_failures_total
perfgate_storage_errors_total
Library usage
use ;
async
See also: Getting Started with Baseline Server
License
MIT OR Apache-2.0