@agoric/cosmic-swingset 0.41.4-dev-1d8b7d2.0 → 0.41.4-dev-74fc45b.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/cosmic-swingset",
3
- "version": "0.41.4-dev-1d8b7d2.0+1d8b7d2",
3
+ "version": "0.41.4-dev-74fc45b.0+74fc45b",
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-1d8b7d2.0+1d8b7d2",
26
- "@agoric/cosmos": "0.34.2-dev-1d8b7d2.0+1d8b7d2",
27
- "@agoric/deploy-script-support": "0.10.4-dev-1d8b7d2.0+1d8b7d2",
28
- "@agoric/internal": "0.3.3-dev-1d8b7d2.0+1d8b7d2",
29
- "@agoric/store": "0.9.3-dev-1d8b7d2.0+1d8b7d2",
30
- "@agoric/swing-store": "0.9.2-dev-1d8b7d2.0+1d8b7d2",
31
- "@agoric/swingset-vat": "0.32.3-dev-1d8b7d2.0+1d8b7d2",
32
- "@agoric/telemetry": "0.6.3-dev-1d8b7d2.0+1d8b7d2",
33
- "@agoric/vm-config": "0.1.1-dev-1d8b7d2.0+1d8b7d2",
25
+ "@agoric/builders": "0.1.1-dev-74fc45b.0+74fc45b",
26
+ "@agoric/cosmos": "0.34.2-dev-74fc45b.0+74fc45b",
27
+ "@agoric/deploy-script-support": "0.10.4-dev-74fc45b.0+74fc45b",
28
+ "@agoric/internal": "0.3.3-dev-74fc45b.0+74fc45b",
29
+ "@agoric/store": "0.9.3-dev-74fc45b.0+74fc45b",
30
+ "@agoric/swing-store": "0.9.2-dev-74fc45b.0+74fc45b",
31
+ "@agoric/swingset-vat": "0.32.3-dev-74fc45b.0+74fc45b",
32
+ "@agoric/telemetry": "0.6.3-dev-74fc45b.0+74fc45b",
33
+ "@agoric/vm-config": "0.1.1-dev-74fc45b.0+74fc45b",
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": "1d8b7d2258ae499fa59ec36372e99186fc23f9e8"
74
+ "gitHead": "74fc45b3588986de37f6a84347058592dc1d529b"
75
75
  }
@@ -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
- const baseMetricOptions = /** @type {const} */ ({
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(baseMetricOptions).map(
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 {import('@opentelemetry/api').Meter} metricMeter
165
+ * @param {OTelMeter} metricMeter
115
166
  * @param {string} name
116
167
  */
117
168
  function createHistogram(metricMeter, name) {
118
- const { description } = baseMetricOptions[name] || {};
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 {Attributes} [group] the
137
- * attributes to associate with a group
138
- * @param {Attributes} [instance] the specific metric attributes
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
- if (meterUsage) {
219
- // Add to aggregated metering stats.
220
- for (const [key, value] of Object.entries(meterUsage)) {
221
- if (key === 'meterType') {
222
- continue;
223
- }
224
- getGroupedRecorder(`swingset_meter_usage`, group, {
225
- // The meterType is an instance-specific attribute--a change in
226
- // it will result in the old value being discarded.
227
- ...(meterUsage.meterType && {
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
- * Create a metrics manager for the 'inboundQueue' structure, which
244
- * can be scaped to report current length, and the number of
245
- * increments and decrements. This must be created with the initial
246
- * length as extracted from durable storage, but after that we assume
247
- * that we're told about every up and down, so our RAM-backed shadow
248
- * 'length' will remain accurate.
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
- * @param {number} initialLength
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
- export function makeInboundQueueMetrics(initialLength) {
256
- let length = initialLength;
257
- let add = 0;
258
- let remove = 0;
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
- updateLength: newLength => {
262
- const delta = newLength - length;
263
- length = newLength;
264
- if (delta > 0) {
265
- add += delta;
266
- } else {
267
- remove -= delta;
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
- length -= delta;
273
- remove += delta;
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
- cosmic_swingset_inbound_queue_length: length,
278
- cosmic_swingset_inbound_queue_add: add,
279
- cosmic_swingset_inbound_queue_remove: remove,
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
- * @param {object} param0
286
- * @param {any} param0.controller
287
- * @param {import('@opentelemetry/api').Meter} param0.metricMeter
288
- * @param {Console} param0.log
289
- * @param {Attributes} [param0.attributes]
290
- * @param {any} [param0.inboundQueueMetrics]
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
- inboundQueueMetrics,
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
- if (inboundQueueMetrics) {
363
- // These are not kernelStatsMetrics, they're outside the kernel.
364
- for (const name of ['length', 'add', 'remove']) {
365
- const key = `cosmic_swingset_inbound_queue_${name}`;
366
- const options = {
367
- description: `inbound queue ${name}`,
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
  };
@@ -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
- * @typedef {'leftover' | 'forced' | 'high-priority' | 'timer' | 'queued' | 'cleanup'} CrankerPhase
92
- * - leftover: work from a previous block
93
- * - forced: work that claims the entirety of the current block
94
- * - high-priority: queued work the precedes timer advancement
95
- * - intermission: needed to note state exports and update consistency hashes
96
- * - queued: queued work the follows timer advancement
97
- * - cleanup: for dealing with data from terminated vats
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
- /** @type {CrankerPhase} */
101
- const CLEANUP = 'cleanup';
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 inboundQueueMetrics = makeInboundQueueMetrics(
470
- actionQueue.size() + highPriorityQueue.size(),
471
- );
472
- const { crankScheduler } = exportKernelStats({
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
- inboundQueueMetrics,
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 === CLEANUP) {
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('forced');
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 {CrankerPhase} phase
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('leftover');
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, 'forced');
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
- 'high-priority',
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('timer');
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, 'queued');
823
+ await processActions(actionQueue, runSwingset, CrankerPhase.Inbound);
805
824
 
806
825
  // Cleanup after terminated vats as allowed.
807
- await runSwingset('cleanup');
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
- inboundQueueMetrics.updateLength(
818
- actionQueue.size() + highPriorityQueue.size() + runThisBlock.size(),
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.