Skip to content
← Back

src/logging/work_counters.ghul

1
namespace Logging is
2
use Collections.Iterable
3
use Collections.LIST
4
5
// The analysis-mode counter vocabulary.
6
//
7
// Analysis mode reports named counters over the STATS request, and a
8
// test asserts against them that no more than the expected amount of
9
// compilation took place. That only works if the names are declared
10
// in one place rather than spelled at each bump site: `declare_all`
11
// creates every counter up front, so a name shows up in the report at
12
// count zero as well as when it fires, and a test can enumerate the
13
// whole vocabulary and check each name is exercised somewhere.
14
//
15
// Three families:
16
//
17
// file-pass:<pass> one file taken through one pass of the
18
// ordinary build loop, or through the
19
// on-demand expression compile
20
// file-rewalk:<pass> one file's bodies taken through one pass by
21
// the incremental body re-walk
22
// subtree-pass:<pass> one changed or appended subtree taken
23
// through one pass
24
//
25
// The distinction is the point: an interface-preserving edit should
26
// show body re-walks and no whole-file passes.
27
class WORK_COUNTERS is
28
EDIT_PATH_BODY_REWALK: string static => "edit-path-body-rewalk"
29
EDIT_PATH_INTERFACE_INCREMENTAL: string static => "edit-path-interface-incremental"
30
EDIT_PATH_FULL_REBUILD: string static => "edit-path-full-rebuild"
31
32
FILE_PASS_PREFIX: string static => "file-pass:"
33
FILE_REWALK_PREFIX: string static => "file-rewalk:"
34
SUBTREE_PASS_PREFIX: string static => "subtree-pass:"
35
DECLINED_PREFIX: string static => "edit-declined-"
36
37
// Why an EDIT was handled by the whole-project rebuild. Exactly
38
// one of these is reported per rebuilt EDIT, so they sum to the
39
// full-rebuild tally and the report says which guard was
40
// responsible rather than only that some guard was.
41
//
42
// A guard inside the narrowing search declines a *route*, not the
43
// edit: the search falls through to member replacement, which may
44
// still succeed. So a reason is latched when its guard fires and
45
// reported only if the edit ends up rebuilding. First latched
46
// wins - the search rejects the narrowest route first and widens
47
// as it falls through, so the first guard to fire is the one that
48
// stopped the best route available.
49
NOT_ELIGIBLE: string static => "not-eligible"
50
NO_RETAINED_PARSE: string static => "no-retained-parse"
51
MISSING_INTERFACE_SIGNATURE: string static => "missing-interface-signature"
52
FILE_ROOT_NOT_A_LIST: string static => "file-root-not-a-list"
53
FUNCTION_PAIRING_DESYNC: string static => "function-pairing-desync"
54
LOCATION_REFRESH_DESYNC: string static => "location-refresh-desync"
55
MULTIPLE_CHANGED_DEFINITIONS: string static => "multiple-changed-definitions"
56
FILE_LEVEL_CHANGE: string static => "file-level-change"
57
FEWER_DEFINITIONS: string static => "fewer-definitions"
58
RETAINED_PREFIX_MISMATCH: string static => "retained-prefix-mismatch"
59
NAMESPACE_NAME_MISMATCH: string static => "namespace-name-mismatch"
60
CLASS_HEADER_MISMATCH: string static => "class-header-mismatch"
61
NO_CLASS_SYMBOL: string static => "no-class-symbol"
62
UNRESETTABLE_CLOSURE: string static => "unresettable-closure"
63
OUTGOING_NOT_A_FUNCTION: string static => "outgoing-not-a-function"
64
OUTGOING_HAS_OVERRIDERS: string static => "outgoing-has-overriders"
65
OUTGOING_HAS_OVERRIDEES: string static => "outgoing-has-overridees"
66
CROSS_FILE_NAMESPACE_REFERENCE: string static => "cross-file-namespace-reference"
67
CROSS_FILE_UNKNOWN_FILE: string static => "cross-file-unknown-file"
68
INCOMING_NOT_A_FUNCTION: string static => "incoming-not-a-function"
69
TOP_LEVEL_STATEMENTS: string static => "top-level-statements"
70
71
edit_paths: Iterable[string] static =>
72
[
73
EDIT_PATH_BODY_REWALK,
74
EDIT_PATH_INTERFACE_INCREMENTAL,
75
EDIT_PATH_FULL_REBUILD
76
]
77
78
decline_reasons: Iterable[string] static =>
79
[
80
NOT_ELIGIBLE,
81
NO_RETAINED_PARSE,
82
MISSING_INTERFACE_SIGNATURE,
83
FILE_ROOT_NOT_A_LIST,
84
FUNCTION_PAIRING_DESYNC,
85
LOCATION_REFRESH_DESYNC,
86
MULTIPLE_CHANGED_DEFINITIONS,
87
FILE_LEVEL_CHANGE,
88
FEWER_DEFINITIONS,
89
RETAINED_PREFIX_MISMATCH,
90
NAMESPACE_NAME_MISMATCH,
91
CLASS_HEADER_MISMATCH,
92
NO_CLASS_SYMBOL,
93
UNRESETTABLE_CLOSURE,
94
OUTGOING_NOT_A_FUNCTION,
95
OUTGOING_HAS_OVERRIDERS,
96
OUTGOING_HAS_OVERRIDEES,
97
CROSS_FILE_NAMESPACE_REFERENCE,
98
CROSS_FILE_UNKNOWN_FILE,
99
INCOMING_NOT_A_FUNCTION,
100
TOP_LEVEL_STATEMENTS
101
]
102
103
// Create every declared counter so the report carries the whole
104
// vocabulary, including names that have not fired. Called once at
105
// analyser startup.
106
declare_all(timers: TIMERS) static is
107
for name in edit_paths do
108
timers.declare(name)
109
od
110
111
for reason in decline_reasons do
112
timers.declare(declined_name(reason))
113
od
114
si
115
116
declined_name(reason: string) -> string static =>
117
"{DECLINED_PREFIX}{reason}"
118
119
file_pass(timers: TIMERS, pass: string) static is
120
timers.bump("{FILE_PASS_PREFIX}{pass}")
121
si
122
123
file_rewalk(timers: TIMERS, pass: string) static is
124
timers.bump("{FILE_REWALK_PREFIX}{pass}")
125
si
126
127
subtree_pass(timers: TIMERS, pass: string) static is
128
timers.bump("{SUBTREE_PASS_PREFIX}{pass}")
129
si
130
131
declined(timers: TIMERS, reason: string) static is
132
timers.bump(declined_name(reason))
133
si
134
si
135
si