Define and track platform engineering success metrics using SLOs, developer experience measurements, and adoption analytics. Covers platform reliability targets, deployment velocity tracking, developer satisfaction frameworks, and proving platform ROI to leadership.
Internal platforms deliver software capabilities to engineering teams. To ensure they remain valuable, teams must measure what matters: not just system health, but the actual experience of developers using the platform. Platform Metrics and SLOs: Measuring What Matters for Internal Platforms is the practice of defining, collecting, and acting on observability data that answer: *Is the platform enabling developers to build, deploy, and operate software effectively?* This page guides platform engineers through the process of building a measurement system that identifies when the platform succeeds — and when it fails — for its users.
SLOs (Service Level Objectives) define the expected behavior of a platform component: how fast, reliable, and available it must be. For internal platforms, SLOs are not just about uptime of a service, but about the *outcomes* developers experience. The foundation of platform SLOs is the platform service contract: a formal agreement between the platform team and internal users, specifying expected behavior.
To define an SLO, begin by identifying a *critical developer workflow*. For example: "Deploy a service to production via the platform’s CI/CD pipeline."
The SLOs for this workflow are:
Each SLO is expressed as a target and a service level. For instance:
deployments:
time-to-first-deploy:
target: 24h
service-level: 95%
metric: "deployment.time-to-first-deploy"
threshold: "p95"
deployment-success-rate:
target: 98%
service-level: 99%
metric: "deployment.success-rate"
threshold: "p99"
mttr:
target: 30m
service-level: 90%
metric: "deployment.mttr"
threshold: "p90"
The metric field references a named time-series metric. These metrics are collected via the platform’s observability stack — typically Prometheus, OpenTelemetry, or a custom logging pipeline.
SLOs are only effective when they trigger actions. Use a SLO alerting engine — a component that ingests metrics and evaluates SLOs against thresholds.
A typical alerting configuration uses a query language like PromQL or SQL. For the deployment.success-rate SLO, the alerting engine evaluates:
# Alert: Deployment Success Rate Below Target
rate(deployment_success_total{job="platform-ci-cd"}[1h])
/
rate(deployment_total{job="platform-ci-cd"}[1h])
< 0.98
This query computes the success rate over the last hour and raises an alert if it falls below 98%. The alert is evaluated every 5 minutes.
When an SLO breach occurs, the system sends a notification via:
@platform-ops SLO breach: deployment-success-rate < 98%critical.The alert includes a root cause diagnosis:
{
"slo": "deployment-success-rate",
"current_value": 0.95,
"target": 0.98,
"window": "1h",
"failed_deployments": [
{
"id": "d12345",
"commit_sha": "a1b2c3d",
"started_at": "2023-10-05T14:22:10Z",
"finished_at": "2023-10-05T14:25:30Z",
"status": "failed",
"error": "Failed to build image: exit code 1",
"logs": [
"Building image from Dockerfile...",
"ERROR: failed to build image: exit code 1",
"Step 1/5: RUN npm install",
"npm ERR! code ERESOLVE",
"npm ERR! ERESOLVE unable to resolve dependency tree"
]
}
]
}
This JSON is ingested by the platform’s incident management system, which surfaces the failure to the team responsible.
Platform metrics are the data that feed SLOs. They must be *developer-centric*, not just infrastructure-centric.
Each major workflow has a set of metrics that define success.
The CI/CD pipeline is the heart of platform delivery. Key metrics include:
pipeline.duration: end-to-end time from commit to production.pipeline.build-time: time to build artifacts.pipeline.test-time: time to run tests.pipeline.deployment-time: time to deploy to a target environment.These are collected via the platform’s CI system (e.g., GitHub Actions, GitLab CI, or a custom platform-ci service). Each step in the pipeline emits structured logs:
{
"event": "pipeline.step.started",
"step": "build",
"job_id": "j123",
"started_at": "2023-10-05T14:22:10Z",
"input": {
"branch": "feature/new-auth",
"commit_sha": "a1b2c3d"
}
}
{
"event": "pipeline.step.completed",
"step": "test",
"job_id": "j123",
"finished_at": "2023-10-05T14:24:45Z",
"duration": 135,
"status": "passed",
"output": {
"test_count": 142,
"failures": 3,
"coverage": 87.5
}
}
These events are stored in a time-series database (e.g., InfluxDB, TimescaleDB, or Prometheus) and used to compute SLOs.
When developers request a new environment, the platform creates a Kubernetes namespace, configures databases, sets up CI/CD, and deploys a staging app.
Metrics collected:
env.provisioning.time: time from request to ready-to-use environment.env.provisioning.success-rate: percentage of successful environment setups.env.provisioning.cost: cost of the environment per day (based on resource usage).The environment provisioning service emits:
{
"event": "env.provisioning.started",
"request_id": "r789",
"user": "[email protected]",
"type": "staging",
"region": "us-west-2",
"requested_at": "2023-10-05T14:10:00Z"
}
{
"event": "env.provisioning.completed",
"request_id": "r789",
"environment_id": "e456",
"ready_at": "2023-10-05T14:15:30Z",
"duration": 330,
"status": "success",
"resources": [
{
"type": "k8s.namespace",
"name": "staging-feature-new-auth",
"created_at": "2023-10-05T14:12:40Z"
},
{
"type": "db.instance",
"name": "db-staging-feature-new-auth",
"endpoint": "postgres://db-staging-1.company.com:5432/staging"
}
]
}
When an environment fails to provision, the platform logs:
{
"event": "env.provisioning.failed",
"request_id": "r789",
"error": "Failed to create database instance: timeout after 60s",
"details": {
"error_code": "DB_TIMEOUT",
"retry_count": 3,
"last_attempt": {
"request": "POST /api/v1/db-instances",
"response": {
"status": 504,
"body": "Gateway Timeout"
}
}
}
}
This data is used to compute SLOs and to triage recurring issues.
SLOs are not monolithic. A platform may serve multiple teams, each with different expectations.
To track SLOs across teams, use SLO tagging:
# platform/slos.yaml
deployments:
time-to-first-deploy:
target: 24h
service-level: 95%
metric: "deployment.time-to-first-deploy"
threshold: "p95"
tags:
team: "frontend"
stack: "react"
region: "us-west-2"
Each SLO is associated with a set of tags. The platform aggregates SLOs by tag, allowing teams to view their own performance.
The SLO dashboard queries the metric database:
SELECT
team,
stack,
avg(time_to_first_deploy) as avg_time,
count(*) as total_deployments,
sum(case when time_to_first_deploy <= 86400 then 1 else 0 end) as on_time
FROM deployments
WHERE started_at >= now() - interval '1 week'
GROUP BY team, stack
ORDER BY avg_time DESC;
This query is run every 15 minutes and stored in a materialized view. The result feeds a SLO dashboard accessible at /slos, which shows:
deployment.success-rate over the last 7 days.When a team requests a new environment, the platform automatically tags the deployment with:
team: frontendstack: reactregion: us-east-1This enables cross-team analysis: the platform team can now answer: *Which team has the slowest environment provisioning?*
SLO breaches are not just alerts — they are events that trigger actions.
When an SLO breach is detected, the system:
The incident includes a SLO root cause analysis section:
## SLO Breach: Deployment Success Rate < 98%
- **SLO**: deployment.success-rate
- **Current**: 95.3%
- **Target**: 98%
- **Window**: 1h
- **Threshold**: p99
### Root Cause
The failure is correlated with `pipeline.test-time`:
```promql
rate(test_failure_total{job="platform-ci-cd"}[1h]) / rate(test_total{job="platform-ci-cd"}[1h])
This query shows a spike in test failures during the same period. The platform’s logs reveal:
> ERROR: test suite failed: Timeout after 300s > > 2023-10-05T14:20:00Z | Test suite: integration-tests > 2023-10-05T14:20:00Z | Timeout: 300s > 2023-10-05T14:20:00Z | Test: API endpoint /v1/users returns empty list > 2023-10-05T14:20:00Z | Error: database connection timeout > > 2023-10-05T14:20:00Z | Stack trace: > at src/integration-tests/api.test.ts:15 > at src/integration-tests/api.test.ts:18 > at src/integration-tests/api.test.ts:22 > > 2023-10-05T14:20:00Z | DB logs: > ERROR: connection timeout: 5000ms > Connection to db-main-1.company.com failed after 5000ms
The incident team can now investigate the database, which is often shared across services.
---
## The Feedback Loop: From SLOs to Platform Evolution
SLOs are not static. They evolve over time.
Each SLO has a **SLO owner** — a team or individual responsible for maintaining it. The owner reviews SLO reports monthly and makes decisions:
- **Adjust SLO targets**: If `time-to-first-deploy` consistently exceeds 24 hours, the team may revise the target to 36 hours.
- **Add new SLOs**: After observing that developers frequently wait for environment provisioning, the team adds `env.provisioning.time` as a new SLO.
- **Refine metrics**: When `deployment.success-rate` shows inconsistent results, the team adds `deployment.failure-reason` to the event schema.
The SLO evolution is documented in `platform/slos.yaml`. Changes are versioned and reviewed via pull requests.
When a new SLO is added, the platform automatically:
- Registers the metric in the observability system.
- Configures alerting.
- Updates the SLO dashboard.
This creates a self-sustaining feedback loop: **platform SLOs drive platform improvement**, and improved platforms deliver better SLOs.
---
## Common Pitfalls and Sharp Edges
- **SLOs are only as good as their data**. If `pipeline.build-time` is recorded only at the start and end of a build, but not during intermediate steps, the metric is inaccurate. Use **distributed tracing** to capture the full timeline.
- **SLO thresholds are often misapplied**. Using `p95` for `deployment.success-rate` but computing `p90` in the dashboard leads to confusion. Standardize threshold calculation across all teams.
- **SLO alerts are frequently ignored**. A common failure: the `deployment.success-rate` alert fires, but no one checks the logs. The solution: embed a **log snippet** directly in the alert.
- **SLOs become stale**. Teams define an SLO, but after 6 months, the target is no longer relevant. Use **SLO drift detection**: compare current SLO performance against historical baselines. When `time-to-first-deploy` drops below its 6-month average, raise a **SLO trend alert**.
- **Tagging is inconsistent**. Some teams tag environments with `team: frontend`, others use `team: Frontend` or `team: Front-End`. Enforce **canonical tag values** via a shared configuration:
```yaml
# platform/tags.yaml
canonical:
team:
- frontend
- backend
- mobile
- data
stack:
- react
- node
- go
- python
This ensures that SLO aggregations are correct and meaningful.
The result: a platform that is not only stable, but *responsive* — continuously improving based on the actual experience of its users.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy