@agoric/cosmic-swingset 0.41.4-dev-0e80151.0 → 0.41.4-dev-b2b8029.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.
@@ -20,16 +20,19 @@ TCP connections from your Prometheus host's IP address.
20
20
 
21
21
  SwingSet is responsible for the chain's Javascript execution. It is
22
22
  instrumented with the [OpenTelemetry](https://opentelemetry.io/) (*OTEL*)
23
- system.
23
+ system in a [slog sender module](../../docs/env.md#slogsender).
24
24
 
25
- To enable the Prometheus exporter, set the desired listening TCP port number in
26
- the `$OTEL_EXPORTER_PROMETHEUS_PORT` environment variable before running the
27
- node. To listen on http://0.0.0.0:9464/metrics use:
25
+ To enable the Prometheus exporter, set environment variable
26
+ [`OTEL_EXPORTER_PROMETHEUS_PORT`](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#prometheus-exporter)
27
+ to the desired listening TCP port number when running the node.
28
+ To listen on http://0.0.0.0:9464/metrics (the OTEL default), use:
28
29
 
29
30
  ```sh
30
31
  OTEL_EXPORTER_PROMETHEUS_PORT=9464 ag-chain-cosmos start ...
31
32
  ```
32
33
 
34
+ See also [env.md](../../docs/env.md#otel_exporter_prometheus_port).
35
+
33
36
  ## Cosmos SDK metrics
34
37
 
35
38
  The [Cosmos SDK](https://docs.cosmos.network/) layer of the system is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/cosmic-swingset",
3
- "version": "0.41.4-dev-0e80151.0+0e80151",
3
+ "version": "0.41.4-dev-b2b8029.0+b2b8029",
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-0e80151.0+0e80151",
26
- "@agoric/cosmos": "0.34.2-dev-0e80151.0+0e80151",
27
- "@agoric/deploy-script-support": "0.10.4-dev-0e80151.0+0e80151",
28
- "@agoric/internal": "0.3.3-dev-0e80151.0+0e80151",
29
- "@agoric/store": "0.9.3-dev-0e80151.0+0e80151",
30
- "@agoric/swing-store": "0.9.2-dev-0e80151.0+0e80151",
31
- "@agoric/swingset-vat": "0.32.3-dev-0e80151.0+0e80151",
32
- "@agoric/telemetry": "0.6.3-dev-0e80151.0+0e80151",
33
- "@agoric/vm-config": "0.1.1-dev-0e80151.0+0e80151",
25
+ "@agoric/builders": "0.1.1-dev-b2b8029.0+b2b8029",
26
+ "@agoric/cosmos": "0.34.2-dev-b2b8029.0+b2b8029",
27
+ "@agoric/deploy-script-support": "0.10.4-dev-b2b8029.0+b2b8029",
28
+ "@agoric/internal": "0.3.3-dev-b2b8029.0+b2b8029",
29
+ "@agoric/store": "0.9.3-dev-b2b8029.0+b2b8029",
30
+ "@agoric/swing-store": "0.9.2-dev-b2b8029.0+b2b8029",
31
+ "@agoric/swingset-vat": "0.32.3-dev-b2b8029.0+b2b8029",
32
+ "@agoric/telemetry": "0.6.3-dev-b2b8029.0+b2b8029",
33
+ "@agoric/vm-config": "0.1.1-dev-b2b8029.0+b2b8029",
34
34
  "@endo/bundle-source": "^3.5.1",
35
35
  "@endo/env-options": "^1.1.8",
36
36
  "@endo/errors": "^1.2.9",
@@ -52,7 +52,7 @@
52
52
  "tmp": "^0.2.1"
53
53
  },
54
54
  "devDependencies": {
55
- "@agoric/kmarshal": "0.1.1-dev-0e80151.0+0e80151",
55
+ "@agoric/kmarshal": "0.1.1-dev-b2b8029.0+b2b8029",
56
56
  "ava": "^5.3.0",
57
57
  "better-sqlite3": "^9.1.1",
58
58
  "c8": "^10.1.2"
@@ -75,5 +75,5 @@
75
75
  "typeCoverage": {
76
76
  "atLeast": 84.59
77
77
  },
78
- "gitHead": "0e80151700ff31478ebb29b4767e87839cbcc66c"
78
+ "gitHead": "b2b8029e5de97e794e4fe6b2e5e1ab36e2a7bc4f"
79
79
  }
package/src/chain-main.js CHANGED
@@ -452,6 +452,7 @@ export const makeLaunchChain = (
452
452
  stateDir: stateDBDir,
453
453
  env,
454
454
  serviceName: TELEMETRY_SERVICE_NAME,
455
+ otelMeterName: 'ag-chain-cosmos',
455
456
  }));
456
457
 
457
458
  const swingStoreTraceFile = processValue.getPath({
@@ -4,17 +4,19 @@ import { MeterProvider } from '@opentelemetry/sdk-metrics';
4
4
  import { Fail } from '@endo/errors';
5
5
  import { isNat } from '@endo/nat';
6
6
 
7
- import { makeLegacyMap } from '@agoric/store';
8
-
7
+ import { defineName } from '@agoric/internal/src/js-utils.js';
9
8
  import {
10
- KERNEL_STATS_SUM_METRICS,
11
- KERNEL_STATS_UPDOWN_METRICS,
12
- } from '@agoric/swingset-vat/src/kernel/metrics.js';
9
+ HISTOGRAM_METRICS,
10
+ KERNEL_STATS_METRICS,
11
+ makeQueueMetricsMeta,
12
+ QueueMetricAspect,
13
+ } from '@agoric/internal/src/metrics.js';
14
+ import { makeLegacyMap } from '@agoric/store';
13
15
 
14
16
  import v8 from 'node:v8';
15
17
  import process from 'node:process';
16
18
 
17
- /** @import {Histogram, Meter as OTelMeter, MetricAttributes} from '@opentelemetry/api' */
19
+ /** @import {Histogram, Meter as OTelMeter, MetricAttributes, ObservableCounter, ObservableUpDownCounter} from '@opentelemetry/api' */
18
20
 
19
21
  /** @import {TotalMap} from '@agoric/internal' */
20
22
 
@@ -29,86 +31,25 @@ import process from 'node:process';
29
31
  */
30
32
  const VAT_ID_IS_TOO_HIGH_CARDINALITY = true;
31
33
 
32
- export const HISTOGRAM_MS_LATENCY_BOUNDARIES = [
33
- 5,
34
- 10,
35
- 25,
36
- 50,
37
- 100,
38
- 250,
39
- 500,
40
- 1000,
41
- 2500,
42
- 5000,
43
- 10000,
44
- Infinity,
45
- ];
46
- export const HISTOGRAM_SECONDS_LATENCY_BOUNDARIES =
47
- HISTOGRAM_MS_LATENCY_BOUNDARIES.map(ms => ms / 1000);
48
-
49
- // TODO: Validate these boundaries. We're not going to have 5ms blocks, but
50
- // we probably care about the difference between 10 vs. 30 seconds.
51
- const HISTOGRAM_METRICS = /** @type {const} */ ({
52
- swingset_crank_processing_time: {
53
- description: 'Processing time per crank (ms)',
54
- boundaries: [1, 11, 21, 31, 41, 51, 61, 71, 81, 91, Infinity],
55
- },
56
- swingset_block_processing_seconds: {
57
- description: 'Processing time per block',
58
- boundaries: HISTOGRAM_SECONDS_LATENCY_BOUNDARIES,
59
- },
60
- swingset_vat_startup: {
61
- description: 'Vat startup time (ms)',
62
- boundaries: HISTOGRAM_MS_LATENCY_BOUNDARIES,
63
- },
64
- swingset_vat_delivery: {
65
- description: 'Vat delivery time (ms)',
66
- boundaries: HISTOGRAM_MS_LATENCY_BOUNDARIES,
67
- },
68
- swingset_meter_usage: {
69
- description: 'Vat meter usage',
70
- boundaries: HISTOGRAM_MS_LATENCY_BOUNDARIES,
71
- },
72
- });
73
-
74
- /** @enum {(typeof QueueMetricAspect)[keyof typeof QueueMetricAspect]} */
75
- const QueueMetricAspect = /** @type {const} */ ({
76
- Length: 'length',
77
- IncrementCount: 'increments',
78
- DecrementCount: 'decrements',
79
- });
80
-
81
- /**
82
- * Queue metrics come in {length,add,remove} triples sharing a common prefix.
83
- *
84
- * @param {string} namePrefix
85
- * @param {string} descPrefix
86
- * @returns {Record<string, {aspect: QueueMetricAspect, description: string}>}
87
- */
88
- const makeQueueMetrics = (namePrefix, descPrefix) => {
89
- /** @type {Array<[QueueMetricAspect, string, string]>} */
90
- const metricsMeta = [
91
- [QueueMetricAspect.Length, 'length', 'length'],
92
- [QueueMetricAspect.IncrementCount, 'add', 'increments'],
93
- [QueueMetricAspect.DecrementCount, 'remove', 'decrements'],
94
- ];
95
- const entries = metricsMeta.map(([aspect, nameSuffix, descSuffix]) => {
96
- const name = `${namePrefix}_${nameSuffix}`;
97
- const description = `${descPrefix} ${descSuffix}`;
98
- return [name, { aspect, description }];
34
+ const inboundQueueMeta = harden(
35
+ makeQueueMetricsMeta('cosmic_swingset_inbound_queue', 'inbound queue'),
36
+ );
37
+
38
+ const maxAgeCache = (fn, maxAge, clock = Date.now) => {
39
+ // An initial invocation verifies that the function doesn't [always] throw.
40
+ let cached = fn();
41
+ let lastTime = -Infinity;
42
+ const get = defineName(`caching ${fn.name}`, () => {
43
+ const time = clock();
44
+ if (time - lastTime < maxAge) return cached;
45
+
46
+ lastTime = time;
47
+ cached = fn();
48
+ return cached;
99
49
  });
100
- return Object.fromEntries(entries);
50
+ return { get, firstResult: cached };
101
51
  };
102
52
 
103
- const QUEUE_METRICS = harden({
104
- // "cosmic_swingset_inbound_queue_{length,add,remove}" measurements carry a
105
- // "queue" attribute.
106
- // Future OpenTelemetry SDKs should support expressing that in Instrument
107
- // creation:
108
- // https://opentelemetry.io/docs/specs/otel/metrics/api/#instrument-advisory-parameter-attributes
109
- ...makeQueueMetrics('cosmic_swingset_inbound_queue', 'inbound queue'),
110
- });
111
-
112
53
  const wrapDeltaMS = (finisher, useDeltaMS) => {
113
54
  const startMS = Date.now();
114
55
  return (...finishArgs) => {
@@ -135,9 +76,9 @@ export function makeDefaultMeterProvider() {
135
76
  * @param {string} name
136
77
  */
137
78
  function createHistogram(metricMeter, name) {
138
- const { description, boundaries } = HISTOGRAM_METRICS[name] || {};
79
+ const { boundaries, ...options } = HISTOGRAM_METRICS[name] || {};
139
80
  const advice = boundaries && { explicitBucketBoundaries: boundaries };
140
- return metricMeter.createHistogram(name, { description, advice });
81
+ return metricMeter.createHistogram(name, { ...options, advice });
141
82
  }
142
83
 
143
84
  /**
@@ -224,6 +165,8 @@ export function makeSlogCallbacks({ metricMeter, attributes = {} }) {
224
165
  startup(_method, [vatID], finisher) {
225
166
  return wrapDeltaMS(finisher, deltaMS => {
226
167
  const group = getVatGroup(vatID);
168
+ // DEPRECATED: This should be equivalent to the "seconds" data of
169
+ // "vat-startup-finish" slog entries.
227
170
  getGroupedRecorder('swingset_vat_startup', group).record(deltaMS);
228
171
  });
229
172
  },
@@ -232,10 +175,15 @@ export function makeSlogCallbacks({ metricMeter, attributes = {} }) {
232
175
  finisher,
233
176
  (deltaMS, [[_status, _problem, meterUsage]]) => {
234
177
  const group = getVatGroup(vatID);
178
+ // DEPRECATED: This should be equivalent to the "seconds" data of
179
+ // "deliver-result" slog entries.
235
180
  getGroupedRecorder('swingset_vat_delivery', group).record(deltaMS);
236
181
  const { meterType, ...measurements } = meterUsage || {};
237
182
  for (const [key, value] of Object.entries(measurements)) {
238
183
  if (typeof value === 'object') continue;
184
+ // DEPRECATED: This should be equivalent to the data of
185
+ // "deliver-result" slog entries as the third element of the "dr"
186
+ // array.
239
187
  // TODO: Each measurement key should have its own histogram; there's
240
188
  // no reason to mix e.g. allocate/compute/currentHeapCount.
241
189
  // cf. https://prometheus.io/docs/practices/naming/#metric-names
@@ -322,12 +270,11 @@ function makeInboundQueueMetrics(metricMeter, initialLengths, logger) {
322
270
  // [monotonic] Counters.
323
271
  // But note that the Prometheus representation of the former will be a Gauge:
324
272
  // https://prometheus.io/docs/concepts/metric_types/
325
- for (const [name, { aspect, description }] of Object.entries(QUEUE_METRICS)) {
273
+ for (const [aspect, { name, options }] of Object.entries(inboundQueueMeta)) {
326
274
  const isMonotonic = aspect !== QueueMetricAspect.Length;
327
- const instrumentOptions = { description };
328
275
  const asyncInstrument = isMonotonic
329
- ? metricMeter.createObservableCounter(name, instrumentOptions)
330
- : metricMeter.createObservableUpDownCounter(name, instrumentOptions);
276
+ ? metricMeter.createObservableCounter(name, options)
277
+ : metricMeter.createObservableUpDownCounter(name, options);
331
278
  asyncInstrument.addCallback(observer => {
332
279
  for (const [queueName, value] of counterData[aspect].entries()) {
333
280
  observer.observe(value, { queue: queueName });
@@ -366,7 +313,7 @@ function makeInboundQueueMetrics(metricMeter, initialLengths, logger) {
366
313
  // (the latter is necessary for backwards compatibility until all old
367
314
  // consumers of e.g. slog entries have been updated).
368
315
  const entries = [];
369
- for (const [name, { aspect }] of Object.entries(QUEUE_METRICS)) {
316
+ for (const [aspect, { name }] of Object.entries(inboundQueueMeta)) {
370
317
  let sum = 0;
371
318
  for (const [queueName, value] of counterData[aspect].entries()) {
372
319
  sum += value;
@@ -395,37 +342,27 @@ export function exportKernelStats({
395
342
  attributes = {},
396
343
  initialQueueLengths = /** @type {any} */ ({}),
397
344
  }) {
398
- const kernelStatsMetrics = new Set();
399
- const kernelStatsCounters = new Map();
345
+ /** @type {Set<string>} */
400
346
  const expectedKernelStats = new Set();
347
+ /** @type {Map<string, ObservableCounter | ObservableUpDownCounter>} */
348
+ const kernelStatsCounters = new Map();
401
349
 
402
- function warnUnexpectedKernelStat(key) {
403
- if (!expectedKernelStats.has(key)) {
404
- log.warn(`Unexpected SwingSet kernel statistic`, key);
405
- expectedKernelStats.add(key);
406
- }
407
- }
408
-
409
- let kernelStatsLast = 0;
410
- let kernelStatsCache = {};
411
- const getKernelStats = () => {
412
- const now = Date.now();
413
- if (now - kernelStatsLast < 800) {
414
- return kernelStatsCache;
415
- }
416
- kernelStatsLast = now;
417
- kernelStatsCache = controller.getStats();
418
- for (const key of Object.keys(kernelStatsCache)) {
419
- warnUnexpectedKernelStat(key);
420
- }
421
- return kernelStatsCache;
422
- };
423
-
424
- for (const { key, name, sub, ...options } of KERNEL_STATS_SUM_METRICS) {
350
+ for (const meta of KERNEL_STATS_METRICS) {
351
+ const { key, name, sub, metricType, ...options } = meta;
425
352
  expectedKernelStats.add(key);
353
+ if (metricType === 'gauge') {
354
+ expectedKernelStats.add(`${key}Up`);
355
+ expectedKernelStats.add(`${key}Down`);
356
+ expectedKernelStats.add(`${key}Max`);
357
+ } else if (metricType !== 'counter') {
358
+ Fail`Unknown metric type ${metricType}`;
359
+ }
426
360
  let counter = kernelStatsCounters.get(name);
427
361
  if (!counter) {
428
- counter = metricMeter.createObservableCounter(name, options);
362
+ counter =
363
+ metricType === 'counter'
364
+ ? metricMeter.createObservableCounter(name, options)
365
+ : metricMeter.createObservableUpDownCounter(name, options);
429
366
  kernelStatsCounters.set(name, counter);
430
367
  }
431
368
  const reportedAttributes = { ...attributes };
@@ -435,30 +372,27 @@ export function exportKernelStats({
435
372
  counter.addCallback(observableResult => {
436
373
  observableResult.observe(getKernelStats()[key], reportedAttributes);
437
374
  });
438
- kernelStatsMetrics.add(key);
439
375
  }
440
376
 
441
- for (const { key, name, sub, ...options } of KERNEL_STATS_UPDOWN_METRICS) {
442
- expectedKernelStats.add(key);
443
- expectedKernelStats.add(`${key}Up`);
444
- expectedKernelStats.add(`${key}Down`);
445
- expectedKernelStats.add(`${key}Max`);
446
- let counter = kernelStatsCounters.get(name);
447
- if (!counter) {
448
- counter = metricMeter.createObservableUpDownCounter(name, options);
449
- kernelStatsCounters.set(name, counter);
377
+ const getKernelStats = maxAgeCache(() => {
378
+ const stats = controller.getStats();
379
+
380
+ const notYetFoundKernelStats = new Set(expectedKernelStats.keys());
381
+ for (const key of Object.keys(stats)) {
382
+ notYetFoundKernelStats.delete(key);
383
+ if (!expectedKernelStats.has(key)) {
384
+ log.warn('Unexpected SwingSet kernel statistic', key);
385
+ expectedKernelStats.add(key);
386
+ }
450
387
  }
451
- const reportedAttributes = { ...attributes };
452
- if (sub) {
453
- reportedAttributes[sub.dimension] = sub.value;
388
+ for (const key of notYetFoundKernelStats) {
389
+ log.warn('Expected SwingSet kernel statistic not found', key);
454
390
  }
455
- counter.addCallback(observableResult => {
456
- observableResult.observe(getKernelStats()[key], reportedAttributes);
457
- });
458
- kernelStatsMetrics.add(key);
459
- }
460
391
 
461
- // These are not kernelStatsMetrics, they're outside the kernel.
392
+ return stats;
393
+ }, 800).get;
394
+
395
+ // These are not kernel stats, they're outside the kernel.
462
396
  const inboundQueueMetrics = makeInboundQueueMetrics(
463
397
  metricMeter,
464
398
  initialQueueLengths,
@@ -468,18 +402,11 @@ export function exportKernelStats({
468
402
  // TODO: We probably shouldn't roll our own Node.js process metrics, but a
469
403
  // cursory search for "opentelemetry node.js VM instrumentation" didn't reveal
470
404
  // anything useful.
471
- let heapStatsLast = 0;
472
- let heapStatsCache = v8.getHeapStatistics();
473
- const getHeapStats = () => {
474
- const now = Date.now();
475
- if (now - heapStatsLast >= 800) {
476
- heapStatsLast = now;
477
- heapStatsCache = v8.getHeapStatistics();
478
- }
479
- return heapStatsCache;
480
- };
481
-
482
- for (const key of Object.keys(heapStatsCache)) {
405
+ const cachingHeapStats = maxAgeCache(() => v8.getHeapStatistics(), 800);
406
+ const getHeapStats = cachingHeapStats.get;
407
+ // DEPRECATED: This should be equivalent to the "heapStats" data of
408
+ // "cosmic-swingset-end-block-finish" slog entries.
409
+ for (const key of Object.keys(cachingHeapStats.firstResult)) {
483
410
  const name = `heapStats_${key}`;
484
411
  const options = { description: 'v8 kernel heap statistic' };
485
412
  const counter = metricMeter.createObservableUpDownCounter(name, options);
@@ -488,18 +415,11 @@ export function exportKernelStats({
488
415
  });
489
416
  }
490
417
 
491
- let memoryUsageLast = 0;
492
- let memoryUsageCache = process.memoryUsage();
493
- const getMemoryUsage = () => {
494
- const now = Date.now();
495
- if (now - memoryUsageLast >= 800) {
496
- memoryUsageLast = now;
497
- memoryUsageCache = process.memoryUsage();
498
- }
499
- return memoryUsageCache;
500
- };
501
-
502
- for (const key of Object.keys(memoryUsageCache)) {
418
+ const cachingMemUsage = maxAgeCache(() => process.memoryUsage(), 800);
419
+ const getMemoryUsage = cachingMemUsage.get;
420
+ // DEPRECATED: This should be equivalent to the "memStats" data of
421
+ // "cosmic-swingset-end-block-finish" slog entries.
422
+ for (const key of Object.keys(cachingMemUsage.firstResult)) {
503
423
  const name = `memoryUsage_${key}`;
504
424
  const options = { description: 'kernel process memory statistic' };
505
425
  const counter = metricMeter.createObservableUpDownCounter(name, options);
@@ -508,31 +428,23 @@ export function exportKernelStats({
508
428
  });
509
429
  }
510
430
 
511
- function checkKernelStats(stats) {
512
- const notYetFoundKernelStats = new Set(kernelStatsMetrics.keys());
513
- for (const key of Object.keys(stats)) {
514
- notYetFoundKernelStats.delete(key);
515
- warnUnexpectedKernelStat(key);
516
- }
517
- for (const key of notYetFoundKernelStats) {
518
- log.warn(`Expected SwingSet kernel statistic`, key, `not found`);
519
- }
520
- }
521
-
522
- // We check everything on initialization. Other checks happen when scraping.
523
- checkKernelStats(controller.getStats());
524
-
525
431
  const [schedulerCrankTimeHistogram, schedulerBlockTimeHistogram] = [
526
432
  'swingset_crank_processing_time',
527
433
  'swingset_block_processing_seconds',
528
434
  ].map(name => createHistogram(metricMeter, name));
529
435
 
530
436
  /**
437
+ * @deprecated Histogram measurements produced by this function should be
438
+ * equivalent to the "seconds" data of slog entries
439
+ * (schedulerCrankTimeHistogram via "crank-finish" entries;
440
+ * schedulerBlockTimeHistogram via "cosmic-swingset-run-finish" entries--and
441
+ * note that the latter name is inaccurate anyway because its measurements
442
+ * are produced once per `controller.run()` rather than once per block).
531
443
  * @param {any} policy
532
- * @param {() => number} clock
444
+ * @param {() => number} msClock
533
445
  */
534
- async function crankScheduler(policy, clock = () => Date.now()) {
535
- let now = clock();
446
+ async function crankScheduler(policy, msClock = () => Date.now()) {
447
+ let now = msClock();
536
448
  let crankStart = now;
537
449
  const blockStart = now;
538
450
 
@@ -542,20 +454,20 @@ export function exportKernelStats({
542
454
  const go = policy.crankComplete(details);
543
455
  schedulerCrankTimeHistogram.record(now - crankStart, attributes);
544
456
  crankStart = now;
545
- now = clock();
457
+ now = msClock();
546
458
  return go;
547
459
  },
548
460
  });
549
461
  await controller.run(instrumentedPolicy);
550
462
 
551
- now = Date.now();
463
+ now = msClock();
552
464
  schedulerBlockTimeHistogram.record((now - blockStart) / 1000, attributes);
553
465
  }
554
466
 
555
467
  return {
556
468
  crankScheduler,
469
+ getHeapStats,
470
+ getMemoryUsage,
557
471
  inboundQueueMetrics,
558
- schedulerCrankTimeHistogram,
559
- schedulerBlockTimeHistogram,
560
472
  };
561
473
  }