1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! What:
//! The longest quiet gap of every Git step and every tool this process ran, kept until a timing record takes it.
//! A quiet gap is a stretch in which the step showed no sign of work: no bytes moved, no processor time, no task running or
//! waiting for the disk (the signs of `push_progress.rs`).
//! Why:
//! A step ends only when it has been quiet for a window, so the window must be set above the longest gap a healthy step
//! shows (`doc/planning/cli-git-shipped-limits-audit.md`, "Time limits on Git child steps of a commit").
//! Nothing logged a gap before this, only whole times, and a whole time is an upper bound of the gap, never the gap.
//! The timing records (`commit_timing.rs`, `add_timing.rs`) take the figures when they are written, so a measurement
//! comes from the runs the person's own work makes. A step that was ended for being quiet is kept apart from the steps
//! that ended by themselves, because only the steps that ended by themselves show how quiet a healthy step gets.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! const gaps = new Map<string, number>();
//! export const note = (step: string, gapMs: number, ended: boolean) => gaps.set(key(step, ended), Math.max(gaps.get(key(step, ended)) ?? 0, gapMs));
//! export const drain = () => { const all = [...gaps]; gaps.clear(); return all; };
//! ```
/// Escaping of a step name inside a JSON string.
use cratequote;
/// A lock around the shared list.
use Mutex;
/// A span of time.
use Duration;
/// The suffix of a step name whose run was ended for being quiet, so it never mixes with the steps that ended by themselves.
pub const ENDED_SUFFIX: &str = " [ended for being quiet]";
/// The longest gap of each step name so far; the names are the Git subcommands and the tool names, a small fixed set.
static GAPS: = new;
/// What:
/// Keep `gap` as the longest quiet gap of `step` when it is longer than the one kept.
/// Why:
/// A step runs many times in one command; the longest of them is the figure an interval has to clear.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// note('git rev-parse', 1200, false);
/// ```
/// What:
/// Take every figure kept and forget them, in the order of the step names.
/// Why:
/// One record takes the figures of the command it describes; the next command starts from nothing.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const figures = drain();
/// ```
/// The milliseconds of a gap with three decimals, the way the timing records write times.
/// What:
/// The figures as a JSON object from step name to milliseconds.
/// Why:
/// Both timing records carry it under one key, so a reader asks one question of both files.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// JSON.stringify(Object.fromEntries(figures.map(([step, gap]) => [step, gap])))
/// ```
/// What:
/// The longest gap among the steps that ended by themselves, in milliseconds, or zero when none ran.
/// Why:
/// One number answers "how quiet did a healthy step get in this command" without reading the object.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// Math.max(0, ...figures.filter(([step]) => !step.endsWith(ENDED_SUFFIX)).map(([, gap]) => gap))
/// ```
/// The figures are kept, drained and written as controls in the test module.