GitHub Actions Observability and Runner Metrics
GitHub Actions observability means per-job CPU, memory, disk, and queue metrics. WarpBuild collects them from an agent on every runner and exposes them by API.
Last verified:
GitHub Actions observability means measuring what the runner did while a job executed: CPU, memory, filesystem, disk I/O, and network utilization, plus the system logs sitting alongside the workflow logs. WarpBuild collects that telemetry from an agent on every runner it operates, correlates it with GitHub Actions job logs, and exposes it in two dashboard surfaces and in the HTTP API.
GitHub's own job logs tell you which step ran and how long it took. They do not tell you that a job held every vCPU near saturation for most of its run, or that it never touched more than a tenth of the 32 GB you paid for. This page covers what gets recorded, how to switch runners over, how to read the numbers, and how to query them from a script or an AI assistant.
Overview
The Observability page in WarpBuild is organized into two sections, and they answer different questions.
Recommendations answers "is this job on the right runner". It aggregates utilization across many runs of the same job and flags instances that are under-provisioned or over-provisioned against fixed thresholds.
Usage answers "what happened on this one machine". It shows the metrics and logs for an individual runner instance, including both the syslogs from the runner and the GitHub Actions logs, so a spike in disk I/O lines up with the step that caused it.
The metric hierarchy
Recommendations aggregates performance metrics in a four-level hierarchy, which matches how GitHub Actions itself is structured:
- Repository: the top-level grouping of all workflows in a repository.
- Workflow: an individual workflow file, such as
ci.ymlorrelease.yml. - Job: a specific job defined in that workflow file, such as
buildorintegration-tests. - Instance type: the runner instance type that job ran on.
That ordering matters when you go looking for waste. A repository average hides everything. One workflow inside it might be dominated by a single fan-out job that runs 200 times a day on a 32 vCPU label while using four cores. Drilling from repository to workflow to job to instance type is how you find that job instead of guessing at it.
What the agent records
The WarpBuild agent on each runner collects three things: utilization metrics, system logs from the runner, and the GitHub Actions logs used to correlate workflow execution with the machine behavior. Collection runs over OpenTelemetry on port 33931.
| Metric | Definition |
|---|---|
| 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 combined read and write disk throughput over the last 30 seconds |
| Network utilization | Maximum rolling average of combined read and write network throughput over the last 30 seconds |
Two operational details decide whether you see data at all. Observability only collects metrics and logs for jobs longer than roughly one minute, so a 20 second lint job will show nothing. And for pooled instances with collection paused, telemetry can appear in the interface before a job is allocated, because the agent initializes before any data is actually collected.
On data handling, the documentation is explicit: no sensitive data is collected through telemetry, collection is limited to syslogs and utilization metrics, everything is encrypted in transit, and none of it is used for training.
Reports, the aggregate view
The Reports page sits next to Observability and covers three sections: Billing, Jobs, and Queue Timings. Every tab supports date range selection, sorting, filtering, search, and CSV export, and the export includes every row matching the current filters rather than the current page.
The Jobs section aggregates per unique combination of repository, workflow, and job name, with run count, success rate, duration P75 and P90, queue time P75 and P90, CPU P75 and P90, and memory P75 and P90. The CPU and memory columns require Observability to be enabled; jobs without telemetry show a dash.
Observability is the piece that tells you whether the rest of it is configured correctly.
Configuration
There is nothing to install. The agent ships inside the runner image, so a job produces telemetry as soon as it runs on a WarpBuild runner. Switching a workflow over is a label change.
All four report into the same hierarchy.
name: ci
on:
pull_request:
push:
branches: [main]
jobs:
unit-tests:
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 test
integration-tests:
runs-on: warp-ubuntu-latest-x64-8x
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
ports: ["5432:5432"]
steps:
- uses: actions/checkout@v4
- run: ./scripts/integration.sh
arm-image:
runs-on: warp-ubuntu-latest-arm64-4x
steps:
- uses: actions/checkout@v4
- run: docker build --platform linux/arm64 -t app:arm64 .
ios-build:
runs-on: warp-macos-latest-arm64-6x
steps:
- uses: actions/checkout@v4
- run: xcodebuild -scheme App -destination 'generic/platform=iOS' build
windows-build:
runs-on: warp-windows-latest-x64-4x
steps:
- uses: actions/checkout@v4
- run: ./build.ps1Once that runs, the four jobs appear under ci.yml in the Recommendations hierarchy, each with its own instance type row. The integration-tests job on warp-ubuntu-latest-x64-8x gets rated separately from unit-tests on the 4x label, which is the point: sizing is a per-job decision.
BYOC and firewall rules
BYOC runs on AWS, GCP, and Azure, and the same agent runs on those instances. If you filter egress inside your own account, allow the observability channel on port 33931 alongside the control plane. Without it, jobs still run and still bill, and the CPU and memory columns in the Jobs report stay empty, which is the usual cause of a fleet that reports duration but no utilization.
API access
The API is served at https://api.warpbuild.com/api/v1, and you authenticate with an API key carrying the ci scope as a bearer token. Pull the Jobs report for one repository over the last 30 days:
curl -sS -H "Authorization: Bearer $WARPBUILD_API_KEY" \
"https://api.warpbuild.com/api/v1/reports/jobs\
?start_date=2026-07-13T00:00:00Z\
&end_date=2026-08-13T00:00:00Z\
&repos=acme/web\
&chart_metric=queue_time\
&chart_percentile=p90\
&sort_by=duration_p90&sort_order=desc&per_page=25"The response carries a chart block, a paginated table, and the filter values available for the current window. Values below are illustrative shapes rather than measurements:
{
"organization_id": "worg_1a2b3c",
"chart": {
"metric": "queue_time",
"percentile": "p90",
"bucket_interval_seconds": 3600,
"rows": [
{
"repo": "acme/web",
"workflow_name": "ci.yml",
"job_name": "integration-tests",
"points": [
{ "bucket": "2026-08-12T09:00:00Z", "value": 11.4, "job_count": 18 }
]
}
]
},
"table": {
"items": [
{
"repo": "acme/web",
"workflow_name": "ci.yml",
"job_name": "integration-tests",
"run_count": 412,
"success_rate": 96.8,
"duration_p75": 512.4,
"duration_p90": 604.1,
"queue_time_p75": 4.2,
"queue_time_p90": 11.4,
"cpu_p75": 38.5,
"cpu_p90": 44.1,
"memory_p75": 29.6,
"memory_p90": 33.2
}
],
"page": 1,
"per_page": 25,
"total_pages": 3,
"total_rows": 61
}
}Add &format=csv to the same call to get the export the dashboard download button produces.
Wiring an assistant to the data
Reading a dashboard is a person's job. Answering "which jobs regressed this week" is a query. WarpBuild hosts a Model Context Protocol server at https://mcp.warpbuild.com/mcp, so an MCP host such as Cursor can call the API directly. Generate an API key with the CI scope from the dashboard, then add the server:
{
"mcpServers": {
"warpbuild": {
"url": "https://mcp.warpbuild.com/mcp",
"headers": {
"Authorization": "Bearer <API KEY>"
}
}
}
}With that connected, the assistant can pull the Jobs report, the Queue Timings report, and the runner recommendations in one turn, and answer questions about your GitHub Actions usage without anyone opening a browser tab. The setup steps are in the MCP support docs.
Sizing
Sizing is where observability turns into a decision. The Recommendations view compares each job's observed utilization against fixed thresholds and labels the ones that are out of range.
| Metric | Threshold | Alert label |
|---|---|---|
| Max sustained CPU | >= 80% | High CPU Usage |
| Max memory utilization | >= 80% | High Memory Usage |
| Max filesystem utilization | >= 80% | High Filesystem Usage |
| Max disk I/O | >= 80% of supported throughput | High Disk IO |
Jobs above those lines are under-provisioned. Jobs sitting far below them across many runs are over-provisioned, and that is the more common finding, because runner labels get chosen once during migration and then never revisited.
The API returns the same analysis:
curl -sS -H "Authorization: Bearer $WARPBUILD_API_KEY" \
"https://api.warpbuild.com/api/v1/org_metrics/job_runner_recommendations\
?search=integration-tests&per_page=20"{
"organization_id": "worg_1a2b3c",
"recommendations": [
{
"repository": "acme/web",
"workflow_name": "ci.yml",
"job_name": "integration-tests",
"total_runs": 412,
"recommendation": {
"type": "downgrade",
"current_label": "warp-ubuntu-latest-x64-8x",
"current_vcpu": 8,
"current_memory_gb": 32,
"recommended_label": "warp-ubuntu-latest-x64-4x",
"recommended_vcpu": 4,
"recommended_memory_gb": 16,
"resources": ["cpu", "memory"],
"already_at_max_size": false
},
"runs": [
{
"runner_display_name": "warp-ubuntu-latest-x64-8x",
"runner_instance_id": "wrni_9f8e7d",
"max_sustained_cpu": 34.8,
"max_memory_utilization": 29.6,
"max_filesystem_utilization": 22.1,
"started_at": "2026-08-12T09:14:03Z",
"ended_at": "2026-08-12T09:20:31Z",
"job_link": "https://github.com/acme/web/actions/runs/1234567890/job/9876543210"
}
]
}
],
"page": 1,
"per_page": 20,
"total_pages": 1,
"total_rows": 7
}Two fields carry most of the meaning. resources says which signals drove the call, so you can tell a memory-bound job from a CPU-bound one. already_at_max_size is set when a job is already on the largest runner and still saturating it, which means a label change will not help; the remaining levers are splitting the work across more jobs, caching more of it, or moving the heavy step to a remote Docker builder.
The rate table you are choosing from
Linux x64 rates, from the WarpBuild pricing page:
| Runner label | vCPU | RAM | Storage | USD per minute |
|---|---|---|---|---|
| warp-ubuntu-latest-x64-2x | 2 | 8 GB | 150GB SSD | $0.004 |
| warp-ubuntu-latest-x64-4x | 4 | 16 GB | 150GB SSD | $0.008 |
| warp-ubuntu-latest-x64-8x | 8 | 32 GB | 150GB SSD | $0.016 |
| warp-ubuntu-latest-x64-16x | 16 | 64 GB | 150GB SSD | $0.032 |
| warp-ubuntu-latest-x64-32x | 32 | 128 GB | 150GB SSD | $0.064 |
Linux ARM64 labels carry the same vCPU and RAM shapes at $0.003, $0.006, $0.012, $0.024, and $0.048 per minute. Windows x64 runs from $0.016 at 4 vCPU to $0.128 at 32 vCPU, and the macOS ARM64 6x label is $0.08 per minute. Runners are billed on a per-minute basis, so the sizing decision and the billing unit line up.
A worked model
Take the integration-tests job above: 412 runs in 30 days, duration P90 of 604 seconds, so budget 11 minutes of billed time per run at the tail. Round the volume to 400 runs per month at 10 minutes each, which is 4,000 minutes.
| Scenario | Rate per minute | Minutes per month | Monthly cost |
|---|---|---|---|
| GitHub-hosted 8-core Linux larger runner | $0.022 | 4,000 | $88.00 |
| warp-ubuntu-latest-x64-8x | $0.016 | 4,000 | $64.00 |
| warp-ubuntu-latest-x64-4x, duration holds | $0.008 | 4,000 | $32.00 |
| warp-ubuntu-latest-x64-4x, duration grows to 13 minutes | $0.008 | 5,200 | $41.60 |
The GitHub rate of $0.022 per minute for the 8-core Linux larger runner comes from GitHub's published Actions minute multipliers reference, cross-checked against github.com/pricing, checked on 2026-08-13. WarpBuild rates come from the pricing page and were checked on 2026-08-13. Stated as a rate comparison: warp-ubuntu-latest-x64-8x (8 vCPU, 32 GB) costs $0.016 per minute against $0.022 per minute for the 8-core Linux larger runner (8 vCPU, 32 GB): 27 percent lower list price. The first two rows hold minutes equal.
Note the fourth row. A downgrade recommendation is a hypothesis about a job, and the outcome depends on how the work parallelizes. A test suite that saturates four cores for a third of its run will stretch when it loses four more. Ship the label change, wait for a week of runs, and compare duration P75 and P90 in the Jobs report against the same window before the change. The recommendation tells you where to look; the Jobs report tells you what actually happened.
Bottlenecks
Utilization tells you about the machine. Queue time tells you about everything before the machine. Both live in the same reports, and confusing them sends teams to buy larger runners for a problem that larger runners cannot fix.
Queue time
The Queue Timings section reports how long jobs waited before starting, broken down per runner label and stack, with run count, queue time P75, and queue time P90 per row, and a daily chart of average queue time next to the day's job count.
curl -sS -H "Authorization: Bearer $WARPBUILD_API_KEY" \
"https://api.warpbuild.com/api/v1/reports/queue-timings\
?start_date=2026-08-01T00:00:00Z\
&end_date=2026-08-13T00:00:00Z\
&runner_labels=warp-ubuntu-latest-x64-8x\
&sort_by=queue_time_p90&sort_order=desc&format=csv"Read the daily chart against the job count series. Queue time that climbs with volume points at capacity or at workflow configuration. Queue time that is flat and high across all volumes usually points at a label that no runner matches, or at a workflow-level concurrency group serializing runs that could go wide.
A queue that spikes only during the morning push wave is worth checking against your own concurrency groups and matrix limits before anyone changes runner labels, and measuring and reducing GitHub Actions queue times walks through that sequence.
CPU saturation
max_sustained_cpu at or above 80 percent with a duration P90 well above P75 is the classic under-provisioned pattern: the job finishes fine most of the time and falls off a cliff when the machine is busy. Move up one label and re-measure.
Memory pressure
Memory shows up differently. A job that reaches high memory utilization often fails outright rather than running slowly, because the kernel kills the process. If a job's failure rate in the Jobs report moves together with max_memory_utilization in the recommendation payload, size for memory rather than cores. The label families step vCPU and RAM together, so moving from warp-ubuntu-latest-x64-4x to warp-ubuntu-latest-x64-8x takes RAM from 16 GB to 32 GB.
Disk I/O and filesystem
High disk I/O with low CPU is usually a caching problem. Dependency installs, Docker layer pulls, and artifact unpacks all show as sustained read and write throughput with idle cores. Filesystem utilization near 80 percent on a 150GB SSD points at artifacts left behind inside a job rather than at a sizing issue.
Network
Sustained network throughput with idle cores means the job is waiting on downloads: base images, packages, or artifacts. That is a caching and image question. Artifact-heavy deployment pipelines are covered on the enterprise zero egress page.
Correlating with logs
When a metric spike needs an explanation, the Usage view is where you get it, because it shows syslogs from the runner and the GitHub Actions logs for the same instance side by side. For failures that need a live machine rather than a chart, the Action Debugger opens an SSH session on the runner mid-workflow, which is covered in debugging failing GitHub Actions jobs.
Proof
SonarQube runs its GitHub Actions fleet on WarpBuild BYOC runners in its own AWS account. Their setup leans on dashboards for visibility into job and queue metrics, and on custom automation built against the WarpBuild API for image updates and runner configuration changes. Read the full write-up on the SonarQube case study.
The agent that produces this telemetry is open source and published at github.com/WarpBuilds/warpbuild-agent, so the collection path is inspectable rather than a black box. The Action Debugger referenced above is also an open source GitHub Action, as are the WarpBuild setup actions that handle dependency caching.
For teams that need the data under access control, SSO is a flat $250 per month, whatever the user count, listed on the pricing page.
Three companion guides go deeper than this page does:
- Collect and read GitHub Actions runner metrics covers each utilization metric and how to interpret a single instance timeline.
- Debug failing GitHub Actions jobs on the runner covers the path from a red check to a live SSH session on the machine that failed.
- Connect the WarpBuild MCP server to an AI assistant covers the full MCP host setup and the questions an assistant can answer from this data.
FAQ
What does GitHub Actions observability measure that the workflow logs do not?
Workflow logs record which step ran and how long it took. Runner observability records what the machine did while those steps ran: CPU, memory, filesystem, disk I/O, and network utilization, plus the system logs from the runner itself. WarpBuild collects all five utilization metrics through an OpenTelemetry agent on port 33931 and shows them next to the GitHub Actions logs for the same job.
Does collecting runner telemetry read my source code or secrets?
No. The WarpBuild documentation states that no sensitive data is collected through telemetry, that collection is limited to syslogs and utilization metrics for CPU, memory, filesystem, and network, that all observability data is encrypted in transit, and that no data is used for training. Collection can also be paused per organization, which stops system logs and GitHub Actions log collection as well.
How do I decide when to move a job to a bigger or smaller runner?
Read the Recommendations view or call GetJobRunnerRecommendations. It flags a job as under-provisioned when max sustained CPU, max memory utilization, or max filesystem utilization reaches 80 percent, and it flags over-provisioned jobs from consistently low utilization. Change one label, then compare duration P75 and P90 in the Jobs report before and after, because a smaller runner can run longer and offset the lower per-minute rate.
Can an AI assistant answer questions about my GitHub Actions usage directly?
Yes. WarpBuild hosts a Model Context Protocol server at https://mcp.warpbuild.com/mcp. Generate an API key with the CI scope, add the server URL and an Authorization Bearer header to your MCP host, and the assistant can call the same reports and recommendation endpoints the dashboard uses.
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.