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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! 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:
/// One figure as a JSON member: the quoted step name, a colon, and the gap in milliseconds.
/// Why:
/// `object_json` maps every figure through it, so no closure is needed to join the two parts.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const memberJson = ([step, gap]: [string, number]) => `${JSON.stringify(step)}:${milliseconds(gap)}`;
/// ```
/// What:
/// Whether a figure is a step that ended by itself, which is a name without the quiet-end suffix.
/// Why:
/// `longest_healthy` filters with it; `filter` hands over a reference to each item, so the figure arrives as `&&(..)`.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const endedByItself = ([step]: [string, number]) => !step.endsWith(ENDED_SUFFIX);
/// ```
/// What:
/// The gap of a figure.
/// Why:
/// `longest_healthy` maps every figure to its gap before taking the maximum.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const gapOf = ([, gap]: [string, number]) => gap;
/// ```
/// 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.