Skip to main content

software_engineering/
dora_metrics.rs

1//! # The DORA Metrics Framework
2//!
3//! The **DORA metrics** come from the DevOps Research and Assessment
4//! programme, later published as the book *Accelerate*, which surveyed tens
5//! of thousands of engineering professionals to find which delivery
6//! practices correlate with organizational performance. Four metrics, paired
7//! two and two: **deployment frequency** and **lead time for changes**
8//! measure speed; **change failure rate** and **failed deployment recovery
9//! time** measure stability. The framework's central finding is that elite
10//! performers are fast and stable simultaneously — speed and safety do not
11//! trade off against each other the way intuition suggests.
12//!
13//! ## Formula
14//!
15//! ```text
16//! Deployment frequency         = deployments / days
17//! Lead time for changes        = deploy time − first commit time
18//! Change failure rate (%)      = (failed deployments / total deployments) × 100
19//! Failed deployment recovery   = restored time − detected time (never the deploy event)
20//! ```
21//!
22//! ## Why it matters
23//!
24//! DORA measures the pipeline, not the value flowing through it: a team can
25//! post excellent DORA numbers while its actual output has quietly drifted
26//! toward rework, a gap this book's Flow Framework chapters are built to
27//! surface and DORA cannot see. Used within that bounded scope, DORA gives
28//! large organizations a consistent, comparable measure of pipeline
29//! mechanics across many teams — genuinely valuable for prioritizing
30//! platform investment, provided all four metrics are reported together and
31//! never applied to individual performance reviews.
32//!
33//! ## Example
34//!
35//! A platform team's change failure rate falls from 25% to 8% across 100
36//! production deployments a year, a stability improvement that DORA's
37//! pairing discipline insists on measuring alongside any speed gain, never
38//! in isolation.
39//!
40//! ```rust
41//! use software_engineering::dora_metrics::{
42//!     change_failure_rate_percent, deployment_frequency_per_day,
43//!     lead_time_for_changes_hours, failed_deployment_recovery_time_hours,
44//! };
45//!
46//! let cfr_before = change_failure_rate_percent(25.0, 100.0).unwrap();
47//! let cfr_after = change_failure_rate_percent(8.0, 100.0).unwrap();
48//! assert_eq!(cfr_before, 25.0);
49//! assert_eq!(cfr_after, 8.0);
50//! assert!(cfr_after < cfr_before);
51//!
52//! // 2 deployments/day, a 6-hour lead time from first commit to production,
53//! // and a 1.5-hour recovery from detection to restoration.
54//! assert_eq!(deployment_frequency_per_day(14.0, 7.0).unwrap(), 2.0);
55//! assert_eq!(lead_time_for_changes_hours(0.0, 6.0), 6.0);
56//! assert_eq!(failed_deployment_recovery_time_hours(10.0, 11.5), 1.5);
57//! ```
58//!
59//! ## Pitfalls
60//!
61//! - **Treating DORA as the whole picture of delivery health** — it is
62//!   silent on what kind of value is being delivered; pair it with flow
63//!   distribution.
64//! - **Reporting only the speed half** — defeats the framework's central
65//!   finding that speed and stability move together in high performers.
66//! - **Using DORA metrics in individual performance reviews** — breaks the
67//!   framework's statistical validity and invites gaming.
68//! - **Comparing teams with inconsistent definitions** of "deployment,"
69//!   "change," or "failure" — produces comparisons that look fair but are
70//!   not.
71//! - **Self-reported DORA numbers instead of pipeline-instrumented ones** —
72//!   reintroduces exactly the bias the framework was designed to eliminate.
73//!
74//! ## Sources
75//!
76//! - Chapter 2.10, The DORA metrics framework.
77//! - Forsgren, Nicole, Jez Humble, and Gene Kim, *Accelerate: The Science of
78//!   Lean Software and DevOps* (2018).
79//!
80//! Topic doc: software-engineering-metrics/locales/en-001/chapters/02-10-the-dora-metrics-framework.md
81
82/// Change failure rate: the percentage of deployments that caused a failure
83/// requiring remediation, a rollback, a hotfix, or an incident.
84///
85/// `(failed_deployments / total_deployments) × 100`. Definition drift here
86/// is the chapter's specific warning: agree on what counts as a "failure" in
87/// writing before comparing this number across teams.
88///
89/// # Arguments
90///
91/// * `failed_deployments` — count of deployments that caused a failure.
92/// * `total_deployments` — total count of deployments in the period.
93///
94/// # Returns
95///
96/// The failure rate as a percentage (0.0–100.0 for sane inputs), or `None`
97/// if `total_deployments` is zero.
98///
99/// # Examples
100///
101/// ```rust
102/// use software_engineering::dora_metrics::change_failure_rate_percent;
103///
104/// assert_eq!(change_failure_rate_percent(25.0, 100.0), Some(25.0));
105/// assert_eq!(change_failure_rate_percent(8.0, 100.0), Some(8.0));
106/// assert_eq!(change_failure_rate_percent(1.0, 0.0), None);
107/// ```
108#[must_use]
109pub fn change_failure_rate_percent(failed_deployments: f64, total_deployments: f64) -> Option<f64> {
110    if total_deployments == 0.0 {
111        return None;
112    }
113    Some((failed_deployments / total_deployments) * 100.0)
114}
115
116/// Deployment frequency: how often a team successfully releases to
117/// production.
118///
119/// `deployments / days`. Count only successful production deployments,
120/// instrumented from the pipeline, never self-reported, and watch for
121/// substitution gaming — splitting one meaningful change into several
122/// trivial deploys purely to inflate the count.
123///
124/// # Arguments
125///
126/// * `deployments` — count of successful production deployments.
127/// * `days` — length of the observation period, in days.
128///
129/// # Returns
130///
131/// Deployments per day, or `None` if `days` is zero.
132///
133/// # Examples
134///
135/// ```rust
136/// use software_engineering::dora_metrics::deployment_frequency_per_day;
137///
138/// // 14 deployments across a week is 2 per day.
139/// assert_eq!(deployment_frequency_per_day(14.0, 7.0), Some(2.0));
140/// assert_eq!(deployment_frequency_per_day(1.0, 0.0), None);
141/// ```
142#[must_use]
143pub fn deployment_frequency_per_day(deployments: f64, days: f64) -> Option<f64> {
144    if days == 0.0 {
145        return None;
146    }
147    Some(deployments / days)
148}
149
150/// Lead time for changes: the time from a code change's first commit to its
151/// successful deployment in production.
152///
153/// `deploy_time_hours − first_commit_time_hours`. Report both the median and
154/// a high percentile across many changes, not just a mean, since this
155/// quantity is typically skewed.
156///
157/// # Arguments
158///
159/// * `first_commit_time_hours` — timestamp of the change's first commit, in
160///   hours on any consistent scale.
161/// * `deploy_time_hours` — timestamp of its successful production
162///   deployment, on the same scale.
163///
164/// # Returns
165///
166/// The elapsed lead time in hours.
167///
168/// # Examples
169///
170/// ```rust
171/// use software_engineering::dora_metrics::lead_time_for_changes_hours;
172///
173/// // First commit at hour 0, deployed at hour 6: a 6-hour lead time.
174/// assert_eq!(lead_time_for_changes_hours(0.0, 6.0), 6.0);
175/// ```
176#[must_use]
177pub fn lead_time_for_changes_hours(first_commit_time_hours: f64, deploy_time_hours: f64) -> f64 {
178    deploy_time_hours - first_commit_time_hours
179}
180
181/// Failed deployment recovery time (often shortened to MTTR): how long it
182/// takes to restore service once a deployment causes a failure.
183///
184/// `restored_time_hours − detected_time_hours`. Start the clock at
185/// detection, not at the deploy event itself, so the number reflects
186/// genuine recovery delay rather than a monitoring gap.
187///
188/// # Arguments
189///
190/// * `detected_time_hours` — timestamp the failure was detected, in hours on
191///   any consistent scale.
192/// * `restored_time_hours` — timestamp service was restored, on the same
193///   scale.
194///
195/// # Returns
196///
197/// The elapsed recovery time in hours.
198///
199/// # Examples
200///
201/// ```rust
202/// use software_engineering::dora_metrics::failed_deployment_recovery_time_hours;
203///
204/// // Detected at hour 10, restored at hour 11.5: a 1.5-hour recovery.
205/// assert_eq!(failed_deployment_recovery_time_hours(10.0, 11.5), 1.5);
206/// ```
207#[must_use]
208pub fn failed_deployment_recovery_time_hours(detected_time_hours: f64, restored_time_hours: f64) -> f64 {
209    restored_time_hours - detected_time_hours
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    // "Change failure rate measures the percentage of deployments that
217    // cause a failure requiring remediation, a rollback, a hotfix, or an
218    // incident."
219    #[test]
220    fn change_failure_rate_computes_percentage() {
221        let cfr = change_failure_rate_percent(25.0, 100.0).unwrap();
222        assert!((cfr - 25.0).abs() < 1e-9);
223        let cfr_after = change_failure_rate_percent(8.0, 100.0).unwrap();
224        assert!((cfr_after - 8.0).abs() < 1e-9);
225    }
226
227    #[test]
228    fn change_failure_rate_is_none_for_zero_total() {
229        assert_eq!(change_failure_rate_percent(1.0, 0.0), None);
230    }
231
232    // "Deployment frequency measures how often a team successfully
233    // releases to production."
234    #[test]
235    fn deployment_frequency_computes_rate_per_day() {
236        let freq = deployment_frequency_per_day(14.0, 7.0).unwrap();
237        assert!((freq - 2.0).abs() < 1e-9);
238    }
239
240    #[test]
241    fn deployment_frequency_is_none_for_zero_days() {
242        assert_eq!(deployment_frequency_per_day(1.0, 0.0), None);
243    }
244
245    // "Lead time for changes measures the time from a code change's first
246    // commit to its successful deployment in production."
247    #[test]
248    fn lead_time_is_deploy_time_minus_first_commit_time() {
249        let lead_time = lead_time_for_changes_hours(2.0, 8.0);
250        assert!((lead_time - 6.0).abs() < 1e-9);
251    }
252
253    // "Start the clock at detection, not at the deploy event itself, so the
254    // number reflects genuine recovery delay rather than a monitoring gap."
255    #[test]
256    fn recovery_time_starts_at_detection_not_deploy() {
257        let recovery = failed_deployment_recovery_time_hours(10.0, 11.5);
258        assert!((recovery - 1.5).abs() < 1e-9);
259    }
260}