@libp2p/opentelemetry-metrics 0.0.0-abe9bd154

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.
Files changed (58) hide show
  1. package/README.md +77 -0
  2. package/dist/index.min.js +3 -0
  3. package/dist/src/counter-group.d.ts +12 -0
  4. package/dist/src/counter-group.d.ts.map +1 -0
  5. package/dist/src/counter-group.js +31 -0
  6. package/dist/src/counter-group.js.map +1 -0
  7. package/dist/src/counter.d.ts +9 -0
  8. package/dist/src/counter.d.ts.map +1 -0
  9. package/dist/src/counter.js +13 -0
  10. package/dist/src/counter.js.map +1 -0
  11. package/dist/src/histogram-group.d.ts +11 -0
  12. package/dist/src/histogram-group.d.ts.map +1 -0
  13. package/dist/src/histogram-group.js +27 -0
  14. package/dist/src/histogram-group.js.map +1 -0
  15. package/dist/src/histogram.d.ts +10 -0
  16. package/dist/src/histogram.d.ts.map +1 -0
  17. package/dist/src/histogram.js +19 -0
  18. package/dist/src/histogram.js.map +1 -0
  19. package/dist/src/index.d.ts +67 -0
  20. package/dist/src/index.d.ts.map +1 -0
  21. package/dist/src/index.js +416 -0
  22. package/dist/src/index.js.map +1 -0
  23. package/dist/src/metric-group.d.ts +14 -0
  24. package/dist/src/metric-group.d.ts.map +1 -0
  25. package/dist/src/metric-group.js +58 -0
  26. package/dist/src/metric-group.js.map +1 -0
  27. package/dist/src/metric.d.ts +13 -0
  28. package/dist/src/metric.d.ts.map +1 -0
  29. package/dist/src/metric.js +35 -0
  30. package/dist/src/metric.js.map +1 -0
  31. package/dist/src/summary-group.d.ts +12 -0
  32. package/dist/src/summary-group.d.ts.map +1 -0
  33. package/dist/src/summary-group.js +36 -0
  34. package/dist/src/summary-group.js.map +1 -0
  35. package/dist/src/summary.d.ts +10 -0
  36. package/dist/src/summary.d.ts.map +1 -0
  37. package/dist/src/summary.js +19 -0
  38. package/dist/src/summary.js.map +1 -0
  39. package/dist/src/system-metrics.browser.d.ts +2 -0
  40. package/dist/src/system-metrics.browser.d.ts.map +1 -0
  41. package/dist/src/system-metrics.browser.js +3 -0
  42. package/dist/src/system-metrics.browser.js.map +1 -0
  43. package/dist/src/system-metrics.d.ts +6 -0
  44. package/dist/src/system-metrics.d.ts.map +1 -0
  45. package/dist/src/system-metrics.js +439 -0
  46. package/dist/src/system-metrics.js.map +1 -0
  47. package/package.json +62 -0
  48. package/src/counter-group.ts +38 -0
  49. package/src/counter.ts +18 -0
  50. package/src/histogram-group.ts +34 -0
  51. package/src/histogram.ts +26 -0
  52. package/src/index.ts +557 -0
  53. package/src/metric-group.ts +69 -0
  54. package/src/metric.ts +44 -0
  55. package/src/summary-group.ts +43 -0
  56. package/src/summary.ts +26 -0
  57. package/src/system-metrics.browser.ts +3 -0
  58. package/src/system-metrics.ts +504 -0
