@agoric/cosmic-swingset 0.41.4-dev-1d8b7d2.0 → 0.41.4-dev-7f4df76.0
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/package.json +11 -11
- package/src/kernel-stats.js +203 -87
- package/src/launch-chain.js +50 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/cosmic-swingset",
|
|
3
|
-
"version": "0.41.4-dev-
|
|
3
|
+
"version": "0.41.4-dev-7f4df76.0+7f4df76",
|
|
4
4
|
"description": "Agoric's Cosmos blockchain integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,15 +22,15 @@
|
|
|
22
22
|
"author": "Agoric",
|
|
23
23
|
"license": "Apache-2.0",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agoric/builders": "0.1.1-dev-
|
|
26
|
-
"@agoric/cosmos": "0.34.2-dev-
|
|
27
|
-
"@agoric/deploy-script-support": "0.10.4-dev-
|
|
28
|
-
"@agoric/internal": "0.3.3-dev-
|
|
29
|
-
"@agoric/store": "0.9.3-dev-
|
|
30
|
-
"@agoric/swing-store": "0.9.2-dev-
|
|
31
|
-
"@agoric/swingset-vat": "0.32.3-dev-
|
|
32
|
-
"@agoric/telemetry": "0.6.3-dev-
|
|
33
|
-
"@agoric/vm-config": "0.1.1-dev-
|
|
25
|
+
"@agoric/builders": "0.1.1-dev-7f4df76.0+7f4df76",
|
|
26
|
+
"@agoric/cosmos": "0.34.2-dev-7f4df76.0+7f4df76",
|
|
27
|
+
"@agoric/deploy-script-support": "0.10.4-dev-7f4df76.0+7f4df76",
|
|
28
|
+
"@agoric/internal": "0.3.3-dev-7f4df76.0+7f4df76",
|
|
29
|
+
"@agoric/store": "0.9.3-dev-7f4df76.0+7f4df76",
|
|
30
|
+
"@agoric/swing-store": "0.9.2-dev-7f4df76.0+7f4df76",
|
|
31
|
+
"@agoric/swingset-vat": "0.32.3-dev-7f4df76.0+7f4df76",
|
|
32
|
+
"@agoric/telemetry": "0.6.3-dev-7f4df76.0+7f4df76",
|
|
33
|
+
"@agoric/vm-config": "0.1.1-dev-7f4df76.0+7f4df76",
|
|
34
34
|
"@endo/bundle-source": "^3.5.1",
|
|
35
35
|
"@endo/env-options": "^1.1.8",
|
|
36
36
|
"@endo/errors": "^1.2.9",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"typeCoverage": {
|
|
72
72
|
"atLeast": 83.4
|
|
73
73
|
},
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "7f4df7622f3e70d2b80b83139e16c4f28616203a"
|
|
75
75
|
}
|
package/src/kernel-stats.js
CHANGED
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
View,
|
|
6
6
|
} from '@opentelemetry/sdk-metrics';
|
|
7
7
|
|
|
8
|
+
import { Fail } from '@endo/errors';
|
|
9
|
+
import { isNat } from '@endo/nat';
|
|
10
|
+
|
|
8
11
|
import { makeLegacyMap } from '@agoric/store';
|
|
9
12
|
|
|
10
13
|
import {
|
|
@@ -12,18 +15,19 @@ import {
|
|
|
12
15
|
KERNEL_STATS_UPDOWN_METRICS,
|
|
13
16
|
} from '@agoric/swingset-vat/src/kernel/metrics.js';
|
|
14
17
|
|
|
15
|
-
// import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
|
|
16
|
-
|
|
17
|
-
// diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.VERBOSE);
|
|
18
|
-
|
|
19
|
-
/** @import {MetricAttributes as Attributes} from '@opentelemetry/api' */
|
|
20
|
-
/** @import {Histogram} from '@opentelemetry/api' */
|
|
21
|
-
|
|
22
18
|
import { getTelemetryProviders as getTelemetryProvidersOriginal } from '@agoric/telemetry';
|
|
23
19
|
|
|
24
20
|
import v8 from 'node:v8';
|
|
25
21
|
import process from 'node:process';
|
|
26
22
|
|
|
23
|
+
/** @import {Histogram, Meter as OTelMeter, MetricAttributes} from '@opentelemetry/api' */
|
|
24
|
+
|
|
25
|
+
/** @import {TotalMap} from '@agoric/internal' */
|
|
26
|
+
|
|
27
|
+
// import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
|
|
28
|
+
|
|
29
|
+
// diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.VERBOSE);
|
|
30
|
+
|
|
27
31
|
/**
|
|
28
32
|
* TODO Would be nice somehow to label the vats individually, but it's too
|
|
29
33
|
* high cardinality for us unless we can somehow limit the number of active
|
|
@@ -48,7 +52,9 @@ export const HISTOGRAM_MS_LATENCY_BOUNDARIES = [
|
|
|
48
52
|
export const HISTOGRAM_SECONDS_LATENCY_BOUNDARIES =
|
|
49
53
|
HISTOGRAM_MS_LATENCY_BOUNDARIES.map(ms => ms / 1000);
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
// TODO: Validate these boundaries. We're not going to have 5ms blocks, but
|
|
56
|
+
// we probably care about the difference between 10 vs. 30 seconds.
|
|
57
|
+
const HISTOGRAM_METRICS = /** @type {const} */ ({
|
|
52
58
|
swingset_crank_processing_time: {
|
|
53
59
|
description: 'Processing time per crank (ms)',
|
|
54
60
|
boundaries: [1, 11, 21, 31, 41, 51, 61, 71, 81, 91, Infinity],
|
|
@@ -71,6 +77,44 @@ const baseMetricOptions = /** @type {const} */ ({
|
|
|
71
77
|
},
|
|
72
78
|
});
|
|
73
79
|
|
|
80
|
+
/** @enum {(typeof QueueMetricAspect)[keyof typeof QueueMetricAspect]} */
|
|
81
|
+
const QueueMetricAspect = /** @type {const} */ ({
|
|
82
|
+
Length: 'length',
|
|
83
|
+
IncrementCount: 'increments',
|
|
84
|
+
DecrementCount: 'decrements',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Queue metrics come in {length,add,remove} triples sharing a common prefix.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} namePrefix
|
|
91
|
+
* @param {string} descPrefix
|
|
92
|
+
* @returns {Record<string, {aspect: QueueMetricAspect, description: string}>}
|
|
93
|
+
*/
|
|
94
|
+
const makeQueueMetrics = (namePrefix, descPrefix) => {
|
|
95
|
+
/** @type {Array<[QueueMetricAspect, string, string]>} */
|
|
96
|
+
const metricsMeta = [
|
|
97
|
+
[QueueMetricAspect.Length, 'length', 'length'],
|
|
98
|
+
[QueueMetricAspect.IncrementCount, 'add', 'increments'],
|
|
99
|
+
[QueueMetricAspect.DecrementCount, 'remove', 'decrements'],
|
|
100
|
+
];
|
|
101
|
+
const entries = metricsMeta.map(([aspect, nameSuffix, descSuffix]) => {
|
|
102
|
+
const name = `${namePrefix}_${nameSuffix}`;
|
|
103
|
+
const description = `${descPrefix} ${descSuffix}`;
|
|
104
|
+
return [name, { aspect, description }];
|
|
105
|
+
});
|
|
106
|
+
return Object.fromEntries(entries);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const QUEUE_METRICS = harden({
|
|
110
|
+
// "cosmic_swingset_inbound_queue_{length,add,remove}" measurements carry a
|
|
111
|
+
// "queue" attribute.
|
|
112
|
+
// Future OpenTelemetry SDKs should support expressing that in Instrument
|
|
113
|
+
// creation:
|
|
114
|
+
// https://opentelemetry.io/docs/specs/otel/metrics/api/#instrument-advisory-parameter-attributes
|
|
115
|
+
...makeQueueMetrics('cosmic_swingset_inbound_queue', 'inbound queue'),
|
|
116
|
+
});
|
|
117
|
+
|
|
74
118
|
const wrapDeltaMS = (finisher, useDeltaMS) => {
|
|
75
119
|
const startMS = Date.now();
|
|
76
120
|
return (...finishArgs) => {
|
|
@@ -88,12 +132,19 @@ const recordToKey = record =>
|
|
|
88
132
|
Object.entries(record).sort(([ka], [kb]) => (ka < kb ? -1 : 1)),
|
|
89
133
|
);
|
|
90
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Return an array of Views defining explicit buckets for Histogram instruments
|
|
137
|
+
* to which we record measurements.
|
|
138
|
+
*/
|
|
91
139
|
export function getMetricsProviderViews() {
|
|
92
|
-
return Object.entries(
|
|
140
|
+
return Object.entries(HISTOGRAM_METRICS).map(
|
|
93
141
|
([instrumentName, { boundaries }]) =>
|
|
142
|
+
// TODO: Add `instrumentType: InstrumentType.HISTOGRAM` and `meterName`
|
|
143
|
+
// filters (the latter of which should be a parameter of the exported
|
|
144
|
+
// function).
|
|
94
145
|
new View({
|
|
95
|
-
aggregation: new ExplicitBucketHistogramAggregation([...boundaries]),
|
|
96
146
|
instrumentName,
|
|
147
|
+
aggregation: new ExplicitBucketHistogramAggregation([...boundaries]),
|
|
97
148
|
}),
|
|
98
149
|
);
|
|
99
150
|
}
|
|
@@ -111,19 +162,16 @@ export function getTelemetryProviders(powers = {}) {
|
|
|
111
162
|
}
|
|
112
163
|
|
|
113
164
|
/**
|
|
114
|
-
* @param {
|
|
165
|
+
* @param {OTelMeter} metricMeter
|
|
115
166
|
* @param {string} name
|
|
116
167
|
*/
|
|
117
168
|
function createHistogram(metricMeter, name) {
|
|
118
|
-
const { description } =
|
|
169
|
+
const { description } = HISTOGRAM_METRICS[name] || {};
|
|
119
170
|
return metricMeter.createHistogram(name, { description });
|
|
120
171
|
}
|
|
121
172
|
|
|
122
173
|
/**
|
|
123
|
-
* @param {{
|
|
124
|
-
* metricMeter: import('@opentelemetry/api').Meter,
|
|
125
|
-
* attributes?: import('@opentelemetry/api').MetricAttributes,
|
|
126
|
-
* }} param0
|
|
174
|
+
* @param {{ metricMeter: OTelMeter, attributes?: MetricAttributes }} options
|
|
127
175
|
*/
|
|
128
176
|
export function makeSlogCallbacks({ metricMeter, attributes = {} }) {
|
|
129
177
|
// Legacy because legacyMaps are not passable
|
|
@@ -133,9 +181,9 @@ export function makeSlogCallbacks({ metricMeter, attributes = {} }) {
|
|
|
133
181
|
* This function reuses or creates per-group named metrics.
|
|
134
182
|
*
|
|
135
183
|
* @param {string} name name of the base metric
|
|
136
|
-
* @param {
|
|
137
|
-
*
|
|
138
|
-
* @param {
|
|
184
|
+
* @param {MetricAttributes} [group] the
|
|
185
|
+
* attributes to associate with a group
|
|
186
|
+
* @param {MetricAttributes} [instance] the specific metric attributes
|
|
139
187
|
* @returns {Pick<Histogram, 'record'>} the attribute-aware recorder
|
|
140
188
|
*/
|
|
141
189
|
const getGroupedRecorder = (name, group = undefined, instance = {}) => {
|
|
@@ -215,21 +263,16 @@ export function makeSlogCallbacks({ metricMeter, attributes = {} }) {
|
|
|
215
263
|
(deltaMS, [[_status, _problem, meterUsage]]) => {
|
|
216
264
|
const group = getVatGroup(vatID);
|
|
217
265
|
getGroupedRecorder('swingset_vat_delivery', group).record(deltaMS);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
meterType: meterUsage.meterType,
|
|
229
|
-
}),
|
|
230
|
-
stat: key,
|
|
231
|
-
}).record(value || 0);
|
|
232
|
-
}
|
|
266
|
+
const { meterType, ...measurements } = meterUsage || {};
|
|
267
|
+
for (const [key, value] of Object.entries(measurements)) {
|
|
268
|
+
if (typeof value === 'object') continue;
|
|
269
|
+
// TODO: Each measurement key should have its own histogram; there's
|
|
270
|
+
// no reason to mix e.g. allocate/compute/currentHeapCount.
|
|
271
|
+
// cf. https://prometheus.io/docs/practices/naming/#metric-names
|
|
272
|
+
const detail = { ...(meterType ? { meterType } : {}), stat: key };
|
|
273
|
+
getGroupedRecorder('swingset_meter_usage', group, detail).record(
|
|
274
|
+
value || 0,
|
|
275
|
+
);
|
|
233
276
|
}
|
|
234
277
|
},
|
|
235
278
|
);
|
|
@@ -240,61 +283,147 @@ export function makeSlogCallbacks({ metricMeter, attributes = {} }) {
|
|
|
240
283
|
}
|
|
241
284
|
|
|
242
285
|
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
|
|
286
|
+
* @template {string} QueueName
|
|
287
|
+
* @typedef InboundQueueMetricsManager
|
|
288
|
+
* @property {(newLengths: Record<QueueName, number>) => void} updateLengths
|
|
289
|
+
* @property {(queueName: QueueName, delta?: number) => void} decStat
|
|
290
|
+
* @property {() => Record<string, number>} getStats
|
|
291
|
+
*/
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Create a metrics manager for inbound queues. It must be initialized with the
|
|
295
|
+
* length of each queue and informed of each subsequent change so that metrics
|
|
296
|
+
* can be provided from RAM.
|
|
249
297
|
*
|
|
250
298
|
* Note that the add/remove counts will get reset at restart, but
|
|
251
299
|
* Prometheus/etc tools can tolerate that just fine.
|
|
252
300
|
*
|
|
253
|
-
* @
|
|
301
|
+
* @template {string} QueueName
|
|
302
|
+
* @param {OTelMeter} metricMeter
|
|
303
|
+
* @param {Record<QueueName, number>} initialLengths per-queue
|
|
304
|
+
* @param {Console} logger
|
|
305
|
+
* @returns {InboundQueueMetricsManager<QueueName>}
|
|
254
306
|
*/
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
307
|
+
function makeInboundQueueMetrics(metricMeter, initialLengths, logger) {
|
|
308
|
+
const initialEntries = Object.entries(initialLengths);
|
|
309
|
+
const zeroEntries = initialEntries.map(([queueName]) => [queueName, 0]);
|
|
310
|
+
const makeQueueCounts = entries => {
|
|
311
|
+
for (const [queueName, length] of entries) {
|
|
312
|
+
isNat(length) ||
|
|
313
|
+
Fail`invalid initial length for queue ${queueName}: ${length}`;
|
|
314
|
+
}
|
|
315
|
+
return /** @type {TotalMap<string, number>} */ (new Map(entries));
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* For each {length,increment count,decrement count} aspect (each such aspect
|
|
319
|
+
* corresponding to a single OpenTelemetry Instrument), keep a map of values
|
|
320
|
+
* keyed by queue name (each corresponding to a value of Attribute "queue").
|
|
321
|
+
*
|
|
322
|
+
* @type {Record<QueueMetricAspect, TotalMap<string, number>>}
|
|
323
|
+
*/
|
|
324
|
+
const counterData = {
|
|
325
|
+
[QueueMetricAspect.Length]: makeQueueCounts(initialEntries),
|
|
326
|
+
[QueueMetricAspect.IncrementCount]: makeQueueCounts(zeroEntries),
|
|
327
|
+
[QueueMetricAspect.DecrementCount]: makeQueueCounts(zeroEntries),
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// In the event of misconfigured reporting for an unknown queue, accept the
|
|
331
|
+
// data with a warning rather than either ignore it or halt the chain.
|
|
332
|
+
const provideQueue = queueName => {
|
|
333
|
+
if (counterData[QueueMetricAspect.Length].has(queueName)) return;
|
|
334
|
+
logger.warn(`unknown inbound queue ${JSON.stringify(queueName)}`);
|
|
335
|
+
for (const [aspect, map] of Object.entries(counterData)) {
|
|
336
|
+
const old = map.get(queueName);
|
|
337
|
+
old === undefined ||
|
|
338
|
+
Fail`internal: unexpected preexisting ${aspect}=${old} data for late queue ${queueName}`;
|
|
339
|
+
map.set(queueName, 0);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const nudge = (map, queueName, delta) => {
|
|
344
|
+
const old = map.get(queueName);
|
|
345
|
+
old !== undefined ||
|
|
346
|
+
Fail`internal: unexpected missing data for queue ${queueName}`;
|
|
347
|
+
map.set(queueName, old + delta);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
// Wire up callbacks for reporting the OpenTelemetry measurements:
|
|
351
|
+
// queue length is an UpDownCounter, while increment and decrement counts are
|
|
352
|
+
// [monotonic] Counters.
|
|
353
|
+
// But note that the Prometheus representation of the former will be a Gauge:
|
|
354
|
+
// https://prometheus.io/docs/concepts/metric_types/
|
|
355
|
+
for (const [name, { aspect, description }] of Object.entries(QUEUE_METRICS)) {
|
|
356
|
+
const isMonotonic = aspect !== QueueMetricAspect.Length;
|
|
357
|
+
const instrumentOptions = { description };
|
|
358
|
+
const asyncInstrument = isMonotonic
|
|
359
|
+
? metricMeter.createObservableCounter(name, instrumentOptions)
|
|
360
|
+
: metricMeter.createObservableUpDownCounter(name, instrumentOptions);
|
|
361
|
+
asyncInstrument.addCallback(observer => {
|
|
362
|
+
for (const [queueName, value] of counterData[aspect].entries()) {
|
|
363
|
+
observer.observe(value, { queue: queueName });
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
259
367
|
|
|
260
368
|
return harden({
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
369
|
+
updateLengths: newLengths => {
|
|
370
|
+
for (const [queueName, newLength] of Object.entries(newLengths)) {
|
|
371
|
+
provideQueue(queueName);
|
|
372
|
+
isNat(newLength) ||
|
|
373
|
+
Fail`invalid length for queue ${queueName}: ${newLength}`;
|
|
374
|
+
const oldLength = counterData[QueueMetricAspect.Length].get(queueName);
|
|
375
|
+
counterData[QueueMetricAspect.Length].set(queueName, newLength);
|
|
376
|
+
if (newLength > oldLength) {
|
|
377
|
+
const map = counterData[QueueMetricAspect.IncrementCount];
|
|
378
|
+
nudge(map, queueName, newLength - oldLength);
|
|
379
|
+
} else if (newLength < oldLength) {
|
|
380
|
+
const map = counterData[QueueMetricAspect.DecrementCount];
|
|
381
|
+
nudge(map, queueName, oldLength - newLength);
|
|
382
|
+
}
|
|
268
383
|
}
|
|
269
384
|
},
|
|
270
385
|
|
|
271
|
-
decStat: (delta = 1) => {
|
|
272
|
-
|
|
273
|
-
|
|
386
|
+
decStat: (queueName, delta = 1) => {
|
|
387
|
+
provideQueue(queueName);
|
|
388
|
+
isNat(delta) || Fail`invalid decStat for queue ${queueName}: ${delta}`;
|
|
389
|
+
nudge(counterData[QueueMetricAspect.Length], queueName, -delta);
|
|
390
|
+
nudge(counterData[QueueMetricAspect.DecrementCount], queueName, delta);
|
|
274
391
|
},
|
|
275
392
|
|
|
276
|
-
getStats: () =>
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
393
|
+
getStats: () => {
|
|
394
|
+
// For each [length,add,remove] metric name, emit both a
|
|
395
|
+
// per-queue-name count and a pre-aggregated sum over all queue names
|
|
396
|
+
// (the latter is necessary for backwards compatibility until all old
|
|
397
|
+
// consumers of e.g. slog entries have been updated).
|
|
398
|
+
const entries = [];
|
|
399
|
+
for (const [name, { aspect }] of Object.entries(QUEUE_METRICS)) {
|
|
400
|
+
let sum = 0;
|
|
401
|
+
for (const [queueName, value] of counterData[aspect].entries()) {
|
|
402
|
+
sum += value;
|
|
403
|
+
entries.push([`${name}_${queueName}`, value]);
|
|
404
|
+
}
|
|
405
|
+
entries.push([name, sum]);
|
|
406
|
+
}
|
|
407
|
+
return Object.fromEntries(entries);
|
|
408
|
+
},
|
|
281
409
|
});
|
|
282
410
|
}
|
|
283
411
|
|
|
284
412
|
/**
|
|
285
|
-
* @
|
|
286
|
-
* @param {
|
|
287
|
-
* @param {
|
|
288
|
-
* @param {
|
|
289
|
-
* @param {
|
|
290
|
-
* @param {
|
|
413
|
+
* @template {string} QueueName
|
|
414
|
+
* @param {object} config
|
|
415
|
+
* @param {any} config.controller
|
|
416
|
+
* @param {OTelMeter} config.metricMeter
|
|
417
|
+
* @param {Console} config.log
|
|
418
|
+
* @param {MetricAttributes} [config.attributes]
|
|
419
|
+
* @param {Record<QueueName, number>} [config.initialQueueLengths] per-queue
|
|
291
420
|
*/
|
|
292
421
|
export function exportKernelStats({
|
|
293
422
|
controller,
|
|
294
423
|
metricMeter,
|
|
295
424
|
log = console,
|
|
296
425
|
attributes = {},
|
|
297
|
-
|
|
426
|
+
initialQueueLengths = /** @type {any} */ ({}),
|
|
298
427
|
}) {
|
|
299
428
|
const kernelStatsMetrics = new Set();
|
|
300
429
|
const kernelStatsCounters = new Map();
|
|
@@ -359,26 +488,12 @@ export function exportKernelStats({
|
|
|
359
488
|
kernelStatsMetrics.add(key);
|
|
360
489
|
}
|
|
361
490
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
};
|
|
369
|
-
const counter =
|
|
370
|
-
name === 'length'
|
|
371
|
-
? metricMeter.createObservableUpDownCounter(key, options)
|
|
372
|
-
: metricMeter.createObservableCounter(key, options);
|
|
373
|
-
|
|
374
|
-
counter.addCallback(observableResult => {
|
|
375
|
-
observableResult.observe(
|
|
376
|
-
inboundQueueMetrics.getStats()[key],
|
|
377
|
-
attributes,
|
|
378
|
-
);
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
}
|
|
491
|
+
// These are not kernelStatsMetrics, they're outside the kernel.
|
|
492
|
+
const inboundQueueMetrics = makeInboundQueueMetrics(
|
|
493
|
+
metricMeter,
|
|
494
|
+
initialQueueLengths,
|
|
495
|
+
log,
|
|
496
|
+
);
|
|
382
497
|
|
|
383
498
|
// TODO: We probably shouldn't roll our own Node.js process metrics, but a
|
|
384
499
|
// cursory search for "opentelemetry node.js VM instrumentation" didn't reveal
|
|
@@ -469,6 +584,7 @@ export function exportKernelStats({
|
|
|
469
584
|
|
|
470
585
|
return {
|
|
471
586
|
crankScheduler,
|
|
587
|
+
inboundQueueMetrics,
|
|
472
588
|
schedulerCrankTimeHistogram,
|
|
473
589
|
schedulerBlockTimeHistogram,
|
|
474
590
|
};
|
package/src/launch-chain.js
CHANGED
|
@@ -38,7 +38,6 @@ import { fileURLToPath } from 'url';
|
|
|
38
38
|
|
|
39
39
|
import {
|
|
40
40
|
makeDefaultMeterProvider,
|
|
41
|
-
makeInboundQueueMetrics,
|
|
42
41
|
exportKernelStats,
|
|
43
42
|
makeSlogCallbacks,
|
|
44
43
|
} from './kernel-stats.js';
|
|
@@ -88,17 +87,35 @@ const parseUpgradePlanInfo = (upgradePlan, prefix = '') => {
|
|
|
88
87
|
*/
|
|
89
88
|
|
|
90
89
|
/**
|
|
91
|
-
*
|
|
92
|
-
* -
|
|
93
|
-
* -
|
|
94
|
-
* -
|
|
95
|
-
* -
|
|
96
|
-
* -
|
|
97
|
-
* -
|
|
90
|
+
* The phase associated with a controller run.
|
|
91
|
+
* - Leftover: work from a previous block
|
|
92
|
+
* - Forced: work that claims the entirety of the current block
|
|
93
|
+
* - Priority: queued work that precedes timer device advancement (e.g., oracle price updates)
|
|
94
|
+
* - Timer: work prompted by timer advancement to the new external time
|
|
95
|
+
* - Inbound: queued work that follows timer advancement (e.g., normal messages)
|
|
96
|
+
* - Cleanup: for dealing with data from terminated vats
|
|
97
|
+
*
|
|
98
|
+
* @enum {(typeof CrankerPhase)[keyof typeof CrankerPhase]} CrankerPhase
|
|
98
99
|
*/
|
|
100
|
+
const CrankerPhase = /** @type {const} */ ({
|
|
101
|
+
Leftover: 'leftover',
|
|
102
|
+
Forced: 'forced',
|
|
103
|
+
Priority: 'priority',
|
|
104
|
+
Timer: 'timer',
|
|
105
|
+
Inbound: 'inbound',
|
|
106
|
+
Cleanup: 'cleanup',
|
|
107
|
+
});
|
|
99
108
|
|
|
100
|
-
/**
|
|
101
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Some phases correspond with inbound message queues.
|
|
111
|
+
*
|
|
112
|
+
* @enum {(typeof InboundQueueName)[keyof typeof InboundQueueName]} InboundQueueName
|
|
113
|
+
*/
|
|
114
|
+
const InboundQueueName = /** @type {const} */ ({
|
|
115
|
+
Forced: CrankerPhase.Forced,
|
|
116
|
+
Priority: CrankerPhase.Priority,
|
|
117
|
+
Inbound: CrankerPhase.Inbound,
|
|
118
|
+
});
|
|
102
119
|
|
|
103
120
|
/**
|
|
104
121
|
* @typedef {(phase: CrankerPhase) => Promise<boolean>} Cranker runs the kernel
|
|
@@ -466,15 +483,17 @@ export async function launch({
|
|
|
466
483
|
? parseInt(env.END_BLOCK_SPIN_MS, 10)
|
|
467
484
|
: 0;
|
|
468
485
|
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
486
|
+
const initialQueueLengths = /** @type {Record<InboundQueueName, number>} */ ({
|
|
487
|
+
[InboundQueueName.Forced]: runThisBlock.size(),
|
|
488
|
+
[InboundQueueName.Priority]: highPriorityQueue.size(),
|
|
489
|
+
[InboundQueueName.Inbound]: actionQueue.size(),
|
|
490
|
+
});
|
|
491
|
+
const { crankScheduler, inboundQueueMetrics } = exportKernelStats({
|
|
473
492
|
controller,
|
|
474
493
|
metricMeter,
|
|
475
494
|
// @ts-expect-error Type 'Logger<BaseLevels>' is not assignable to type 'Console'.
|
|
476
495
|
log: console,
|
|
477
|
-
|
|
496
|
+
initialQueueLengths,
|
|
478
497
|
});
|
|
479
498
|
|
|
480
499
|
/**
|
|
@@ -485,7 +504,7 @@ export async function launch({
|
|
|
485
504
|
function makeRunSwingset(blockHeight, runPolicy) {
|
|
486
505
|
let runNum = 0;
|
|
487
506
|
async function runSwingset(phase) {
|
|
488
|
-
if (phase ===
|
|
507
|
+
if (phase === CrankerPhase.Cleanup) {
|
|
489
508
|
const allowCleanup = runPolicy.startCleanup();
|
|
490
509
|
if (!allowCleanup) return false;
|
|
491
510
|
}
|
|
@@ -532,7 +551,7 @@ export async function launch({
|
|
|
532
551
|
const runPolicy = computronCounter(params, true);
|
|
533
552
|
const runSwingset = makeRunSwingset(blockHeight, runPolicy);
|
|
534
553
|
|
|
535
|
-
await runSwingset(
|
|
554
|
+
await runSwingset(CrankerPhase.Forced);
|
|
536
555
|
}
|
|
537
556
|
|
|
538
557
|
async function saveChainState() {
|
|
@@ -734,13 +753,13 @@ export async function launch({
|
|
|
734
753
|
*
|
|
735
754
|
* @param {InboundQueue} inboundQueue
|
|
736
755
|
* @param {Cranker} runSwingset
|
|
737
|
-
* @param {
|
|
756
|
+
* @param {InboundQueueName} phase
|
|
738
757
|
*/
|
|
739
758
|
async function processActions(inboundQueue, runSwingset, phase) {
|
|
740
759
|
let keepGoing = true;
|
|
741
760
|
for await (const { action, context } of inboundQueue.consumeAll()) {
|
|
742
761
|
const inboundNum = `${context.blockHeight}-${context.txHash}-${context.msgIdx}`;
|
|
743
|
-
inboundQueueMetrics.decStat();
|
|
762
|
+
inboundQueueMetrics.decStat(phase);
|
|
744
763
|
countInboundAction(action.type);
|
|
745
764
|
await performAction(action, inboundNum);
|
|
746
765
|
keepGoing = await runSwingset(phase);
|
|
@@ -763,13 +782,13 @@ export async function launch({
|
|
|
763
782
|
*/
|
|
764
783
|
async function processBlockActions(runSwingset, blockHeight, blockTime) {
|
|
765
784
|
// First, complete leftover work, if any
|
|
766
|
-
let keepGoing = await runSwingset(
|
|
785
|
+
let keepGoing = await runSwingset(CrankerPhase.Leftover);
|
|
767
786
|
if (!keepGoing) return;
|
|
768
787
|
|
|
769
788
|
// Then, if we have anything in the special runThisBlock queue, process
|
|
770
789
|
// it and do no further work.
|
|
771
790
|
if (runThisBlock.size()) {
|
|
772
|
-
await processActions(runThisBlock, runSwingset,
|
|
791
|
+
await processActions(runThisBlock, runSwingset, CrankerPhase.Forced);
|
|
773
792
|
return;
|
|
774
793
|
}
|
|
775
794
|
|
|
@@ -777,7 +796,7 @@ export async function launch({
|
|
|
777
796
|
keepGoing = await processActions(
|
|
778
797
|
highPriorityQueue,
|
|
779
798
|
runSwingset,
|
|
780
|
-
|
|
799
|
+
CrankerPhase.Priority,
|
|
781
800
|
);
|
|
782
801
|
if (!keepGoing) return;
|
|
783
802
|
|
|
@@ -797,14 +816,14 @@ export async function launch({
|
|
|
797
816
|
// We must run the kernel even if nothing was added since the kernel
|
|
798
817
|
// only notes state exports and updates consistency hashes when attempting
|
|
799
818
|
// to perform a crank.
|
|
800
|
-
keepGoing = await runSwingset(
|
|
819
|
+
keepGoing = await runSwingset(CrankerPhase.Timer);
|
|
801
820
|
if (!keepGoing) return;
|
|
802
821
|
|
|
803
822
|
// Finally, process as much as we can from the actionQueue.
|
|
804
|
-
await processActions(actionQueue, runSwingset,
|
|
823
|
+
await processActions(actionQueue, runSwingset, CrankerPhase.Inbound);
|
|
805
824
|
|
|
806
825
|
// Cleanup after terminated vats as allowed.
|
|
807
|
-
await runSwingset(
|
|
826
|
+
await runSwingset(CrankerPhase.Cleanup);
|
|
808
827
|
}
|
|
809
828
|
|
|
810
829
|
async function endBlock(blockHeight, blockTime, params) {
|
|
@@ -814,9 +833,12 @@ export async function launch({
|
|
|
814
833
|
|
|
815
834
|
// First, record new actions (bridge/mailbox/etc events that cosmos
|
|
816
835
|
// added up for delivery to swingset) into our inboundQueue metrics
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
836
|
+
const newLengths = /** @type {Record<InboundQueueName, number>} */ ({
|
|
837
|
+
[InboundQueueName.Forced]: runThisBlock.size(),
|
|
838
|
+
[InboundQueueName.Priority]: highPriorityQueue.size(),
|
|
839
|
+
[InboundQueueName.Inbound]: actionQueue.size(),
|
|
840
|
+
});
|
|
841
|
+
inboundQueueMetrics.updateLengths(newLengths);
|
|
820
842
|
|
|
821
843
|
// If we have work to complete this block, it needs to run to completion.
|
|
822
844
|
// It will also run to completion any work that swingset still had pending.
|