Skip to main content
DevOps · lesson 6 of 8
DevOps intermediate Lesson 6 of 8

Computing DORA Metrics From Your Own Git History

Compute all four DORA metrics — deployment frequency, lead time for changes, change failure rate, and MTTR — from real git log and tag data instead of a dashboard.

By SkillByExample · Published

devops dora-metrics git

Four numbers, one git log, no dashboard.

A repo with a deploy history

Four commits and four annotated deploy tags, one of them marked failed, built with explicit timestamps so the metrics below come out to round numbers:

# .scratch/build_dora_repo.py (excerpt)
commit("feature A: add checkout retry", "2026-01-01T09:00:00")
tag("v1", "deploy v1", "2026-01-02T09:00:00", failed=False)

commit("feature B: new pricing table", "2026-01-03T09:00:00")
tag("v2", "deploy v2", "2026-01-04T09:00:00", failed=True)

commit("hotfix: revert pricing table bug", "2026-01-04T15:00:00")
tag("v3", "deploy v3", "2026-01-04T18:00:00", failed=False)

commit("feature C: add search filters", "2026-01-06T09:00:00")
tag("v4", "deploy v4", "2026-01-07T09:00:00", failed=False)

Each deploy is a real annotated tag with a [status:success] or [status:FAILED] marker in its message — a convention, not a git feature, but enough for a script to parse:

$ git for-each-ref --format='%(refname:short) %(creatordate:iso-strict) %(contents:subject)' refs/tags
v1 2026-01-02T09:00:00+05:30 deploy v1 [status:success]
v2 2026-01-04T09:00:00+05:30 deploy v2 [status:FAILED]
v3 2026-01-04T18:00:00+05:30 deploy v3 [status:success]
v4 2026-01-07T09:00:00+05:30 deploy v4 [status:success]

Deployment frequency

Deploys per week, from the span between the first and last deploy tag:

# .scratch/compute_dora.py (excerpt)
span_days = (tags[-1][1] - tags[0][1]).total_seconds() / 86400
deploys_per_week = len(tags) / span_days * 7
print(f"Deployment frequency: {len(tags)} deploys over {span_days:.1f} days = {deploys_per_week:.2f}/week")
Deployment frequency: 4 deploys over 5.0 days = 5.60/week

Lead time for changes

For each deploy, find the earliest commit made since the previous deploy, and measure how long it waited before shipping:

lead_times = []
prev_deploy_time = commits[0][1]
for name, deploy_time, _ in tags:
    new_commits = [c for c in commits if prev_deploy_time <= c[1] < deploy_time or c[1] == prev_deploy_time]
    first_new = min((c[1] for c in new_commits), default=deploy_time)
    lead_hours = (deploy_time - first_new).total_seconds() / 3600
    lead_times.append(lead_hours)
    prev_deploy_time = deploy_time
Lead time for changes: ['24.0h', '24.0h', '3.0h', '24.0h'] -> avg 18.75h

The 3-hour outlier is the hotfix — committed at 15:00 and deployed at 18:00 the same day, dragging the average down from what three ordinary 24-hour cycles alone would show. Lead time is sensitive to exactly this kind of urgent, fast-tracked change, which is part of why it’s tracked as an average rather than read from any single deploy.

Change failure rate

A direct read of the [status:FAILED] markers:

failed = [t for t in tags if t[2]]
print(f"Change failure rate: {len(failed)}/{len(tags)} = {len(failed) / len(tags):.1%}")
Change failure rate: 1/4 = 25.0%

MTTR

For each failed deploy, the gap until the next deploy — the point at which the failure was actually resolved in production:

restore_times = []
for i, (name, deploy_time, is_failed) in enumerate(tags):
    if is_failed and i + 1 < len(tags):
        restore_hours = (tags[i + 1][1] - deploy_time).total_seconds() / 3600
        restore_times.append(restore_hours)
MTTR: ['9.0h'] -> avg 9.00h

v2 deployed at 09:00 and broke pricing; v3 — the hotfix deploy — landed at 18:00 the same day. Nine hours from a failed deploy to its fix is the entire MTTR calculation, read straight off two tag timestamps.

What the numbers say together

MetricValueReading it
Deployment frequency5.60/weekShipping roughly every 1.25 days
Lead time for changes18.75h avgMost changes ship within a day of being committed
Change failure rate25%1 in 4 deploys broke something in this window
MTTR9.00hThe one failure took under half a day to resolve