@@ -0,0 +1,43 @@
1
+ import type { HistogramGroup, StopTimer } from '@libp2p/interface'
2
+ import type { Gauge } from '@opentelemetry/api'
3
+
4
+ export class OpenTelemetrySummaryGroup implements HistogramGroup {
5
+ private readonly label: string
6
+ private readonly gauge: Gauge
7
+ private readonly lastValues: Record<string, number>
8
+
9
+ constructor (label: string, gauge: Gauge) {
10
+ this.label = label
11
+ this.gauge = gauge
12
+ this.lastValues = {}
13
+ }
14
+
15
+ observe (values: Record<string, number>): void {
16
+ Object.entries(values).forEach(([key, value]) => {
17
+ this.lastValues[key] = value
18
+ this.gauge.record(value, {
19
+ [this.label]: key
20
+ })
21
+ })
22
+ }
23
+
24
+ reset (): void {
25
+ Object.keys(this.lastValues).forEach(key => {
26
+ this.lastValues[key] = 0
27
+ this.gauge.record(0, {
28
+ [this.label]: key
29
+ })
30
+ })
31
+ }
32
+
33
+ timer (key: string): StopTimer {
34
+ const start = Date.now()
35
+
36
+ return () => {
37
+ this.lastValues[key] = Date.now() - start
38
+ this.gauge.record(this.lastValues[key], {
39
+ [this.label]: key
40
+ })
41
+ }
42
+ }
43
+ }
package/src/summary.ts ADDED
@@ -0,0 +1,26 @@
1
+ import type { StopTimer, Summary } from '@libp2p/interface'
2
+ import type { Gauge } from '@opentelemetry/api'
3
+
4
+ export class OpenTelemetrySummary implements Summary {
5
+ private readonly gauge: Gauge
6
+
7
+ constructor (gauge: Gauge) {
8
+ this.gauge = gauge
9
+ }
10
+
11
+ observe (value: number): void {
12
+ this.gauge.record(value)
13
+ }
14
+
15
+ reset (): void {
16
+ this.gauge.record(0)
17
+ }
18
+
19
+ timer (): StopTimer {
20
+ const start = Date.now()
21
+
22
+ return () => {
23
+ this.observe(Date.now() - start)
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,3 @@
1
+ export function collectSystemMetrics (): void {
2
+
3
+ }
@@ -0,0 +1,504 @@
1
+ import { readdirSync, readFileSync } from 'node:fs'
2
+ import { statfs } from 'node:fs/promises'
3
+ import { totalmem } from 'node:os'
4
+ import { monitorEventLoopDelay, PerformanceObserver, constants as PerfHooksConstants } from 'node:perf_hooks'
5
+ import { getHeapSpaceStatistics } from 'node:v8'
6
+ import type { Metrics } from '@libp2p/interface'
7
+
8
+ export interface SystemMetricsOptions {
9
+ statfsLocation?: string
10
+ }
11
+
12
+ export function collectSystemMetrics (metrics: Metrics, init?: SystemMetricsOptions): void {
13
+ metrics.registerMetricGroup('nodejs_memory_usage_bytes', {
14
+ label: 'memory',
15
+ calculate: () => {
16
+ return {
17
+ ...process.memoryUsage()
18
+ }
19
+ }
20
+ })
21
+ const totalMemoryMetric = metrics.registerMetric('nodejs_memory_total_bytes')
22
+ totalMemoryMetric.update(totalmem())
23
+
24
+ metrics.registerMetricGroup('nodejs_fs_usage_bytes', {
25
+ label: 'filesystem',
26
+ calculate: async () => {
27
+ const stats = await statfs(init?.statfsLocation ?? process.cwd())
28
+ const total = stats.bsize * stats.blocks
29
+ const available = stats.bsize * stats.bavail
30
+
31
+ return {
32
+ total,
33
+ free: stats.bsize * stats.bfree,
34
+ available,
35
+ used: (available / total) * 100
36
+ }
37
+ }
38
+ })
39
+
40
+ collectProcessCPUMetrics(metrics)
41
+ collectProcessStartTime(metrics)
42
+ collectMemoryHeap(metrics)
43
+ collectOpenFileDescriptors(metrics)
44
+ collectMaxFileDescriptors(metrics)
45
+ collectEventLoopStats(metrics)
46
+ collectProcessResources(metrics)
47
+ collectProcessHandles(metrics)
48
+ collectProcessRequests(metrics)
49
+ collectHeapSizeAndUsed(metrics)
50
+ collectHeapSpacesSizeAndUsed(metrics)
51
+ collectNodeVersion(metrics)
52
+ collectGcStats(metrics)
53
+ }
54
+
55
+ /**
56
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/processCpuTotal.js
57
+ */
58
+ function collectProcessCPUMetrics (metrics: Metrics): void {
59
+ let lastCpuUsage = process.cpuUsage()
60
+ const cpuUserSecondsTotal = metrics.registerCounter('process_cpu_user_seconds_total', {
61
+ help: 'Total user CPU time spent in seconds.'
62
+ })
63
+ const cpuSystemSecondsTotal = metrics.registerCounter('process_cpu_system_seconds_total', {
64
+ help: 'Total system CPU time spent in seconds.'
65
+ })
66
+
67
+ metrics.registerCounter('process_cpu_seconds_total', {
68
+ help: 'Total user and system CPU time spent in seconds.',
69
+ calculate: () => {
70
+ const cpuUsage = process.cpuUsage()
71
+ const userUsageMicros = cpuUsage.user - lastCpuUsage.user
72
+ const systemUsageMicros = cpuUsage.system - lastCpuUsage.system
73
+ lastCpuUsage = cpuUsage
74
+
75
+ cpuUserSecondsTotal.increment(userUsageMicros / 1e6)
76
+ cpuSystemSecondsTotal.increment(systemUsageMicros / 1e6)
77
+
78
+ return (userUsageMicros + systemUsageMicros) / 1e6
79
+ }
80
+ })
81
+ }
82
+
83
+ /**
84
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/processStartTime.js
85
+ */
86
+ function collectProcessStartTime (metrics: Metrics): void {
87
+ const metric = metrics.registerMetric('process_start_time_seconds', {
88
+ help: 'Start time of the process since unix epoch in seconds.'
89
+ })
90
+
91
+ metric.update(Math.round(Date.now() / 1000 - process.uptime()))
92
+ }
93
+
94
+ /**
95
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/osMemoryHeap.js
96
+ */
97
+ function collectMemoryHeap (metrics: Metrics): void {
98
+ metrics.registerMetric('process_resident_memory_bytes', {
99
+ help: 'Resident memory size in bytes.',
100
+ calculate: () => {
101
+ try {
102
+ return process.memoryUsage().rss
103
+ } catch {}
104
+ return 0
105
+ }
106
+ })
107
+ metrics.registerMetric('process_virtual_memory_bytes', {
108
+ help: 'Virtual memory size in bytes.',
109
+ calculate: () => {
110
+ // this involves doing sync io in prom-client so skip it
111
+ // https://github.com/siimon/prom-client/blob/c1d76c5d497ef803f6bd90c56c713c3fa811c3e0/lib/metrics/osMemoryHeapLinux.js#L53C5-L54C52
112
+ return 0
113
+ }
114
+ })
115
+ metrics.registerMetric('process_heap_bytes', {
116
+ help: 'Process heap size in bytes.',
117
+ calculate: () => {
118
+ try {
119
+ return process.memoryUsage().heapTotal
120
+ } catch {}
121
+ return 0
122
+ }
123
+ })
124
+ }
125
+
126
+ /**
127
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/processOpenFileDescriptors.js
128
+ */
129
+ function collectOpenFileDescriptors (metrics: Metrics): void {
130
+ if (process.platform !== 'linux') {
131
+ return
132
+ }
133
+
134
+ metrics.registerMetric('process_open_fds', {
135
+ help: 'Number of open file descriptors.',
136
+ calculate: () => {
137
+ try {
138
+ const fds = readdirSync('/proc/self/fd')
139
+ // Minus 1 to not count the fd that was used by readdirSync(),
140
+ // it's now closed.
141
+ return fds.length - 1
142
+ } catch {}
143
+
144
+ return 0
145
+ }
146
+ })
147
+ }
148
+
149
+ /**
150
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/processMaxFileDescriptors.js
151
+ */
152
+ function collectMaxFileDescriptors (metrics: Metrics): void {
153
+ let maxFds: number | undefined
154
+
155
+ // This will fail if a linux-like procfs is not available.
156
+ try {
157
+ const limits = readFileSync('/proc/self/limits', 'utf8')
158
+ const lines = limits.split('\n')
159
+ for (const line of lines) {
160
+ if (line.startsWith('Max open files')) {
161
+ const parts = line.split(/ +/)
162
+ maxFds = Number(parts[1])
163
+ break
164
+ }
165
+ }
166
+ } catch {
167
+ return
168
+ }
169
+
170
+ if (maxFds == null) {
171
+ return
172
+ }
173
+
174
+ const metric = metrics.registerMetric('process_max_fds', {
175
+ help: 'Maximum number of open file descriptors.'
176
+ })
177
+ metric.update(maxFds)
178
+ }
179
+
180
+ /**
181
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/eventLoopLag.js
182
+ */
183
+ function collectEventLoopStats (metrics: Metrics): void {
184
+ try {
185
+ const histogram = monitorEventLoopDelay()
186
+ histogram.enable()
187
+
188
+ metrics.registerMetric('nodejs_eventloop_lag_seconds', {
189
+ help: 'Lag of event loop in seconds.',
190
+ calculate: async () => {
191
+ const start = process.hrtime()
192
+
193
+ return new Promise<number>(resolve => {
194
+ setImmediate(() => {
195
+ const delta = process.hrtime(start)
196
+ const nanosec = delta[0] * 1e9 + delta[1]
197
+ const seconds = nanosec / 1e9
198
+
199
+ lagMin.update(histogram.min / 1e9)
200
+ lagMax.update(histogram.max / 1e9)
201
+ lagMean.update(histogram.mean / 1e9)
202
+ lagStddev.update(histogram.stddev / 1e9)
203
+ lagP50.update(histogram.percentile(50) / 1e9)
204
+ lagP90.update(histogram.percentile(90) / 1e9)
205
+ lagP99.update(histogram.percentile(99) / 1e9)
206
+
207
+ histogram.reset()
208
+
209
+ resolve(seconds)
210
+ })
211
+ })
212
+ }
213
+ })
214
+ const lagMin = metrics.registerMetric('nodejs_eventloop_lag_min_seconds', {
215
+ help: 'The minimum recorded event loop delay.'
216
+ })
217
+ const lagMax = metrics.registerMetric('nodejs_eventloop_lag_max_seconds', {
218
+ help: 'The maximum recorded event loop delay.'
219
+ })
220
+ const lagMean = metrics.registerMetric('nodejs_eventloop_lag_mean_seconds', {
221
+ help: 'The mean of the recorded event loop delays.'
222
+ })
223
+ const lagStddev = metrics.registerMetric('nodejs_eventloop_lag_stddev_seconds', {
224
+ help: 'The standard deviation of the recorded event loop delays.'
225
+ })
226
+ const lagP50 = metrics.registerMetric('nodejs_eventloop_lag_p50_seconds', {
227
+ help: 'The 50th percentile of the recorded event loop delays.'
228
+ })
229
+ const lagP90 = metrics.registerMetric('nodejs_eventloop_lag_p90_seconds', {
230
+ help: 'The 90th percentile of the recorded event loop delays.'
231
+ })
232
+ const lagP99 = metrics.registerMetric('nodejs_eventloop_lag_p99_seconds', {
233
+ help: 'The 99th percentile of the recorded event loop delays.'
234
+ })
235
+ } catch (err: any) {
236
+ if (err.code === 'ERR_NOT_IMPLEMENTED') {
237
+ return // Bun
238
+ }
239
+
240
+ throw err
241
+ }
242
+ }
243
+
244
+ /**
245
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/processResources.js
246
+ */
247
+ function collectProcessResources (metrics: Metrics): void {
248
+ // Don't do anything if the function does not exist in previous nodes (exists in node@17.3.0)
249
+ if (typeof process.getActiveResourcesInfo !== 'function') {
250
+ return
251
+ }
252
+
253
+ metrics.registerMetricGroup('nodejs_active_resources', {
254
+ help: 'Number of active resources that are currently keeping the event loop alive, grouped by async resource type.',
255
+ label: 'type',
256
+ calculate: () => {
257
+ const resources = process.getActiveResourcesInfo()
258
+
259
+ const data: Record<string, number> = {}
260
+
261
+ for (let i = 0; i < resources.length; i++) {
262
+ const resource = resources[i]
263
+
264
+ if (Object.hasOwn(data, resource)) {
265
+ data[resource] += 1
266
+ } else {
267
+ data[resource] = 1
268
+ }
269
+ }
270
+
271
+ return data
272
+ }
273
+ })
274
+
275
+ metrics.registerMetric('nodejs_active_resources_total', {
276
+ help: 'Total number of active resources.',
277
+ calculate: () => {
278
+ const resources = process.getActiveResourcesInfo()
279
+
280
+ return resources.length
281
+ }
282
+ })
283
+ }
284
+
285
+ /**
286
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/processHandles.js
287
+ */
288
+ function collectProcessHandles (metrics: Metrics): void {
289
+ // Don't do anything if the function is removed in later nodes (exists in node@6-12...)
290
+ // @ts-expect-error not part of the public API
291
+ if (typeof process._getActiveHandles !== 'function') {
292
+ return
293
+ }
294
+
295
+ metrics.registerMetricGroup('nodejs_active_handles', {
296
+ help: 'Number of active libuv handles grouped by handle type. Every handle type is C++ class name.',
297
+ label: 'type',
298
+ calculate: () => {
299
+ // @ts-expect-error not part of the public API
300
+ const resources = process._getActiveHandles()
301
+
302
+ const data: Record<string, number> = {}
303
+
304
+ for (let i = 0; i < resources.length; i++) {
305
+ const listElement = resources[i]
306
+
307
+ if (listElement == null || typeof listElement.constructor === 'undefined') {
308
+ continue
309
+ }
310
+
311
+ if (Object.hasOwnProperty.call(data, listElement.constructor.name)) {
312
+ data[listElement.constructor.name] += 1
313
+ } else {
314
+ data[listElement.constructor.name] = 1
315
+ }
316
+ }
317
+
318
+ return data
319
+ }
320
+ })
321
+
322
+ metrics.registerMetric('nodejs_active_handles_total', {
323
+ help: 'Total number of active handles.',
324
+ calculate: () => {
325
+ // @ts-expect-error not part of the public API
326
+ const resources = process._getActiveHandles()
327
+
328
+ return resources.length
329
+ }
330
+ })
331
+ }
332
+
333
+ /**
334
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/processRequests.js
335
+ */
336
+ function collectProcessRequests (metrics: Metrics): void {
337
+ // Don't do anything if the function is removed in later nodes (exists in node@6)
338
+ // @ts-expect-error not part of the public API
339
+ if (typeof process._getActiveRequests !== 'function') {
340
+ return
341
+ }
342
+
343
+ metrics.registerMetricGroup('nodejs_active_requests', {
344
+ help: 'Number of active libuv requests grouped by request type. Every request type is C++ class name.',
345
+ label: 'type',
346
+ calculate: () => {
347
+ // @ts-expect-error not part of the public API
348
+ const resources = process._getActiveRequests()
349
+
350
+ const data: Record<string, number> = {}
351
+
352
+ for (let i = 0; i < resources.length; i++) {
353
+ const listElement = resources[i]
354
+
355
+ if (listElement == null || typeof listElement.constructor === 'undefined') {
356
+ continue
357
+ }
358
+
359
+ if (Object.hasOwnProperty.call(data, listElement.constructor.name)) {
360
+ data[listElement.constructor.name] += 1
361
+ } else {
362
+ data[listElement.constructor.name] = 1
363
+ }
364
+ }
365
+
366
+ return data
367
+ }
368
+ })
369
+
370
+ metrics.registerMetric('nodejs_active_requests_total', {
371
+ help: 'Total number of active requests.',
372
+ calculate: () => {
373
+ // @ts-expect-error not part of the public API
374
+ const resources = process._getActiveRequests()
375
+
376
+ return resources.length
377
+ }
378
+ })
379
+ }
380
+
381
+ /**
382
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/heapSizeAndUsed.js
383
+ */
384
+ function collectHeapSizeAndUsed (metrics: Metrics): void {
385
+ const heapSizeUsed = metrics.registerMetric('nodejs_heap_size_used_bytes', {
386
+ help: 'Process heap size used from Node.js in bytes.'
387
+ })
388
+ const externalMemUsed = metrics.registerMetric('nodejs_external_memory_bytes', {
389
+ help: 'Node.js external memory size in bytes.'
390
+ })
391
+
392
+ metrics.registerMetric('nodejs_heap_size_total_bytes', {
393
+ help: 'Process heap size from Node.js in bytes.',
394
+ calculate: () => {
395
+ try {
396
+ const memUsage = process.memoryUsage()
397
+
398
+ heapSizeUsed.update(memUsage.heapUsed)
399
+ if (memUsage.external !== undefined) {
400
+ externalMemUsed.update(memUsage.external)
401
+ }
402
+
403
+ return memUsage.heapTotal
404
+ } catch {}
405
+
406
+ return 0
407
+ }
408
+ })
409
+ }
410
+
411
+ /**
412
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/heapSpacesSizeAndUsed.js
413
+ */
414
+ function collectHeapSpacesSizeAndUsed (metrics: Metrics): void {
415
+ try {
416
+ getHeapSpaceStatistics()
417
+ } catch (err: any) {
418
+ if (err.code === 'ERR_NOT_IMPLEMENTED') {
419
+ return // Bun
420
+ }
421
+ throw err
422
+ }
423
+
424
+ const used = metrics.registerMetricGroup('nodejs_heap_space_size_used_bytes', {
425
+ help: 'Process heap space size used from Node.js in bytes.',
426
+ label: 'space'
427
+ })
428
+ const available = metrics.registerMetricGroup('nodejs_heap_space_size_available_bytes', {
429
+ help: 'Process heap space size available from Node.js in bytes.',
430
+ label: 'space'
431
+ })
432
+
433
+ metrics.registerMetricGroup('nodejs_heap_space_size_total_bytes', {
434
+ help: 'Process heap space size total from Node.js in bytes.',
435
+ label: 'space',
436
+ calculate: () => {
437
+ const data: Record<string, number> = {}
438
+
439
+ for (const space of getHeapSpaceStatistics()) {
440
+ const spaceName = space.space_name.substr(0, space.space_name.indexOf('_space'))
441
+
442
+ used.update({
443
+ [spaceName]: space.space_used_size
444
+ })
445
+
446
+ available.update({
447
+ [spaceName]: space.space_available_size
448
+ })
449
+
450
+ data[spaceName] = space.space_size
451
+ }
452
+
453
+ return data
454
+ }
455
+ })
456
+ }
457
+
458
+ /**
459
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/version.js
460
+ */
461
+ function collectNodeVersion (metrics: Metrics): void {
462
+ const metric = metrics.registerMetricGroup('nodejs_version_info', {
463
+ help: 'Node.js version info.'
464
+ })
465
+
466
+ const version = process.version
467
+ const versionSegments = version.slice(1).split('.').map(Number)
468
+
469
+ // @ts-expect-error use internal API to get same result as prom-client
470
+ metric.gauge.record(1, {
471
+ version,
472
+ major: versionSegments[0],
473
+ minor: versionSegments[1],
474
+ patch: versionSegments[2]
475
+ })
476
+ }
477
+
478
+ /**
479
+ * @see https://github.com/siimon/prom-client/blob/master/lib/metrics/gc.js
480
+ */
481
+ function collectGcStats (metrics: Metrics): void {
482
+ const histogram = metrics.registerHistogramGroup('nodejs_gc_duration_seconds_bucket', {
483
+ buckets: [0.001, 0.01, 0.1, 1, 2, 5],
484
+ label: 'kind'
485
+ })
486
+
487
+ const kinds: string[] = []
488
+ kinds[PerfHooksConstants.NODE_PERFORMANCE_GC_MAJOR] = 'major'
489
+ kinds[PerfHooksConstants.NODE_PERFORMANCE_GC_MINOR] = 'minor'
490
+ kinds[PerfHooksConstants.NODE_PERFORMANCE_GC_INCREMENTAL] = 'incremental'
491
+ kinds[PerfHooksConstants.NODE_PERFORMANCE_GC_WEAKCB] = 'weakcb'
492
+
493
+ const obs = new PerformanceObserver(list => {
494
+ const entry = list.getEntries()[0]
495
+ // @ts-expect-error types are incomplete
496
+ const kind = kinds[entry.detail.kind]
497
+ // Convert duration from milliseconds to seconds
498
+ histogram.observe({
499
+ [kind]: entry.duration / 1000
500
+ })
501
+ })
502
+
503
+ obs.observe({ entryTypes: ['gc'] })
504
+ }