forgejo-runner-optimiser/README.md

189 lines
5.1 KiB
Markdown
Raw Normal View History

# Forgejo Runner Resource Collector
A lightweight resource metrics collector designed to run alongside CI/CD workloads in shared PID namespace environments. It collects CPU and memory metrics, groups them by container/cgroup, and pushes summaries to a receiver service.
## Components
- **Collector**: Gathers system and per-process metrics at regular intervals, computes run-level statistics, and pushes a summary on shutdown.
- **Receiver**: HTTP service that stores metric summaries in SQLite and provides a query API.
## Receiver API
### POST `/api/v1/metrics`
Receives metric summaries from collectors.
### GET `/api/v1/metrics/repo/{org}/{repo}/{workflow}/{job}`
Retrieves all stored metrics for a specific workflow and job.
**Example request:**
```
GET /api/v1/metrics/repo/my-org/my-repo/ci.yml/build
```
**Example response:**
```json
[
{
"id": 1,
"organization": "my-org",
"repository": "my-org/my-repo",
"workflow": "ci.yml",
"job": "build",
"run_id": "run-123",
"received_at": "2026-02-06T14:30:23.056Z",
"payload": {
"start_time": "2026-02-06T14:30:02.185Z",
"end_time": "2026-02-06T14:30:22.190Z",
"duration_seconds": 20.0,
"sample_count": 11,
"cpu_total_percent": { ... },
"mem_used_bytes": { ... },
"mem_used_percent": { ... },
"top_cpu_processes": [ ... ],
"top_mem_processes": [ ... ],
"containers": [
{
"name": "runner",
"cpu_cores": {
"peak": 2.007,
"p99": 2.005,
"p95": 2.004,
"p75": 1.997,
"p50": 1.817,
"avg": 1.5
},
"memory_bytes": {
"peak": 18567168,
"p99": 18567168,
"p95": 18567168,
"p75": 18567168,
"p50": 18567168,
"avg": 18567168
}
}
]
}
}
]
```
## Understanding the Metrics
### CPU Metrics
There are two different CPU metric formats in the response:
#### 1. System and Process CPU: Percentage (`cpu_total_percent`, `peak_cpu_percent`)
These values represent **CPU utilization as a percentage** of total available CPU time.
- `cpu_total_percent`: Overall system CPU usage (0-100%)
- `peak_cpu_percent` (in process lists): Per-process CPU usage where 100% = 1 full CPU core
#### 2. Container CPU: Cores (`cpu_cores`)
**Important:** The `cpu_cores` field in container metrics represents **CPU usage in number of cores**, not percentage.
| Value | Meaning |
|-------|---------|
| `0.5` | Half a CPU core |
| `1.0` | One full CPU core |
| `2.0` | Two CPU cores |
| `2.5` | Two and a half CPU cores |
This allows direct comparison with Kubernetes resource limits (e.g., `cpu: "2"` or `cpu: "500m"`).
**Example interpretation:**
```json
{
"name": "runner",
"cpu_cores": {
"peak": 2.007,
"avg": 1.5
}
}
```
This means the "runner" container used a peak of ~2 CPU cores and averaged 1.5 CPU cores during the run.
### Memory Metrics
All memory values are in **bytes**:
- `mem_used_bytes`: System memory usage
- `memory_bytes` (in containers): Container RSS memory usage
- `peak_mem_rss_bytes` (in processes): Process RSS memory
### Statistical Fields
Each metric includes percentile statistics across all samples:
| Field | Description |
|-------|-------------|
| `peak` | Maximum value observed |
| `p99` | 99th percentile |
| `p95` | 95th percentile |
| `p75` | 75th percentile |
| `p50` | Median (50th percentile) |
| `avg` | Arithmetic mean |
## Configuration
### Collector Environment Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `GITHUB_REPOSITORY_OWNER` | Organization name | `my-org` |
| `GITHUB_REPOSITORY` | Full repository path | `my-org/my-repo` |
| `GITHUB_WORKFLOW` | Workflow filename | `ci.yml` |
| `GITHUB_JOB` | Job name | `build` |
| `GITHUB_RUN_ID` | Unique run identifier | `run-123` |
| `CGROUP_PROCESS_MAP` | JSON mapping process names to container names | `{"node":"runner"}` |
| `CGROUP_LIMITS` | JSON with CPU/memory limits per container | See below |
**CGROUP_LIMITS example:**
```json
{
"runner": {"cpu": "2", "memory": "1Gi"},
"sidecar": {"cpu": "500m", "memory": "256Mi"}
}
```
CPU values support Kubernetes notation: `"2"` = 2 cores, `"500m"` = 0.5 cores.
Memory values support: `Ki`, `Mi`, `Gi`, `Ti` (binary) or `K`, `M`, `G`, `T` (decimal).
### Receiver Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `DB_PATH` | SQLite database path | `metrics.db` |
| `LISTEN_ADDR` | HTTP listen address | `:8080` |
## Running
### Docker Compose (stress test example)
```bash
docker compose -f test/docker/docker-compose-stress.yaml up -d
# Wait for metrics collection...
docker compose -f test/docker/docker-compose-stress.yaml stop collector
# Query results
curl http://localhost:9080/api/v1/metrics/repo/test-org/test-org%2Fstress-test/stress-test-workflow/heavy-workload
```
### Local Development
```bash
# Build
go build -o collector ./cmd/collector
go build -o receiver ./cmd/receiver
# Run receiver
./receiver --listen=:8080 --db=metrics.db
# Run collector
./collector --interval=2s --top=10 --push-endpoint=http://localhost:8080/api/v1/metrics
```