@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.
- package/README.md +77 -0
- package/dist/index.min.js +3 -0
- package/dist/src/counter-group.d.ts +12 -0
- package/dist/src/counter-group.d.ts.map +1 -0
- package/dist/src/counter-group.js +31 -0
- package/dist/src/counter-group.js.map +1 -0
- package/dist/src/counter.d.ts +9 -0
- package/dist/src/counter.d.ts.map +1 -0
- package/dist/src/counter.js +13 -0
- package/dist/src/counter.js.map +1 -0
- package/dist/src/histogram-group.d.ts +11 -0
- package/dist/src/histogram-group.d.ts.map +1 -0
- package/dist/src/histogram-group.js +27 -0
- package/dist/src/histogram-group.js.map +1 -0
- package/dist/src/histogram.d.ts +10 -0
- package/dist/src/histogram.d.ts.map +1 -0
- package/dist/src/histogram.js +19 -0
- package/dist/src/histogram.js.map +1 -0
- package/dist/src/index.d.ts +67 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +416 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/metric-group.d.ts +14 -0
- package/dist/src/metric-group.d.ts.map +1 -0
- package/dist/src/metric-group.js +58 -0
- package/dist/src/metric-group.js.map +1 -0
- package/dist/src/metric.d.ts +13 -0
- package/dist/src/metric.d.ts.map +1 -0
- package/dist/src/metric.js +35 -0
- package/dist/src/metric.js.map +1 -0
- package/dist/src/summary-group.d.ts +12 -0
- package/dist/src/summary-group.d.ts.map +1 -0
- package/dist/src/summary-group.js +36 -0
- package/dist/src/summary-group.js.map +1 -0
- package/dist/src/summary.d.ts +10 -0
- package/dist/src/summary.d.ts.map +1 -0
- package/dist/src/summary.js +19 -0
- package/dist/src/summary.js.map +1 -0
- package/dist/src/system-metrics.browser.d.ts +2 -0
- package/dist/src/system-metrics.browser.d.ts.map +1 -0
- package/dist/src/system-metrics.browser.js +3 -0
- package/dist/src/system-metrics.browser.js.map +1 -0
- package/dist/src/system-metrics.d.ts +6 -0
- package/dist/src/system-metrics.d.ts.map +1 -0
- package/dist/src/system-metrics.js +439 -0
- package/dist/src/system-metrics.js.map +1 -0
- package/package.json +62 -0
- package/src/counter-group.ts +38 -0
- package/src/counter.ts +18 -0
- package/src/histogram-group.ts +34 -0
- package/src/histogram.ts +26 -0
- package/src/index.ts +557 -0
- package/src/metric-group.ts +69 -0
- package/src/metric.ts +44 -0
- package/src/summary-group.ts +43 -0
- package/src/summary.ts +26 -0
- package/src/system-metrics.browser.ts +3 -0
- package/src/system-metrics.ts +504 -0
package/src/histogram.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Histogram, StopTimer } from '@libp2p/interface'
|
|
2
|
+
import type { Histogram as OTelHistogram } from '@opentelemetry/api'
|
|
3
|
+
|
|
4
|
+
export class OpenTelemetryHistogram implements Histogram {
|
|
5
|
+
private readonly histogram: OTelHistogram
|
|
6
|
+
|
|
7
|
+
constructor (histogram: OTelHistogram) {
|
|
8
|
+
this.histogram = histogram
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
observe (value: number): void {
|
|
12
|
+
this.histogram.record(value)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
reset (): void {
|
|
16
|
+
this.histogram.record(0)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
timer (): StopTimer {
|
|
20
|
+
const start = Date.now()
|
|
21
|
+
|
|
22
|
+
return () => {
|
|
23
|
+
this.observe(Date.now() - start)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
*
|
|
4
|
+
* Uses [OpenTelemetry](https://opentelemetry.io/) to store metrics and method
|
|
5
|
+
* traces in libp2p.
|
|
6
|
+
*
|
|
7
|
+
* @example Node.js
|
|
8
|
+
*
|
|
9
|
+
* Use with [OpenTelemetry Desktop Viewer](https://github.com/CtrlSpice/otel-desktop-viewer):
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createLibp2p } from 'libp2p'
|
|
13
|
+
* import { openTelemetryMetrics } from '@libp2p/opentelemetry-metrics'
|
|
14
|
+
* import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'
|
|
15
|
+
* import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
|
16
|
+
* import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
17
|
+
*
|
|
18
|
+
* const sdk = new NodeSDK({
|
|
19
|
+
* traceExporter: new OTLPTraceExporter({
|
|
20
|
+
* url: 'http://127.0.0.1:4318/v1/traces'
|
|
21
|
+
* }),
|
|
22
|
+
* metricReader: new PrometheusExporter({
|
|
23
|
+
* port: 9464
|
|
24
|
+
* }),
|
|
25
|
+
* serviceName: 'my-app'
|
|
26
|
+
* })
|
|
27
|
+
* sdk.start()
|
|
28
|
+
*
|
|
29
|
+
* const node = await createLibp2p({
|
|
30
|
+
* // ... other options
|
|
31
|
+
* metrics: openTelemetryMetrics()
|
|
32
|
+
* })
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { InvalidParametersError, serviceCapabilities } from '@libp2p/interface'
|
|
37
|
+
import { trace, metrics, context, SpanStatusCode } from '@opentelemetry/api'
|
|
38
|
+
import each from 'it-foreach'
|
|
39
|
+
import { OpenTelemetryCounterGroup } from './counter-group.js'
|
|
40
|
+
import { OpenTelemetryCounter } from './counter.js'
|
|
41
|
+
import { OpenTelemetryHistogramGroup } from './histogram-group.js'
|
|
42
|
+
import { OpenTelemetryHistogram } from './histogram.js'
|
|
43
|
+
import { OpenTelemetryMetricGroup } from './metric-group.js'
|
|
44
|
+
import { OpenTelemetryMetric } from './metric.js'
|
|
45
|
+
import { OpenTelemetrySummaryGroup } from './summary-group.js'
|
|
46
|
+
import { OpenTelemetrySummary } from './summary.js'
|
|
47
|
+
import { collectSystemMetrics } from './system-metrics.js'
|
|
48
|
+
import type { MultiaddrConnection, Stream, Connection, Metric, MetricGroup, Metrics, CalculatedMetricOptions, MetricOptions, Counter, CounterGroup, Histogram, HistogramOptions, HistogramGroup, Summary, SummaryOptions, SummaryGroup, CalculatedHistogramOptions, CalculatedSummaryOptions, NodeInfo, TraceFunctionOptions, TraceGeneratorFunctionOptions, TraceAttributes } from '@libp2p/interface'
|
|
49
|
+
import type { Span, Attributes } from '@opentelemetry/api'
|
|
50
|
+
import type { Duplex } from 'it-stream-types'
|
|
51
|
+
|
|
52
|
+
// see https://betterstack.com/community/guides/observability/opentelemetry-metrics-nodejs/#prerequisites
|
|
53
|
+
|
|
54
|
+
export interface OpenTelemetryComponents {
|
|
55
|
+
nodeInfo: NodeInfo
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface OpenTelemetryMetricsInit {
|
|
59
|
+
/**
|
|
60
|
+
* The app name used to create the tracer
|
|
61
|
+
*
|
|
62
|
+
* @default 'js-libp2p'
|
|
63
|
+
*/
|
|
64
|
+
appName?: string
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The app version used to create the tracer.
|
|
68
|
+
*
|
|
69
|
+
* The version number of the running version of libp2p is used as the default.
|
|
70
|
+
*/
|
|
71
|
+
appVersion?: string
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* On Node.js platforms the current filesystem usage is reported as the metric
|
|
75
|
+
* `nodejs_fs_usage_bytes` using the `statfs` function from `node:fs` - the
|
|
76
|
+
* default location to stat is the current working directory, configured this
|
|
77
|
+
* location here
|
|
78
|
+
*/
|
|
79
|
+
statfsLocation?: string
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The meter name used for creating metrics
|
|
83
|
+
*
|
|
84
|
+
* @default 'js-libp2p'
|
|
85
|
+
*/
|
|
86
|
+
meterName?: string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class OpenTelemetryMetrics implements Metrics {
|
|
90
|
+
private transferStats: Map<string, number>
|
|
91
|
+
private readonly tracer: ReturnType<typeof trace.getTracer>
|
|
92
|
+
private readonly meterName: string
|
|
93
|
+
|
|
94
|
+
constructor (components: OpenTelemetryComponents, init?: OpenTelemetryMetricsInit) {
|
|
95
|
+
this.tracer = trace.getTracer(init?.appName ?? components.nodeInfo.name, init?.appVersion ?? components.nodeInfo.version)
|
|
96
|
+
|
|
97
|
+
// holds global and per-protocol sent/received stats
|
|
98
|
+
this.transferStats = new Map()
|
|
99
|
+
this.meterName = init?.meterName ?? components.nodeInfo.name
|
|
100
|
+
|
|
101
|
+
this.registerCounterGroup('libp2p_data_transfer_bytes_total', {
|
|
102
|
+
label: 'protocol',
|
|
103
|
+
calculate: () => {
|
|
104
|
+
const output: Record<string, number> = {}
|
|
105
|
+
|
|
106
|
+
for (const [key, value] of this.transferStats.entries()) {
|
|
107
|
+
output[key] = value
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// reset counts for next time
|
|
111
|
+
this.transferStats = new Map()
|
|
112
|
+
|
|
113
|
+
return output
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
collectSystemMetrics(this, init)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
readonly [Symbol.toStringTag] = '@libp2p/metrics-opentelemetry'
|
|
121
|
+
|
|
122
|
+
readonly [serviceCapabilities]: string[] = [
|
|
123
|
+
'@libp2p/metrics'
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Increment the transfer stat for the passed key, making sure
|
|
128
|
+
* it exists first
|
|
129
|
+
*/
|
|
130
|
+
_incrementValue (key: string, value: number): void {
|
|
131
|
+
const existing = this.transferStats.get(key) ?? 0
|
|
132
|
+
|
|
133
|
+
this.transferStats.set(key, existing + value)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Override the sink/source of the stream to count the bytes
|
|
138
|
+
* in and out
|
|
139
|
+
*/
|
|
140
|
+
_track (stream: Duplex<AsyncGenerator<any>>, name: string): void {
|
|
141
|
+
const self = this
|
|
142
|
+
|
|
143
|
+
const sink = stream.sink
|
|
144
|
+
stream.sink = async function trackedSink (source) {
|
|
145
|
+
await sink(each(source, buf => {
|
|
146
|
+
self._incrementValue(`${name} sent`, buf.byteLength)
|
|
147
|
+
}))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const source = stream.source
|
|
151
|
+
stream.source = each(source, buf => {
|
|
152
|
+
self._incrementValue(`${name} received`, buf.byteLength)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
trackMultiaddrConnection (maConn: MultiaddrConnection): void {
|
|
157
|
+
this._track(maConn, 'global')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
trackProtocolStream (stream: Stream, connection: Connection): void {
|
|
161
|
+
if (stream.protocol == null) {
|
|
162
|
+
// protocol not negotiated yet, should not happen as the upgrader
|
|
163
|
+
// calls this handler after protocol negotiation
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this._track(stream, stream.protocol)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
registerMetric (name: string, opts: CalculatedMetricOptions): void
|
|
171
|
+
registerMetric (name: string, opts?: MetricOptions): Metric
|
|
172
|
+
registerMetric (name: string, opts: CalculatedMetricOptions | MetricOptions = {}): any {
|
|
173
|
+
if (name == null || name.trim() === '') {
|
|
174
|
+
throw new InvalidParametersError('Metric name is required')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const meter = metrics.getMeterProvider().getMeter(this.meterName)
|
|
178
|
+
|
|
179
|
+
if (isCalculatedMetricOptions<CalculatedMetricOptions>(opts)) {
|
|
180
|
+
const calculate = opts.calculate
|
|
181
|
+
const counter = meter.createObservableGauge(name, {
|
|
182
|
+
description: opts?.help ?? name
|
|
183
|
+
})
|
|
184
|
+
counter.addCallback(async (result) => {
|
|
185
|
+
result.observe(await calculate())
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return new OpenTelemetryMetric(meter.createGauge(name, {
|
|
192
|
+
description: opts?.help ?? name
|
|
193
|
+
}))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
registerMetricGroup (name: string, opts: CalculatedMetricOptions<Record<string, number>>): void
|
|
197
|
+
registerMetricGroup (name: string, opts?: MetricOptions): MetricGroup
|
|
198
|
+
registerMetricGroup (name: string, opts: CalculatedMetricOptions | MetricOptions = {}): any {
|
|
199
|
+
if (name == null || name.trim() === '') {
|
|
200
|
+
throw new InvalidParametersError('Metric name is required')
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const meter = metrics.getMeterProvider().getMeter(this.meterName)
|
|
204
|
+
const label = opts?.label ?? name
|
|
205
|
+
|
|
206
|
+
if (isCalculatedMetricOptions<CalculatedMetricOptions<Record<string, number>>>(opts)) {
|
|
207
|
+
const calculate = opts.calculate
|
|
208
|
+
const gauge = meter.createObservableGauge(name, {
|
|
209
|
+
description: opts?.help ?? name
|
|
210
|
+
})
|
|
211
|
+
gauge.addCallback(async (observable) => {
|
|
212
|
+
const observed = await calculate()
|
|
213
|
+
|
|
214
|
+
for (const [key, value] of Object.entries(observed)) {
|
|
215
|
+
observable.observe(value, {
|
|
216
|
+
[label]: key
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return new OpenTelemetryMetricGroup(label, meter.createGauge(name, {
|
|
225
|
+
description: opts?.help ?? name
|
|
226
|
+
}))
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
registerCounter (name: string, opts: CalculatedMetricOptions): void
|
|
230
|
+
registerCounter (name: string, opts?: MetricOptions): Counter
|
|
231
|
+
registerCounter (name: string, opts: CalculatedMetricOptions | MetricOptions = {}): any {
|
|
232
|
+
if (name == null || name.trim() === '') {
|
|
233
|
+
throw new InvalidParametersError('Metric name is required')
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const meter = metrics.getMeterProvider().getMeter(this.meterName)
|
|
237
|
+
|
|
238
|
+
if (isCalculatedMetricOptions<CalculatedMetricOptions>(opts)) {
|
|
239
|
+
const calculate = opts.calculate
|
|
240
|
+
const counter = meter.createObservableCounter(name, {
|
|
241
|
+
description: opts?.help ?? name
|
|
242
|
+
})
|
|
243
|
+
counter.addCallback(async (result) => {
|
|
244
|
+
result.observe(await calculate())
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return new OpenTelemetryCounter(meter.createCounter(name, {
|
|
251
|
+
description: opts?.help ?? name
|
|
252
|
+
}))
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
registerCounterGroup (name: string, opts: CalculatedMetricOptions<Record<string, number>>): void
|
|
256
|
+
registerCounterGroup (name: string, opts?: MetricOptions): CounterGroup
|
|
257
|
+
registerCounterGroup (name: string, opts: CalculatedMetricOptions | MetricOptions = {}): any {
|
|
258
|
+
if (name == null || name.trim() === '') {
|
|
259
|
+
throw new InvalidParametersError('Metric name is required')
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const meter = metrics.getMeterProvider().getMeter(this.meterName)
|
|
263
|
+
const label = opts?.label ?? name
|
|
264
|
+
|
|
265
|
+
if (isCalculatedMetricOptions<CalculatedMetricOptions<Record<string, number>>>(opts)) {
|
|
266
|
+
const values: Record<string, number> = {}
|
|
267
|
+
const calculate = opts.calculate
|
|
268
|
+
const counter = meter.createObservableGauge(name, {
|
|
269
|
+
description: opts?.help ?? name
|
|
270
|
+
})
|
|
271
|
+
counter.addCallback(async (observable) => {
|
|
272
|
+
const observed = await calculate()
|
|
273
|
+
|
|
274
|
+
for (const [key, value] of Object.entries(observed)) {
|
|
275
|
+
if (values[key] == null) {
|
|
276
|
+
values[key] = 0
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
values[key] += value
|
|
280
|
+
|
|
281
|
+
observable.observe(values[key], {
|
|
282
|
+
[label]: key
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return new OpenTelemetryCounterGroup(label, meter.createCounter(name, {
|
|
291
|
+
description: opts?.help ?? name
|
|
292
|
+
}))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
registerHistogram (name: string, opts: CalculatedHistogramOptions): void
|
|
296
|
+
registerHistogram (name: string, opts?: HistogramOptions): Histogram
|
|
297
|
+
registerHistogram (name: string, opts: CalculatedHistogramOptions | HistogramOptions = {}): any {
|
|
298
|
+
if (name == null || name.trim() === '') {
|
|
299
|
+
throw new InvalidParametersError('Metric name is required')
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const meter = metrics.getMeterProvider().getMeter(this.meterName)
|
|
303
|
+
|
|
304
|
+
if (isCalculatedMetricOptions<CalculatedHistogramOptions>(opts)) {
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return new OpenTelemetryHistogram(meter.createHistogram(name, {
|
|
309
|
+
advice: {
|
|
310
|
+
explicitBucketBoundaries: opts.buckets
|
|
311
|
+
},
|
|
312
|
+
description: opts?.help ?? name
|
|
313
|
+
}))
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
registerHistogramGroup (name: string, opts: CalculatedHistogramOptions<Record<string, number>>): void
|
|
317
|
+
registerHistogramGroup (name: string, opts?: HistogramOptions): HistogramGroup
|
|
318
|
+
registerHistogramGroup (name: string, opts: CalculatedHistogramOptions | HistogramOptions = {}): any {
|
|
319
|
+
if (name == null || name.trim() === '') {
|
|
320
|
+
throw new InvalidParametersError('Metric name is required')
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const meter = metrics.getMeterProvider().getMeter(this.meterName)
|
|
324
|
+
const label = opts?.label ?? name
|
|
325
|
+
|
|
326
|
+
if (isCalculatedMetricOptions<CalculatedHistogramOptions<Record<string, number>>>(opts)) {
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return new OpenTelemetryHistogramGroup(label, meter.createHistogram(name, {
|
|
331
|
+
advice: {
|
|
332
|
+
explicitBucketBoundaries: opts.buckets
|
|
333
|
+
},
|
|
334
|
+
description: opts?.help ?? name
|
|
335
|
+
}))
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
registerSummary (name: string, opts: CalculatedSummaryOptions): void
|
|
339
|
+
registerSummary (name: string, opts?: SummaryOptions): Summary
|
|
340
|
+
registerSummary (name: string, opts: CalculatedSummaryOptions | SummaryOptions = {}): any {
|
|
341
|
+
if (name == null || name.trim() === '') {
|
|
342
|
+
throw new InvalidParametersError('Metric name is required')
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const meter = metrics.getMeterProvider().getMeter(this.meterName)
|
|
346
|
+
|
|
347
|
+
if (isCalculatedMetricOptions<CalculatedHistogramOptions>(opts)) {
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return new OpenTelemetrySummary(meter.createGauge(name, {
|
|
352
|
+
description: opts?.help ?? name
|
|
353
|
+
}))
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
registerSummaryGroup (name: string, opts: CalculatedSummaryOptions<Record<string, number>>): void
|
|
357
|
+
registerSummaryGroup (name: string, opts?: SummaryOptions): SummaryGroup
|
|
358
|
+
registerSummaryGroup (name: string, opts: CalculatedSummaryOptions | SummaryOptions = {}): any {
|
|
359
|
+
if (name == null || name.trim() === '') {
|
|
360
|
+
throw new InvalidParametersError('Metric name is required')
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const meter = metrics.getMeterProvider().getMeter(this.meterName)
|
|
364
|
+
const label = opts?.label ?? name
|
|
365
|
+
|
|
366
|
+
if (isCalculatedMetricOptions<CalculatedSummaryOptions>(opts)) {
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return new OpenTelemetrySummaryGroup(label, meter.createGauge(name, {
|
|
371
|
+
description: opts?.help ?? name
|
|
372
|
+
}))
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
createTrace (): any {
|
|
376
|
+
return context.active()
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
traceFunction <F extends (...args: any[]) => any> (name: string, fn: F, options?: TraceFunctionOptions<Parameters<F>, ReturnType<F>>): F {
|
|
380
|
+
// @ts-expect-error returned function could be different to T
|
|
381
|
+
return (...args: Parameters<F>): any => {
|
|
382
|
+
const optionsIndex = options?.optionsIndex ?? 0
|
|
383
|
+
// make sure we have an options object
|
|
384
|
+
const opts = {
|
|
385
|
+
...(args[optionsIndex] ?? {})
|
|
386
|
+
}
|
|
387
|
+
args[optionsIndex] = opts
|
|
388
|
+
|
|
389
|
+
// skip tracing if no context is passed
|
|
390
|
+
if (opts.trace == null) {
|
|
391
|
+
return fn.apply(null, args)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const attributes = {}
|
|
395
|
+
|
|
396
|
+
// extract the parent context from the options object
|
|
397
|
+
const parentContext = opts.trace
|
|
398
|
+
const span = this.tracer.startSpan(name, {
|
|
399
|
+
attributes: options?.getAttributesFromArgs?.(args, attributes)
|
|
400
|
+
}, parentContext)
|
|
401
|
+
|
|
402
|
+
const childContext = trace.setSpan(parentContext, span)
|
|
403
|
+
opts.trace = childContext
|
|
404
|
+
let result: any
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
result = context.with(childContext, fn, undefined, ...args)
|
|
408
|
+
} catch (err: any) {
|
|
409
|
+
span.recordException(err)
|
|
410
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err.toString() })
|
|
411
|
+
span.end()
|
|
412
|
+
throw err
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (isPromise(result)) {
|
|
416
|
+
return wrapPromise(result, span, attributes, options)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (isGenerator(result)) {
|
|
420
|
+
return wrapGenerator(result, span, attributes, options)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (isAsyncGenerator(result)) {
|
|
424
|
+
return wrapAsyncGenerator(result, span, attributes, options)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
setAttributes(span, options?.getAttributesFromReturnValue?.(result, attributes))
|
|
428
|
+
|
|
429
|
+
span.setStatus({ code: SpanStatusCode.OK })
|
|
430
|
+
span.end()
|
|
431
|
+
|
|
432
|
+
return result
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function openTelemetryMetrics (init: OpenTelemetryMetricsInit = {}): (components: OpenTelemetryComponents) => Metrics {
|
|
438
|
+
return (components: OpenTelemetryComponents) => new OpenTelemetryMetrics(components, init)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function isPromise <T = any> (obj?: any): obj is Promise<T> {
|
|
442
|
+
return typeof obj?.then === 'function'
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function wrapPromise (promise: Promise<any>, span: Span, attributes: TraceAttributes, options?: TraceFunctionOptions<any, any>): Promise<any> {
|
|
446
|
+
return promise
|
|
447
|
+
.then(res => {
|
|
448
|
+
setAttributes(span, options?.getAttributesFromReturnValue?.(res, attributes))
|
|
449
|
+
span.setStatus({ code: SpanStatusCode.OK })
|
|
450
|
+
return res
|
|
451
|
+
})
|
|
452
|
+
.catch(err => {
|
|
453
|
+
span.recordException(err)
|
|
454
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err.toString() })
|
|
455
|
+
})
|
|
456
|
+
.finally(() => {
|
|
457
|
+
span.end()
|
|
458
|
+
})
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function isGenerator (obj?: any): obj is Generator {
|
|
462
|
+
return obj?.[Symbol.iterator] != null
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function wrapGenerator (gen: Generator, span: Span, attributes: TraceAttributes, options?: TraceGeneratorFunctionOptions<any, any, any>): Generator {
|
|
466
|
+
const iter = gen[Symbol.iterator]()
|
|
467
|
+
let index = 0
|
|
468
|
+
|
|
469
|
+
const wrapped: Generator = {
|
|
470
|
+
next: () => {
|
|
471
|
+
try {
|
|
472
|
+
const res = iter.next()
|
|
473
|
+
|
|
474
|
+
if (res.done === true) {
|
|
475
|
+
setAttributes(span, options?.getAttributesFromReturnValue?.(res.value, attributes))
|
|
476
|
+
span.setStatus({ code: SpanStatusCode.OK })
|
|
477
|
+
span.end()
|
|
478
|
+
} else {
|
|
479
|
+
setAttributes(span, options?.getAttributesFromYieldedValue?.(res.value, attributes, ++index))
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return res
|
|
483
|
+
} catch (err: any) {
|
|
484
|
+
span.recordException(err)
|
|
485
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err.toString() })
|
|
486
|
+
span.end()
|
|
487
|
+
|
|
488
|
+
throw err
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
return: (value) => {
|
|
492
|
+
return iter.return(value)
|
|
493
|
+
},
|
|
494
|
+
throw: (err) => {
|
|
495
|
+
return iter.throw(err)
|
|
496
|
+
},
|
|
497
|
+
[Symbol.iterator]: () => {
|
|
498
|
+
return wrapped
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return wrapped
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function isAsyncGenerator (obj?: any): obj is AsyncGenerator {
|
|
506
|
+
return obj?.[Symbol.asyncIterator] != null
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function wrapAsyncGenerator (gen: AsyncGenerator, span: Span, attributes: TraceAttributes, options?: TraceGeneratorFunctionOptions<any, any, any>): AsyncGenerator {
|
|
510
|
+
const iter = gen[Symbol.asyncIterator]()
|
|
511
|
+
let index = 0
|
|
512
|
+
|
|
513
|
+
const wrapped: AsyncGenerator = {
|
|
514
|
+
next: async () => {
|
|
515
|
+
try {
|
|
516
|
+
const res = await iter.next()
|
|
517
|
+
|
|
518
|
+
if (res.done === true) {
|
|
519
|
+
setAttributes(span, options?.getAttributesFromReturnValue?.(res.value, attributes))
|
|
520
|
+
span.setStatus({ code: SpanStatusCode.OK })
|
|
521
|
+
span.end()
|
|
522
|
+
} else {
|
|
523
|
+
setAttributes(span, options?.getAttributesFromYieldedValue?.(res.value, attributes, ++index))
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return res
|
|
527
|
+
} catch (err: any) {
|
|
528
|
+
span.recordException(err)
|
|
529
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err.toString() })
|
|
530
|
+
span.end()
|
|
531
|
+
|
|
532
|
+
throw err
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
return: async (value) => {
|
|
536
|
+
return iter.return(value)
|
|
537
|
+
},
|
|
538
|
+
throw: async (err) => {
|
|
539
|
+
return iter.throw(err)
|
|
540
|
+
},
|
|
541
|
+
[Symbol.asyncIterator]: () => {
|
|
542
|
+
return wrapped
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return wrapped
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function isCalculatedMetricOptions <T> (opts?: any): opts is T {
|
|
550
|
+
return opts?.calculate != null
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function setAttributes (span: Span, attributes?: Attributes): void {
|
|
554
|
+
if (attributes != null) {
|
|
555
|
+
span.setAttributes(attributes)
|
|
556
|
+
}
|
|
557
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { MetricGroup, StopTimer } from '@libp2p/interface'
|
|
2
|
+
import type { Gauge } from '@opentelemetry/api'
|
|
3
|
+
|
|
4
|
+
export class OpenTelemetryMetricGroup implements MetricGroup {
|
|
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
|
+
update (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
|
+
increment (values: Record<string, number | true>): void {
|
|
25
|
+
Object.entries(values).forEach(([key, value]) => {
|
|
26
|
+
if (this.lastValues[key] == null) {
|
|
27
|
+
this.lastValues[key] = 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.lastValues[key] += value === true ? 1 : value
|
|
31
|
+
this.gauge.record(this.lastValues[key], {
|
|
32
|
+
[this.label]: key
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
decrement (values: Record<string, number | true>): void {
|
|
38
|
+
Object.entries(values).forEach(([key, value]) => {
|
|
39
|
+
if (this.lastValues[key] == null) {
|
|
40
|
+
this.lastValues[key] = 0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.lastValues[key] -= value === true ? 1 : value
|
|
44
|
+
this.gauge.record(this.lastValues[key], {
|
|
45
|
+
[this.label]: key
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
reset (): void {
|
|
51
|
+
Object.keys(this.lastValues).forEach(key => {
|
|
52
|
+
this.lastValues[key] = 0
|
|
53
|
+
this.gauge.record(0, {
|
|
54
|
+
[this.label]: key
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
timer (key: string): StopTimer {
|
|
60
|
+
const start = Date.now()
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
this.lastValues[key] = Date.now() - start
|
|
64
|
+
this.gauge.record(this.lastValues[key], {
|
|
65
|
+
[this.label]: key
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/metric.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Metric, StopTimer } from '@libp2p/interface'
|
|
2
|
+
import type { Gauge } from '@opentelemetry/api'
|
|
3
|
+
|
|
4
|
+
export class OpenTelemetryMetric implements Metric {
|
|
5
|
+
private readonly gauge: Gauge
|
|
6
|
+
private lastValue: number
|
|
7
|
+
|
|
8
|
+
constructor (gauge: Gauge) {
|
|
9
|
+
this.gauge = gauge
|
|
10
|
+
this.lastValue = 0
|
|
11
|
+
this.update(0)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
update (value: number): void {
|
|
15
|
+
this.lastValue = value
|
|
16
|
+
this.gauge.record(value, {
|
|
17
|
+
attrName: 'attrValue'
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
increment (value: number = 1): void {
|
|
22
|
+
this.lastValue += value
|
|
23
|
+
this.gauge.record(this.lastValue)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
decrement (value: number = 1): void {
|
|
27
|
+
this.lastValue -= value
|
|
28
|
+
this.gauge.record(this.lastValue)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
reset (): void {
|
|
32
|
+
this.gauge.record(0)
|
|
33
|
+
this.lastValue = 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
timer (): StopTimer {
|
|
37
|
+
const start = Date.now()
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
this.lastValue = Date.now() - start
|
|
41
|
+
this.gauge.record(this.lastValue)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|