@mark1russell7/service 0.5.13 → 0.5.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/build/{builder-DawJhq4p.d.ts → builder-CsSO4LzB.d.ts} +2 -2
- package/build/{core-BPLRDcUJ.d.ts → core-D8iXeoE4.d.ts} +10 -3
- package/build/{dial-DA8vJfh2.d.ts → dial-DJolDmOK.d.ts} +2 -2
- package/build/grpc.d.ts +3 -3
- package/build/grpc.js +1 -1
- package/build/http-client.d.ts +3 -3
- package/build/http-client.js +1 -1
- package/build/http.d.ts +1 -1
- package/build/index.d.ts +103 -9
- package/build/index.js +316 -27
- package/build/index.js.map +1 -1
- package/build/{lifecycle-fgYfxrHK.d.ts → lifecycle-CMedxaQd.d.ts} +60 -2
- package/build/{machine-BhTRu3u9.js → machine-DZMb_QEU.js} +23 -7
- package/build/machine-DZMb_QEU.js.map +1 -0
- package/build/mongodb.d.ts +3 -3
- package/build/mongodb.js +1 -1
- package/build/pg.d.ts +2 -2
- package/build/pg.js +1 -1
- package/build/{queue-snRHK3Jm.d.ts → queue-CndiCVaY.d.ts} +2 -2
- package/build/rabbitmq.d.ts +4 -4
- package/build/rabbitmq.js +1 -1
- package/build/redis.d.ts +2 -2
- package/build/redis.js +1 -1
- package/build/testing.d.ts +2 -2
- package/build/testing.js +1 -0
- package/build/testing.js.map +1 -1
- package/build/websocket.d.ts +3 -3
- package/build/websocket.js +1 -1
- package/package.json +1 -1
- package/build/machine-BhTRu3u9.js.map +0 -1
package/build/index.js
CHANGED
|
@@ -136,6 +136,114 @@ const latch = (timing, clock, tripped, changed, trace) => {
|
|
|
136
136
|
};
|
|
137
137
|
};
|
|
138
138
|
//#endregion
|
|
139
|
+
//#region src/metrics.ts
|
|
140
|
+
/** These fractions of the timeout of a poll are the bounds of its histogram. */
|
|
141
|
+
const DURATION_FRACTIONS = [
|
|
142
|
+
.05,
|
|
143
|
+
.1,
|
|
144
|
+
.25,
|
|
145
|
+
.5,
|
|
146
|
+
.75,
|
|
147
|
+
1
|
|
148
|
+
];
|
|
149
|
+
/** The runtime keeps this many episodes of each check. */
|
|
150
|
+
const EPISODES_KEPT = 8;
|
|
151
|
+
/**
|
|
152
|
+
* This function removes the secrets that a cause can hold. These are the user and the password of a
|
|
153
|
+
* URL, and the value of a key, for example `password=`. A driver error can hold a connection string.
|
|
154
|
+
*/
|
|
155
|
+
const redact = (cause) => cause.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/@]*@/gi, "$1***@").replace(/\b(password|passwd|pwd|secret|token|api[_-]?key|auth)(\s*[=:]\s*)("[^"]*"|'[^']*'|[^\s,;&]+)/gi, "$1$2***");
|
|
156
|
+
/** This function makes a meter. */
|
|
157
|
+
const meter = () => {
|
|
158
|
+
const checks = /* @__PURE__ */ new Map();
|
|
159
|
+
const transitions = /* @__PURE__ */ new Map();
|
|
160
|
+
const add = (check) => {
|
|
161
|
+
if (checks.has(check.name)) return;
|
|
162
|
+
const timeout = valueIn(check.poll.timeout, "ms");
|
|
163
|
+
checks.set(check.name, {
|
|
164
|
+
ok: 0,
|
|
165
|
+
error: 0,
|
|
166
|
+
indeterminate: 0,
|
|
167
|
+
thrown: 0,
|
|
168
|
+
forced: 0,
|
|
169
|
+
le: DURATION_FRACTIONS.map((fraction) => fraction * timeout),
|
|
170
|
+
counts: DURATION_FRACTIONS.map(() => 0),
|
|
171
|
+
timedOut: 0,
|
|
172
|
+
episodes: [],
|
|
173
|
+
open: null
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
const close = (counted, end) => {
|
|
177
|
+
if (counted.open === null) return null;
|
|
178
|
+
return {
|
|
179
|
+
start: counted.open.start,
|
|
180
|
+
end,
|
|
181
|
+
failures: counted.open.failures,
|
|
182
|
+
cause: counted.open.cause
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
const poll = (check, result) => {
|
|
186
|
+
add(check);
|
|
187
|
+
const counted = checks.get(check.name);
|
|
188
|
+
counted[result.outcome] += 1;
|
|
189
|
+
if (result.thrown) counted.thrown += 1;
|
|
190
|
+
if (result.forced) counted.forced += 1;
|
|
191
|
+
if (result.timedOut) counted.timedOut += 1;
|
|
192
|
+
else {
|
|
193
|
+
const at = counted.le.findIndex((bound) => result.took <= bound);
|
|
194
|
+
const bucket = at === -1 ? counted.counts.length - 1 : at;
|
|
195
|
+
counted.counts[bucket] = (counted.counts[bucket] ?? 0) + 1;
|
|
196
|
+
}
|
|
197
|
+
if (result.outcome === "error") {
|
|
198
|
+
if (counted.open === null) counted.open = {
|
|
199
|
+
start: result.at,
|
|
200
|
+
failures: 0,
|
|
201
|
+
cause: result.cause === null ? null : redact(result.cause)
|
|
202
|
+
};
|
|
203
|
+
counted.open.failures += 1;
|
|
204
|
+
} else if (result.outcome === "ok" && counted.open !== null) {
|
|
205
|
+
counted.episodes.push(close(counted, result.at));
|
|
206
|
+
counted.open = null;
|
|
207
|
+
if (counted.episodes.length > 8) counted.episodes.shift();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const event = (event) => {
|
|
211
|
+
const key = `${event.machine} ${event.instance} ${event.from} -> ${event.to}`;
|
|
212
|
+
transitions.set(key, (transitions.get(key) ?? 0) + 1);
|
|
213
|
+
};
|
|
214
|
+
const metrics = () => {
|
|
215
|
+
const out = {};
|
|
216
|
+
for (const [name, counted] of checks) {
|
|
217
|
+
const open = close(counted, null);
|
|
218
|
+
out[name] = {
|
|
219
|
+
outcomes: {
|
|
220
|
+
ok: counted.ok,
|
|
221
|
+
error: counted.error,
|
|
222
|
+
indeterminate: counted.indeterminate
|
|
223
|
+
},
|
|
224
|
+
thrown: counted.thrown,
|
|
225
|
+
forced: counted.forced,
|
|
226
|
+
durations: {
|
|
227
|
+
le: [...counted.le],
|
|
228
|
+
counts: [...counted.counts],
|
|
229
|
+
timedOut: counted.timedOut
|
|
230
|
+
},
|
|
231
|
+
episodes: open === null ? [...counted.episodes] : [...counted.episodes, open].slice(-8)
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
checks: out,
|
|
236
|
+
transitions: Object.fromEntries(transitions)
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
add,
|
|
241
|
+
poll,
|
|
242
|
+
event,
|
|
243
|
+
metrics
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
//#endregion
|
|
139
247
|
//#region src/poller.ts
|
|
140
248
|
const message$1 = (error) => error instanceof Error ? error.message : String(error);
|
|
141
249
|
/**
|
|
@@ -304,7 +412,12 @@ const healthRuntime = (description, runs, options = {}) => {
|
|
|
304
412
|
let draining = false;
|
|
305
413
|
let stopped = false;
|
|
306
414
|
let gate = "closed";
|
|
307
|
-
const
|
|
415
|
+
const counts = meter();
|
|
416
|
+
const record = (event) => {
|
|
417
|
+
counts.event(event);
|
|
418
|
+
options.record?.(event);
|
|
419
|
+
};
|
|
420
|
+
const tracer = (machine, instance) => (from, to, on) => record({
|
|
308
421
|
at: clock.now(),
|
|
309
422
|
machine,
|
|
310
423
|
instance,
|
|
@@ -316,7 +429,7 @@ const healthRuntime = (description, runs, options = {}) => {
|
|
|
316
429
|
const moveGate = (to, on, detail) => {
|
|
317
430
|
const from = gate;
|
|
318
431
|
gate = to;
|
|
319
|
-
|
|
432
|
+
record({
|
|
320
433
|
at: clock.now(),
|
|
321
434
|
machine: "gate",
|
|
322
435
|
instance: description.name,
|
|
@@ -350,6 +463,7 @@ const healthRuntime = (description, runs, options = {}) => {
|
|
|
350
463
|
random,
|
|
351
464
|
forced: () => forced.has(check.name) || check.dependency !== null && forced.has(check.dependency),
|
|
352
465
|
record: (result) => {
|
|
466
|
+
counts.poll(check, result);
|
|
353
467
|
made?.record(result);
|
|
354
468
|
if (result.outcome === "ok" && check.dependency !== null) {
|
|
355
469
|
if (lateDependencies.delete(check.dependency)) changed();
|
|
@@ -359,6 +473,7 @@ const healthRuntime = (description, runs, options = {}) => {
|
|
|
359
473
|
})
|
|
360
474
|
};
|
|
361
475
|
entries.set(check.name, entry);
|
|
476
|
+
counts.add(check);
|
|
362
477
|
if (late && !stopped) {
|
|
363
478
|
entry.poller.pollNow();
|
|
364
479
|
entry.poller.start(entry.poller.jittered());
|
|
@@ -425,6 +540,7 @@ const healthRuntime = (description, runs, options = {}) => {
|
|
|
425
540
|
});
|
|
426
541
|
return {
|
|
427
542
|
verdict,
|
|
543
|
+
metrics: counts.metrics,
|
|
428
544
|
probe(kind, controller) {
|
|
429
545
|
const now = verdict();
|
|
430
546
|
if (kind === "startup") return answer(kind, now.started);
|
|
@@ -2202,8 +2318,74 @@ const verdictOf = (rule, bind, evaluated) => {
|
|
|
2202
2318
|
const all = inequalities(rule);
|
|
2203
2319
|
return applicable.some((index) => readsAbsent(all[index], bind)) ? "unbounded" : "holds";
|
|
2204
2320
|
};
|
|
2321
|
+
/** This function gives the mark of a symbol: its mark at its hop, else the mark of the bare symbol. */
|
|
2322
|
+
const markOf = (marks, key) => marks[key] ?? marks[key.split("@")[0] ?? key];
|
|
2323
|
+
const STEP_EPSILON = 1e-9;
|
|
2324
|
+
const scaled = (seconds, k, mark) => {
|
|
2325
|
+
switch (mark.kind) {
|
|
2326
|
+
case "scalable": return seconds * k;
|
|
2327
|
+
case "fixed": return seconds;
|
|
2328
|
+
case "stepped": return Math.max(Math.ceil(seconds * k / mark.step - STEP_EPSILON), 0) * mark.step;
|
|
2329
|
+
case "floored": return Math.max(seconds * k, mark.floor);
|
|
2330
|
+
}
|
|
2331
|
+
};
|
|
2332
|
+
/**
|
|
2333
|
+
* This function scales each timer of a binding by k, by its mark (E10). An absent timer ("inf" or
|
|
2334
|
+
* "off") stays absent. A chain scales each value. The margins of the binding and the constants of
|
|
2335
|
+
* the rules do not scale: they are tolerances, not timers. A symbol with no mark keeps its value.
|
|
2336
|
+
*/
|
|
2337
|
+
const compress = (bind, k, marks) => {
|
|
2338
|
+
if (!Number.isFinite(k) || k <= 0) throw new RangeError(`a compression scales time by a finite factor above 0, not ${k}`);
|
|
2339
|
+
const out = {};
|
|
2340
|
+
const unmarked = [];
|
|
2341
|
+
for (const [key, bound] of Object.entries(bind)) {
|
|
2342
|
+
if (key === "margin" || key === "margins") continue;
|
|
2343
|
+
const mark = markOf(marks, key);
|
|
2344
|
+
if (mark === void 0) {
|
|
2345
|
+
if (bound !== void 0) unmarked.push(key);
|
|
2346
|
+
out[key] = bound;
|
|
2347
|
+
} else if (typeof bound === "number") out[key] = scaled(bound, k, mark);
|
|
2348
|
+
else if (Array.isArray(bound)) out[key] = bound.map((seconds) => scaled(seconds, k, mark));
|
|
2349
|
+
else out[key] = bound;
|
|
2350
|
+
}
|
|
2351
|
+
const margins = {
|
|
2352
|
+
...bind.margin !== void 0 ? { margin: bind.margin } : {},
|
|
2353
|
+
...bind.margins !== void 0 ? { margins: bind.margins } : {}
|
|
2354
|
+
};
|
|
2355
|
+
return {
|
|
2356
|
+
bind: {
|
|
2357
|
+
...out,
|
|
2358
|
+
...margins
|
|
2359
|
+
},
|
|
2360
|
+
unmarked
|
|
2361
|
+
};
|
|
2362
|
+
};
|
|
2363
|
+
/**
|
|
2364
|
+
* This function tells which rules a compression by k changes. A compression keeps each ordering
|
|
2365
|
+
* when no verdict changes. A change in either direction counts: a violation that the compression
|
|
2366
|
+
* hides is as wrong as one that it makes. The unmarked symbols are the symbols that the rules read.
|
|
2367
|
+
*/
|
|
2368
|
+
const compression = (rules, bind, k, marks) => {
|
|
2369
|
+
const compressed = compress(bind, k, marks);
|
|
2370
|
+
const changed = rules.flatMap((rule) => {
|
|
2371
|
+
const before = verdictOf(rule, bind, evaluate(rule, bind));
|
|
2372
|
+
const after = verdictOf(rule, compressed.bind, evaluate(rule, compressed.bind));
|
|
2373
|
+
return before === after ? [] : [{
|
|
2374
|
+
rule: rule.id,
|
|
2375
|
+
before,
|
|
2376
|
+
after
|
|
2377
|
+
}];
|
|
2378
|
+
});
|
|
2379
|
+
const read = new Set(rules.flatMap(symbolsOf));
|
|
2380
|
+
return {
|
|
2381
|
+
changed,
|
|
2382
|
+
unmarked: compressed.unmarked.filter((symbol) => read.has(symbol))
|
|
2383
|
+
};
|
|
2384
|
+
};
|
|
2205
2385
|
//#endregion
|
|
2206
2386
|
//#region src/lint.ts
|
|
2387
|
+
/** The law binds this statistic of a distribution. */
|
|
2388
|
+
const LAW_STATISTIC = "p99";
|
|
2207
2389
|
/** These are the identifiers of the rules that each declaration gets. */
|
|
2208
2390
|
const SERVICE_RULES = [
|
|
2209
2391
|
"O04",
|
|
@@ -2294,6 +2476,35 @@ const LIMITER_RULES = [{
|
|
|
2294
2476
|
const secondsOf = (amount) => rationals.toNumber(convertExact(rationals.fromNumber(amount.value), amount.unit, "s"));
|
|
2295
2477
|
/** This function gives an amount, or no bound, as the law binds it. */
|
|
2296
2478
|
const boundOf = (amount) => amount.value === "infinite" ? "inf" : secondsOf(amount);
|
|
2479
|
+
/**
|
|
2480
|
+
* This function gives a value of the scenario as the law binds it, with its epoch. A distribution
|
|
2481
|
+
* binds its p99. A distribution without a p99 binds nothing, thus each rule that reads it depends.
|
|
2482
|
+
*/
|
|
2483
|
+
const claimOf = (value) => {
|
|
2484
|
+
if (!("epoch" in value)) return {
|
|
2485
|
+
bound: boundOf(value),
|
|
2486
|
+
epoch: null
|
|
2487
|
+
};
|
|
2488
|
+
if ("value" in value) return {
|
|
2489
|
+
bound: boundOf(value.value),
|
|
2490
|
+
epoch: value.epoch
|
|
2491
|
+
};
|
|
2492
|
+
const statistic = value.distribution["p99"];
|
|
2493
|
+
return {
|
|
2494
|
+
bound: statistic === void 0 ? void 0 : secondsOf(statistic),
|
|
2495
|
+
epoch: value.epoch
|
|
2496
|
+
};
|
|
2497
|
+
};
|
|
2498
|
+
/** This function binds each value of a part of the scenario, at the symbol that `at` gives, and records its epoch. */
|
|
2499
|
+
const bindScenario = (values, at, bind, epochs) => {
|
|
2500
|
+
for (const [symbol, value] of Object.entries(values)) {
|
|
2501
|
+
const claim = claimOf(value);
|
|
2502
|
+
const key = at(symbol);
|
|
2503
|
+
epochs[key] = claim.epoch;
|
|
2504
|
+
if (claim.bound !== void 0) bind[key] = claim.bound;
|
|
2505
|
+
else delete bind[key];
|
|
2506
|
+
}
|
|
2507
|
+
};
|
|
2297
2508
|
/** This function tells if a unit is a time. A unit that the package does not know is not a time. */
|
|
2298
2509
|
const isTime = (unit) => {
|
|
2299
2510
|
try {
|
|
@@ -2308,6 +2519,7 @@ const most = (values) => values.length === 0 ? void 0 : Math.max(...values);
|
|
|
2308
2519
|
const serviceBindings = (svc, scenario) => {
|
|
2309
2520
|
const bind = {};
|
|
2310
2521
|
const from = {};
|
|
2522
|
+
const epochs = {};
|
|
2311
2523
|
const set = (symbol, bound, said) => {
|
|
2312
2524
|
if (bound === void 0) return;
|
|
2313
2525
|
bind[symbol] = bound;
|
|
@@ -2330,18 +2542,25 @@ const serviceBindings = (svc, scenario) => {
|
|
|
2330
2542
|
set("cooperative_window@process", boundOf(svc.shutdown.cooperativeWindow), "the declared cooperative window");
|
|
2331
2543
|
const hold = svc.shutdown.phases.find((phase) => phase.name === "fail readiness");
|
|
2332
2544
|
set("readiness_hold@process", hold === void 0 ? 0 : secondsOf(hold.cap), hold === void 0 ? "no fail readiness phase: the intake stops at once after the signal" : "the cap of the fail readiness phase, which holds while the endpoints update");
|
|
2333
|
-
|
|
2545
|
+
bindScenario(scenario.platform, (symbol) => symbol, bind, epochs);
|
|
2546
|
+
for (const [symbol, epoch] of Object.entries(epochs)) if (bind[symbol] === void 0) delete from[symbol];
|
|
2547
|
+
else from[symbol] = epoch === null ? "the scenario" : `the scenario, for ${epoch}`;
|
|
2334
2548
|
return {
|
|
2335
2549
|
bind,
|
|
2336
|
-
from
|
|
2550
|
+
from,
|
|
2551
|
+
epochs
|
|
2337
2552
|
};
|
|
2338
2553
|
};
|
|
2339
2554
|
/** This function gives the client knobs of a dependency at @client, and the values of the scenario at its own hops. */
|
|
2340
2555
|
const dependencyBindings = (dependency, scenario) => {
|
|
2341
2556
|
const bind = {};
|
|
2557
|
+
const epochs = {};
|
|
2342
2558
|
for (const [knob, amount] of Object.entries(dependency.knobs)) bind[`${knob}@client`] = boundOf(amount);
|
|
2343
|
-
|
|
2344
|
-
return
|
|
2559
|
+
bindScenario(scenario.dependencies?.[dependency.name] ?? {}, (symbol) => symbol.includes("@") ? symbol : `${symbol}@dependency`, bind, epochs);
|
|
2560
|
+
return {
|
|
2561
|
+
bind,
|
|
2562
|
+
epochs
|
|
2563
|
+
};
|
|
2345
2564
|
};
|
|
2346
2565
|
/**
|
|
2347
2566
|
* These are the hops of the law at which the knobs of a transport bind, by its kind. A worker does
|
|
@@ -2358,12 +2577,16 @@ const TRANSPORT_HOPS = {
|
|
|
2358
2577
|
*/
|
|
2359
2578
|
const transportBindings = (transport, hop, scenario) => {
|
|
2360
2579
|
const bind = {};
|
|
2580
|
+
const epochs = {};
|
|
2361
2581
|
for (const [knob, amount] of Object.entries(transport.knobs)) {
|
|
2362
2582
|
if (!isTime(amount.unit)) continue;
|
|
2363
2583
|
bind[knob.includes("@") ? knob : `${knob}@${hop}`] = boundOf(amount);
|
|
2364
2584
|
}
|
|
2365
|
-
|
|
2366
|
-
return
|
|
2585
|
+
bindScenario(scenario.transports?.[transport.name] ?? {}, (symbol) => symbol, bind, epochs);
|
|
2586
|
+
return {
|
|
2587
|
+
bind,
|
|
2588
|
+
epochs
|
|
2589
|
+
};
|
|
2367
2590
|
};
|
|
2368
2591
|
/**
|
|
2369
2592
|
* This function gives the symbols of a limiter, and the values of the scenario for its callers.
|
|
@@ -2377,8 +2600,12 @@ const limiterBindings = (limiter, scenario) => {
|
|
|
2377
2600
|
"retry_after@limiter": retryAfterSeconds(longestRetryAfter(limiter.policy)),
|
|
2378
2601
|
"retry_after_at_cap@limiter": retryAfterSeconds(idleSpan(limiter.policy))
|
|
2379
2602
|
};
|
|
2380
|
-
|
|
2381
|
-
|
|
2603
|
+
const epochs = {};
|
|
2604
|
+
bindScenario(scenario.limiters?.[limiter.name] ?? {}, (symbol) => symbol.includes("@") ? symbol : `${symbol}@caller`, bind, epochs);
|
|
2605
|
+
return {
|
|
2606
|
+
bind,
|
|
2607
|
+
epochs
|
|
2608
|
+
};
|
|
2382
2609
|
};
|
|
2383
2610
|
const SCOPED = /@(client|dependency|server)$/;
|
|
2384
2611
|
const TRANSPORT_SCOPED = /@consumer$/;
|
|
@@ -2528,6 +2755,79 @@ const progressFindings = (svc) => {
|
|
|
2528
2755
|
}
|
|
2529
2756
|
return findings;
|
|
2530
2757
|
};
|
|
2758
|
+
/** This function gives the scopes of a rule that reads these symbols: the service, each client, each consumer and worker, or each limiter. */
|
|
2759
|
+
const scopesOf = (svc, scenario, service, symbols) => {
|
|
2760
|
+
const within = (name, scoped) => {
|
|
2761
|
+
const bind = {
|
|
2762
|
+
...service.bind,
|
|
2763
|
+
...scoped.bind
|
|
2764
|
+
};
|
|
2765
|
+
for (const symbol of Object.keys(scoped.epochs)) if (!(symbol in scoped.bind)) delete bind[symbol];
|
|
2766
|
+
return [
|
|
2767
|
+
name,
|
|
2768
|
+
bind,
|
|
2769
|
+
{
|
|
2770
|
+
...service.epochs,
|
|
2771
|
+
...scoped.epochs
|
|
2772
|
+
}
|
|
2773
|
+
];
|
|
2774
|
+
};
|
|
2775
|
+
return symbols.some((symbol) => LIMITER_SCOPED.test(symbol)) ? (svc.limiters ?? []).map((limiter) => within(limiter.name, limiterBindings(limiter, scenario))) : symbols.some((symbol) => TRANSPORT_SCOPED.test(symbol)) ? svc.transports.flatMap((transport) => {
|
|
2776
|
+
const hop = TRANSPORT_HOPS[transport.kind];
|
|
2777
|
+
return hop === null ? [] : [within(transport.name, transportBindings(transport, hop, scenario))];
|
|
2778
|
+
}) : symbols.some((symbol) => SCOPED.test(symbol)) ? svc.dependencies.map((dependency) => within(dependency.name, dependencyBindings(dependency, scenario))) : [[
|
|
2779
|
+
svc.name,
|
|
2780
|
+
service.bind,
|
|
2781
|
+
service.epochs
|
|
2782
|
+
]];
|
|
2783
|
+
};
|
|
2784
|
+
/** This function gives the epochs of the symbols of a rule that the scenario binds. */
|
|
2785
|
+
const epochsOf = (symbols, epochs) => Object.fromEntries(symbols.flatMap((symbol) => symbol in epochs ? [[symbol, epochs[symbol] ?? null]] : []));
|
|
2786
|
+
/**
|
|
2787
|
+
* These are the marks of the timers of the platform (E10). Kubernetes takes the probe period, the
|
|
2788
|
+
* preStop sleep and the termination grace in whole seconds, thus each is stepped by 1 s. The
|
|
2789
|
+
* cluster sets the propagation of the endpoints, thus a compression of the service cannot scale it.
|
|
2790
|
+
*/
|
|
2791
|
+
const PLATFORM_MARKS = {
|
|
2792
|
+
"probe_period@pod": {
|
|
2793
|
+
kind: "stepped",
|
|
2794
|
+
step: 1
|
|
2795
|
+
},
|
|
2796
|
+
"prestop_sleep@pod": {
|
|
2797
|
+
kind: "stepped",
|
|
2798
|
+
step: 1
|
|
2799
|
+
},
|
|
2800
|
+
"termination_grace@pod": {
|
|
2801
|
+
kind: "stepped",
|
|
2802
|
+
step: 1
|
|
2803
|
+
},
|
|
2804
|
+
"propagation_bound@cluster": { kind: "fixed" }
|
|
2805
|
+
};
|
|
2806
|
+
/**
|
|
2807
|
+
* This function tells which findings of `lint` a compression by k changes (E10). It scales the
|
|
2808
|
+
* bindings of each scope by the marks. Thus a harness that runs the service k times faster learns
|
|
2809
|
+
* the orderings that its run breaks. The unmarked symbols are the symbols of the rules with no mark.
|
|
2810
|
+
*/
|
|
2811
|
+
const compressionOf = (svc, scenario, rules, k, marks) => {
|
|
2812
|
+
const service = serviceBindings(svc, scenario);
|
|
2813
|
+
const changed = [];
|
|
2814
|
+
const unmarked = /* @__PURE__ */ new Set();
|
|
2815
|
+
for (const rule of rules) {
|
|
2816
|
+
if (!SERVICE_RULES.includes(rule.id)) continue;
|
|
2817
|
+
for (const [scope, bind] of scopesOf(svc, scenario, service, symbolsOf(rule))) {
|
|
2818
|
+
const result = compression([rule], bind, k, marks);
|
|
2819
|
+
for (const change of result.changed) changed.push({
|
|
2820
|
+
...change,
|
|
2821
|
+
scope
|
|
2822
|
+
});
|
|
2823
|
+
for (const symbol of result.unmarked) unmarked.add(symbol);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
return {
|
|
2827
|
+
changed,
|
|
2828
|
+
unmarked: [...unmarked]
|
|
2829
|
+
};
|
|
2830
|
+
};
|
|
2531
2831
|
/**
|
|
2532
2832
|
* This function applies each service rule to a declaration and a scenario. A rule applies one time
|
|
2533
2833
|
* for the service, for each client, for each consumer and worker, or for each limiter. The function
|
|
@@ -2535,26 +2835,14 @@ const progressFindings = (svc) => {
|
|
|
2535
2835
|
* Each progress check that names its loops also gets its findings (`PROGRESS_RULE`).
|
|
2536
2836
|
*/
|
|
2537
2837
|
const lint = (svc, scenario, rules) => {
|
|
2538
|
-
const service = serviceBindings(svc, scenario)
|
|
2838
|
+
const service = serviceBindings(svc, scenario);
|
|
2539
2839
|
const findings = [...declarationFindings(svc), ...progressFindings(svc)];
|
|
2540
2840
|
for (const rule of rules) {
|
|
2541
2841
|
if (!SERVICE_RULES.includes(rule.id)) continue;
|
|
2542
2842
|
const symbols = symbolsOf(rule);
|
|
2543
|
-
const
|
|
2544
|
-
...service,
|
|
2545
|
-
...limiterBindings(limiter, scenario)
|
|
2546
|
-
}]) : symbols.some((symbol) => TRANSPORT_SCOPED.test(symbol)) ? svc.transports.flatMap((transport) => {
|
|
2547
|
-
const hop = TRANSPORT_HOPS[transport.kind];
|
|
2548
|
-
return hop === null ? [] : [[transport.name, {
|
|
2549
|
-
...service,
|
|
2550
|
-
...transportBindings(transport, hop, scenario)
|
|
2551
|
-
}]];
|
|
2552
|
-
}) : symbols.some((symbol) => SCOPED.test(symbol)) ? svc.dependencies.map((dependency) => [dependency.name, {
|
|
2553
|
-
...service,
|
|
2554
|
-
...dependencyBindings(dependency, scenario)
|
|
2555
|
-
}]) : [[svc.name, service]];
|
|
2556
|
-
for (const [scope, bind] of scopes) {
|
|
2843
|
+
for (const [scope, bind, all] of scopesOf(svc, scenario, service, symbols)) {
|
|
2557
2844
|
const evaluated = evaluate(rule, bind);
|
|
2845
|
+
const epochs = epochsOf(symbols, all);
|
|
2558
2846
|
findings.push({
|
|
2559
2847
|
rule: rule.id,
|
|
2560
2848
|
scope,
|
|
@@ -2562,7 +2850,8 @@ const lint = (svc, scenario, rules) => {
|
|
|
2562
2850
|
statement: rule.rule,
|
|
2563
2851
|
...rule.window !== void 0 ? { window: rule.window } : {},
|
|
2564
2852
|
evaluated,
|
|
2565
|
-
unbound: symbols.filter((symbol) => bind[symbol] === void 0)
|
|
2853
|
+
unbound: symbols.filter((symbol) => bind[symbol] === void 0),
|
|
2854
|
+
...Object.values(epochs).some((epoch) => epoch !== null) ? { epochs } : {}
|
|
2566
2855
|
});
|
|
2567
2856
|
}
|
|
2568
2857
|
}
|
|
@@ -3555,6 +3844,6 @@ const MACHINES = {
|
|
|
3555
3844
|
watchdog: WATCHDOG_MACHINE
|
|
3556
3845
|
};
|
|
3557
3846
|
//#endregion
|
|
3558
|
-
export { DECLARATION_RULE, EXIT_MACHINE, EXIT_SIGNALS, FORMAT, GATE_MACHINE, LATCH_MACHINE, LIMITER_RULES, MACHINES, POLLER_MACHINE, PROCESS_TRIGGERS, PROGRESS_RULE, SERVICE_RULES, SLIDING_WINDOW_MACHINE, STAGES, TOKEN_BUCKET_MACHINE, WATCHDOG_MACHINE, WINDOW_MACHINE, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, capKnob, cooperativeAbortAt, daemon, decide, detectionTime, drainMachine, embeddedProcess, ephemeral, evaluate, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, inequalities, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, lint, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redeliverOnce, replay, retryAfterSeconds, service, serviceBindings, stageRank, symbolsOf, verdictOf, watchdog, workerLoop };
|
|
3847
|
+
export { DECLARATION_RULE, DURATION_FRACTIONS, EPISODES_KEPT, EXIT_MACHINE, EXIT_SIGNALS, FORMAT, GATE_MACHINE, LATCH_MACHINE, LAW_STATISTIC, LIMITER_RULES, MACHINES, PLATFORM_MARKS, POLLER_MACHINE, PROCESS_TRIGGERS, PROGRESS_RULE, SERVICE_RULES, SLIDING_WINDOW_MACHINE, STAGES, TOKEN_BUCKET_MACHINE, WATCHDOG_MACHINE, WINDOW_MACHINE, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, capKnob, compress, compression, compressionOf, cooperativeAbortAt, daemon, decide, detectionTime, drainMachine, embeddedProcess, ephemeral, evaluate, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, inequalities, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, lint, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redact, redeliverOnce, replay, retryAfterSeconds, service, serviceBindings, stageRank, symbolsOf, verdictOf, watchdog, workerLoop };
|
|
3559
3848
|
|
|
3560
3849
|
//# sourceMappingURL=index.js.map
|