None of these numbers means anything alone — a 25% failure rate next to a 9-hour MTTR reads very differently than the same 25% next to a 9-day MTTR. DORA’s own research groups performance by combinations of all four, not any single metric in isolation.

Practice

1. Add a fifth deploy, v5, three days after v4, also successful. Recompute deployment frequency.

Add commit("feature D: ...", "2026-01-09T09:00:00") and tag("v5", "deploy v5", "2026-01-10T09:00:00", failed=False) to build_dora_repo.py, rebuild the repo, and rerun compute_dora.py. The span grows to 8 days across 5 deploys, giving 5 / 8 * 7 = 4.375/week — adding a slower deploy at the end pulls the weekly rate down even though total deploy count went up, since frequency is about pace, not raw count.

2. Mark v4 as failed too, with no fix deploy after it. What does the MTTR calculation do?

Change tag("v4", ..., failed=True) and rerun. The MTTR loop’s if is_failed and i + 1 < len(tags) guard means v4 (the last tag, with no tag after it) contributes nothing to restore_times — an unresolved failure with no subsequent deploy is invisible to this MTTR calculation, which is a real limitation: it can only measure incidents that have already been fixed by a later deploy, not ones still ongoing.

3. Change v2's failure marker to `[status:FAILED-partial]` instead of `[status:FAILED]`. What happens to the change failure rate?

Since the failure check is "FAILED" in subject, FAILED-partial still contains the substring FAILED, so it’s still counted — the change failure rate stays at 25%. This is worth noticing as a real fragility: the parser matches on a substring, not an exact status value, so a differently-formatted marker could either be silently miscounted or silently ignored depending on wording.

4. Compute lead time using each deploy's *latest* new commit instead of its earliest. How do the numbers change?

Replace min((c[1] for c in new_commits), ...) with max(...). For v1, v2, and v4 — each with exactly one commit in their window — the answer doesn’t change. For v3, whose window only contains the hotfix commit itself, it also doesn’t change. The two calculations only diverge when a single deploy window contains multiple commits, which none of these four windows do — a good reason to add a second commit to one window and see the two calculations actually produce different numbers.

5. What lead time would this repo show if `feature B` and `feature C` were both committed on 2026-01-01 (batched), with deploys still on their original dates?

Change feature B’s commit date to "2026-01-01T09:00:00" — the same day as feature A — and rerun. v2’s lead time doesn’t rise to reflect the longer real wait; it drops to 0.0h. The script’s window filter only looks for commits at-or-after the previous deploy’s timestamp (2026-01-02), so a commit dated before that boundary falls outside every window and min(..., default=deploy_time) silently falls back to the deploy time itself. This is a real bug the naive script has: it can’t attribute a commit to the deploy that shipped it if that commit predates the window it’s searching, and it fails silently (a suspiciously-low 0.0h) rather than raising an error — worth checking for in any lead-time script before trusting its output.

Frequently Asked Questions

What are the four DORA metrics?
Deployment frequency (how often you ship to production), lead time for changes (time from a commit to it running in production), change failure rate (the percentage of deployments that cause a failure), and MTTR — mean time to restore service after a failure. They come from the DevOps Research and Assessment team's research correlating these four measurements with organizational performance.
Do I need a dashboard tool like LinearB or Sleuth to measure DORA metrics?
No — all four numbers can be computed directly from git log and tags if your team tags every production deploy and marks failed ones, which is exactly what this lesson's script does. Dashboard tools add visualization, alerting, and integration with incident systems, but the underlying computation is ordinary git history analysis.
How is lead time for changes different from deployment frequency?
Deployment frequency counts how often you ship, independent of what's in each deploy. Lead time for changes measures the delay between a specific commit being made and that commit's code reaching production — a team can deploy daily (high frequency) while still having multi-week lead time if a change sits in a long-lived branch before merging.
Why does change failure rate matter if MTTR is already low?
They measure different things: change failure rate is how often you break something, MTTR is how fast you fix it once broken. A team with low MTTR but a high failure rate is fast at cleanup but shipping unreliable changes in the first place — both are needed for the pattern DORA research associates with high performers: frequent, low-risk, quickly-recoverable deployments.
Can these metrics be gamed?
Yes, easily — tagging tiny meaningless commits as 'deploys' inflates deployment frequency, and under-reporting failures deflates change failure rate. The numbers are only useful if the tagging discipline behind them (a real deploy gets a real tag, a real failure gets marked as such) is honest, which is a process problem no script can enforce.