OpenTelemetry Metrics from GitHub Actions Runners
GitHub Actions reports run duration and nothing about the machine. The WarpBuild agent emits runner metrics over OpenTelemetry, grouped per job and instance.
GitHub Actions publishes run duration, queue time, job counts, and conclusion (GitHub Actions metrics), and none of those numbers describe the machine the job ran on, so utilization data has to come from an agent on the runner emitting OpenTelemetry. On runners WarpBuild operates that agent is already installed: it reports CPU, memory, filesystem, disk I/O, and network utilization over OpenTelemetry on port 33931, along with the runner's system logs and the GitHub Actions logs for the same job.
This guide covers exactly what the agent emits and the hierarchy the platform groups it under, the two collection controls that decide whether data exists at all, a worked read of one job's metric series against its step timeline, and the per minute arithmetic for the label change that read implies.
Diagnosis
A job that takes fourteen minutes reports one number. That number can be a compiler pinned on four cores, a test runner swapping against a memory ceiling, a checkout saturating the volume, or several minutes of queue wait, and the four have different fixes. Telemetry from the machine is what separates them.
What the agent emits
The agent collects three kinds of data, described in the observability documentation: utilization metrics, system logs that capture WarpBuild and other service behavior on the runner, and the GitHub Actions logs that let a metric spike be correlated with the step that caused it. Five utilization series are recorded per runner instance.
| Metric | What the agent records |
|---|---|
| CPU utilization | Maximum rolling average CPU usage percentage over the last 30 seconds |
| Memory utilization | Maximum memory usage percentage |
| Filesystem utilization | Maximum storage usage percentage |
| Disk I/O | Maximum rolling average of read plus write disk throughput over the last 30 seconds |
| Network utilization | Maximum rolling average of read plus write network throughput over the last 30 seconds |
The rolling average is the detail that changes how you read the CPU line. A three second spike inside a compile burst does not lift the sustained figure, so a job whose peak sits at 40 percent has real headroom even when the log looks busy.
The hierarchy it is grouped under
Metrics are aggregated in four levels, which mirror how GitHub Actions is organized:
- Repository: every workflow in one repository.
- Workflow: an individual workflow file, such as
ci.ymlorrelease.yml. - Job: a job defined in that file, such as
buildorintegration-tests. - Instance type: the runner instance type the job actually landed on.
Start at the top and drill down. A repository average hides the one fan-out job running two hundred times a day on a large label while touching four cores, and the instance type level is where matrix jobs give themselves away, because a matrix frequently lands on a size nobody chose deliberately.
Every runner WarpBuild operates carries the same agent. The series for a Windows job and the series for a Linux job arrive in the same views under the same four levels.
Fix
The read that turns the series into a decision is always the same: put the metric line next to the step timeline for one run, find the plateau, and name the step under it.
Take an integration-tests job in ci.yml running on warp-ubuntu-latest-x64-4x (4 vCPU, 16 GB), wall clock 14 minutes 20 seconds. The Usage view shows the utilization charts and both log streams for that single instance, so the elapsed times below come from the GitHub Actions log and the peaks come from the metric series over the same window. The numbers are an illustrative run.
| Elapsed | Step in the GitHub Actions log | Peak CPU | Peak memory | Peak disk I/O |
|---|---|---|---|---|
| 0:00 to 0:35 | Set up job, actions/checkout@v4 | 22 percent | 7 percent | 180 MB/s |
| 0:35 to 2:10 | actions/setup-node@v4, npm ci | 61 percent | 19 percent | 240 MB/s |
| 2:10 to 3:05 | docker compose up for postgres and redis | 35 percent | 28 percent | 90 MB/s |
| 3:05 to 12:40 | npm run test:integration | 99 percent | 91 percent | 12 MB/s |
| 12:40 to 14:20 | Upload coverage artifact | 24 percent | 33 percent | 70 MB/s |
Four things fall out of that table.
One step owns the job. The test step holds 9 minutes 35 seconds of a 14 minute 20 second run. Any work spent on checkout or install returns at most a few seconds, so the install caching ticket can wait.
CPU and memory hit the ceiling together. 99 percent CPU alongside 91 percent memory for the whole plateau means the machine has nothing left on either axis. Sizes in the catalog scale vCPU and RAM together, so the next step up the same family, warp-ubuntu-latest-x64-8x at 8 vCPU and 32 GB, is the move.
Disk is only busy at the ends. The 240 MB/s peak during npm ci and the 180 MB/s peak during checkout confirm those phases are I/O bound and short. A job whose disk line stayed high through the middle would be a different diagnosis, and a bigger CPU label would not touch it.
A memory-only ceiling is a different fix. If the memory line sat at 91 percent while CPU idled in the twenties, the lever is the worker or shard count inside the test runner rather than the label, because more cores would sit unused next to the same ceiling.
Once a job is flagged, the aggregate views tell you whether the single run was representative. The reports documentation covers the Jobs report, which carries run count, success rate, and P75 and P90 for duration, queue time, CPU, and memory per unique repository, workflow, and job name. The full walkthrough of those percentiles lives in the guide to runner level metrics, and GitHub Actions observability with WarpBuild covers the surfaces around it.
Configuration
Two collection controls decide whether any of this exists for a given job.
Pausing collection. Observability data collection can be paused. While paused, no telemetry is collected from the runners, and that includes the system logs and the GitHub Actions logs as well as the utilization metrics. There is no per-stream opt-out, so a job that must run with collection off runs with all three streams off. On pooled instances with collection paused, the agent still initializes before a job is allocated, which is why an instance can appear in the interface before any data is recorded for it.
The one minute floor. Observability only collects metrics and logs for jobs longer than roughly one minute. A 25 second lint job produces no series at all, which is the usual reason a job shows a dash where CPU and memory should be.
Port usage. Collection and communication with the platform run over port 33931, using OpenTelemetry as the transport. That port is open by default on runners WarpBuild operates. On BYOC runners, confirm that egress on 33931 is allowed from the subnet the runners launch in, otherwise the jobs succeed while the Usage view stays empty.
The label change the worked read implies is one line per job:
name: ci
on:
pull_request:
jobs:
build:
runs-on: warp-ubuntu-latest-x64-4x
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run build
integration-tests:
needs: build
runs-on: warp-ubuntu-latest-x64-8x
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: docker compose up -d postgres redis
- run: npm run test:integrationPublishing the same image for a second architecture uses the ARM64 labels from the same catalog, and the agent behaves identically on them:
build-arm:
runs-on: warp-ubuntu-latest-arm64-8x
steps:
- uses: actions/checkout@v4
- run: make release ARCH=arm64If you want the metric series for a job that does not run on WarpBuild infrastructure, the standard supports that directly. OpenTelemetry covers the collector, the OTLP endpoint, and the resource attributes that join a metric series to a run id, and how to see CPU and memory usage for a job is the short version of the same question.
Cost or Time Model
Linux x64 rates from the WarpBuild pricing page, billed per minute:
| Label | vCPU | RAM | USD per minute |
|---|---|---|---|
warp-ubuntu-latest-x64-2x | 2 | 8 GB | $0.004 |
warp-ubuntu-latest-x64-4x | 4 | 16 GB | $0.008 |
warp-ubuntu-latest-x64-8x | 8 | 32 GB | $0.016 |
warp-ubuntu-latest-x64-16x | 16 | 64 GB | $0.032 |
warp-ubuntu-latest-x64-32x | 32 | 128 GB | $0.064 |
Assumptions. Rates are the published per minute rates above. The job runs 400 times a month at the 14.33 minute duration from the worked read. Queue time is excluded because it is not billed. The post-change duration is an assumption to check against the Jobs report after the label moves rather than a measured result.
Today. 400 runs x 14.33 minutes = 5,732 minutes on warp-ubuntu-latest-x64-4x at $0.008 = $45.86 a month.
After the move. Assume the test step scales with cores and duration lands at 8.5 minutes. 400 x 8.5 = 3,400 minutes on warp-ubuntu-latest-x64-8x at $0.016 = $54.40 a month. That is $8.54 more per month and returns 5.83 minutes per run, which is 2,332 minutes of wall clock a month handed back to whoever is waiting on the pull request.
The number to check first. The dollar break even sits at 7.2 minutes: at 400 runs, the 8x label costs the same as today's bill once duration falls to 7.2 minutes, and anything below that lowers the bill as well as the clock. If the new P90 comes back at 11 minutes rather than 8.5, the upgrade bought time at $32.14 more per month, and that is a decision rather than a mistake.
Two structural notes for anyone building this into a budget. Every cost statement here carries its rate, its run count, and the date the rates were checked, which is 2026-08-13.
FAQ
What OpenTelemetry data does a WarpBuild runner emit?
Three streams from an agent on the machine: CPU, memory, filesystem, disk I/O, and network utilization metrics, system logs from the runner, and the GitHub Actions logs used to line the metrics up with the step that produced them. Collection runs over OpenTelemetry on port 33931 and the results are grouped by repository, then workflow, then job, then instance type.
Why is there no telemetry for some of my jobs?
Observability collects metrics and logs only for jobs longer than about one minute, so a short lint job produces no series. Collection can also be paused, and pausing stops metrics, system logs, and GitHub Actions logs together rather than one stream at a time. On pooled instances with collection paused, the agent still initializes before a job is allocated, so an entry can appear in the interface before any data is recorded.
Do I have to run my own OpenTelemetry collector to get runner metrics?
Not on runners WarpBuild operates. The agent is already on the machine and reports to the platform on port 33931, and the metrics appear in the Usage view next to the same job's logs. On BYOC runners the agent behaves the same way, so the item to check is that egress on port 33931 is allowed from the subnet the runners launch in, otherwise jobs run normally while the Usage view stays empty.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.