Skip to content

Commit 3cba596

Browse files
committed
chore(instrumentation-runtime-node): sync with conventions
1 parent 922d677 commit 3cba596

File tree

10 files changed

+370
-170
lines changed

10 files changed

+370
-170
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const NODE_JS_VERSION_ATTRIBUTE = "nodejs.version"
1+
export const NODE_JS_VERSION_ATTRIBUTE = "nodejsruntime.version"

plugins/node/instrumentation-runtime-node/src/instrumentation.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ import { VERSION } from './version';
1919
import { RuntimeNodeInstrumentationConfig } from './types';
2020
import { MetricCollector } from './types/metricCollector';
2121
import { EventLoopUtilizationCollector } from './metrics/eventLoopUtilizationCollector';
22-
import { EventLoopLagCollector } from './metrics/eventLoopLagCollector';
22+
import { EventLoopDelayCollector } from './metrics/eventLoopDelayCollector';
2323
import { GCCollector } from './metrics/gcCollector';
2424
import { HeapSizeAndUsedCollector } from './metrics/heapSizeAndUsedCollector';
2525
import { HeapSpacesSizeAndUsedCollector } from './metrics/heapSpacesSizeAndUsedCollector';
26+
import {ConventionalNamePrefix} from "./types/ConventionalNamePrefix";
2627

2728
const DEFAULT_CONFIG: RuntimeNodeInstrumentationConfig = {
2829
monitoringPrecision: 5000,
2930
};
3031

31-
const namePrefix = 'jsruntime';
3232

3333
export class RuntimeNodeInstrumentation extends InstrumentationBase {
3434
private _collectors: MetricCollector[] = [];
@@ -40,11 +40,11 @@ export class RuntimeNodeInstrumentation extends InstrumentationBase {
4040
Object.assign({}, DEFAULT_CONFIG, config)
4141
);
4242
this._collectors = [
43-
new EventLoopUtilizationCollector(this._config, namePrefix),
44-
new EventLoopLagCollector(this._config, namePrefix),
45-
new GCCollector(this._config, namePrefix),
46-
new HeapSizeAndUsedCollector(this._config, namePrefix),
47-
new HeapSpacesSizeAndUsedCollector(this._config, namePrefix),
43+
new EventLoopUtilizationCollector(this._config, ConventionalNamePrefix.NodeJsRuntime),
44+
new EventLoopDelayCollector(this._config, ConventionalNamePrefix.NodeJsRuntime),
45+
new GCCollector(this._config, ConventionalNamePrefix.V8EnjineRuntime),
46+
new HeapSizeAndUsedCollector(this._config, ConventionalNamePrefix.V8EnjineRuntime),
47+
new HeapSpacesSizeAndUsedCollector(this._config, ConventionalNamePrefix.V8EnjineRuntime),
4848
];
4949
if (this._config.enabled) {
5050
for (const collector of this._collectors) {

plugins/node/instrumentation-runtime-node/src/metrics/baseCollector.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,29 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { MetricCollector } from '../types/metricCollector';
17-
import { Meter } from '@opentelemetry/api';
18-
import { clearInterval } from 'node:timers';
19-
import { RuntimeNodeInstrumentationConfig } from '../types';
16+
import {MetricCollector} from '../types/metricCollector';
17+
import {Meter} from '@opentelemetry/api';
18+
import {clearInterval} from 'node:timers';
19+
import {RuntimeNodeInstrumentationConfig} from '../types';
20+
import {NODE_JS_VERSION_ATTRIBUTE} from "../consts/attributes";
21+
22+
type VersionAttribute = { [NODE_JS_VERSION_ATTRIBUTE]: string }
2023

2124
export abstract class BaseCollector<T> implements MetricCollector {
2225
protected _config: RuntimeNodeInstrumentationConfig = {};
2326

2427
protected namePrefix: string;
2528
private _interval: NodeJS.Timeout | undefined;
2629
protected _scrapeQueue: T[] = [];
30+
protected versionAttribute: VersionAttribute
2731

28-
constructor(
32+
protected constructor(
2933
config: RuntimeNodeInstrumentationConfig = {},
3034
namePrefix: string
3135
) {
3236
this._config = config;
3337
this.namePrefix = namePrefix;
38+
this.versionAttribute = {[NODE_JS_VERSION_ATTRIBUTE]: process.version}
3439
}
3540

3641
public disable(): void {
@@ -67,7 +72,10 @@ export abstract class BaseCollector<T> implements MetricCollector {
6772
}
6873

6974
public abstract updateMetricInstruments(meter: Meter): void;
75+
7076
protected abstract internalEnable(): void;
77+
7178
protected abstract internalDisable(): void;
79+
7280
protected abstract scrape(): T;
7381
}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import {RuntimeNodeInstrumentationConfig} from '../types';
17+
import {Meter} from '@opentelemetry/api';
18+
import {IntervalHistogram} from 'node:perf_hooks';
19+
import {BaseCollector} from './baseCollector';
20+
import * as perf_hooks from 'node:perf_hooks';
21+
22+
enum NodeJsEventLoopDelay {
23+
delay = 'eventloop.delay',
24+
min = 'eventloop.delay.min',
25+
max = 'eventloop.delay.max',
26+
mean = 'eventloop.delay.mean',
27+
stddev = 'eventloop.delay.stddev',
28+
p50 = 'eventloop.delay.p50',
29+
p90 = 'eventloop.delay.p90',
30+
p99 = 'eventloop.delay.p99'
31+
}
32+
33+
export const metricNames: Record<NodeJsEventLoopDelay, { description: string }> = {
34+
[NodeJsEventLoopDelay.delay]:
35+
{
36+
description:
37+
'Lag of event loop in seconds.'
38+
},
39+
[NodeJsEventLoopDelay.min]:
40+
{
41+
description:
42+
'The minimum recorded event loop delay.',
43+
},
44+
[NodeJsEventLoopDelay.max]:
45+
{
46+
description:
47+
'The maximum recorded event loop delay.',
48+
}
49+
,
50+
[NodeJsEventLoopDelay.mean]:
51+
{
52+
description:
53+
'The mean of the recorded event loop delays.',
54+
}
55+
,
56+
[NodeJsEventLoopDelay.stddev]:
57+
{
58+
description:
59+
'The standard deviation of the recorded event loop delays.',
60+
}
61+
,
62+
[NodeJsEventLoopDelay.p50]:
63+
{
64+
description:
65+
'The 50th percentile of the recorded event loop delays.',
66+
}
67+
,
68+
[NodeJsEventLoopDelay.p90]:
69+
{
70+
description:
71+
'The 90th percentile of the recorded event loop delays.',
72+
}
73+
,
74+
[NodeJsEventLoopDelay.p99]:
75+
{
76+
77+
description:
78+
'The 99th percentile of the recorded event loop delays.',
79+
}
80+
,
81+
};
82+
83+
export interface EventLoopLagInformation {
84+
min: number;
85+
max: number;
86+
mean: number;
87+
stddev: number;
88+
p50: number;
89+
p90: number;
90+
p99: number;
91+
}
92+
93+
export class EventLoopDelayCollector extends BaseCollector<EventLoopLagInformation> {
94+
private _histogram: IntervalHistogram;
95+
96+
97+
constructor(
98+
config: RuntimeNodeInstrumentationConfig = {},
99+
namePrefix: string
100+
) {
101+
super(config, namePrefix);
102+
this._histogram = perf_hooks.monitorEventLoopDelay({
103+
resolution: config.monitoringPrecision,
104+
});
105+
}
106+
107+
updateMetricInstruments(meter: Meter): void {
108+
const delay = meter.createObservableGauge(
109+
`${this.namePrefix}.${NodeJsEventLoopDelay.delay}`,
110+
{
111+
description: metricNames[NodeJsEventLoopDelay.delay].description,
112+
unit: 's',
113+
}
114+
);
115+
const delayMin = meter.createObservableGauge(
116+
`${this.namePrefix}.${NodeJsEventLoopDelay.min}`,
117+
{
118+
description: metricNames[NodeJsEventLoopDelay.min].description,
119+
unit: 's',
120+
}
121+
);
122+
const delayMax = meter.createObservableGauge(
123+
`${this.namePrefix}.${NodeJsEventLoopDelay.max}`,
124+
{
125+
description: metricNames[NodeJsEventLoopDelay.max].description,
126+
unit: 's',
127+
}
128+
);
129+
const delayMean = meter.createObservableGauge(
130+
`${this.namePrefix}.${NodeJsEventLoopDelay.mean}`,
131+
{
132+
description: metricNames[NodeJsEventLoopDelay.mean].description,
133+
unit: 's',
134+
}
135+
);
136+
const delayStddev = meter.createObservableGauge(
137+
`${this.namePrefix}.${NodeJsEventLoopDelay.stddev}`,
138+
{
139+
description: metricNames[NodeJsEventLoopDelay.stddev].description,
140+
unit: 's',
141+
}
142+
);
143+
const delayp50 = meter.createObservableGauge(
144+
`${this.namePrefix}.${NodeJsEventLoopDelay.p50}`,
145+
{
146+
description: metricNames[NodeJsEventLoopDelay.p50].description,
147+
unit: 's',
148+
}
149+
);
150+
const delayp90 = meter.createObservableGauge(
151+
`${this.namePrefix}.${NodeJsEventLoopDelay.p90}`,
152+
{
153+
description: metricNames[NodeJsEventLoopDelay.p90].description,
154+
unit: 's',
155+
}
156+
);
157+
const delayp99 = meter.createObservableGauge(
158+
`${this.namePrefix}.${NodeJsEventLoopDelay.p99}`,
159+
{
160+
description: metricNames[NodeJsEventLoopDelay.p99].description,
161+
unit: 's',
162+
}
163+
);
164+
165+
meter.addBatchObservableCallback(
166+
async observableResult => {
167+
if (this._scrapeQueue.length === 0) return;
168+
169+
const data = this._scrapeQueue.shift();
170+
if (data === undefined) return;
171+
172+
const start = process.hrtime();
173+
const delayResult = await new Promise<number>(res => {
174+
setImmediate((start: [number, number]) => {
175+
res(this._reportEventloopLag(start));
176+
}, start);
177+
});
178+
179+
observableResult.observe(delay, delayResult, this.versionAttribute);
180+
observableResult.observe(delayMin, data.min, this.versionAttribute);
181+
observableResult.observe(delayMax, data.max, this.versionAttribute);
182+
observableResult.observe(delayMean, data.mean, this.versionAttribute);
183+
observableResult.observe(delayStddev, data.stddev, this.versionAttribute);
184+
observableResult.observe(delayp50, data.p50, this.versionAttribute);
185+
observableResult.observe(delayp90, data.p90, this.versionAttribute);
186+
observableResult.observe(delayp99, data.p99, this.versionAttribute);
187+
188+
this._histogram.reset();
189+
},
190+
[delay, delayMin, delayMax, delayMean, delayStddev, delayp50, delayp90, delayp99]
191+
);
192+
}
193+
194+
internalEnable(): void {
195+
this._histogram.enable();
196+
}
197+
198+
internalDisable(): void {
199+
this._histogram.disable();
200+
}
201+
202+
protected scrape(): EventLoopLagInformation {
203+
return {
204+
min: this.checkNan(this._histogram.min / 1e9),
205+
max: this.checkNan(this._histogram.max / 1e9),
206+
mean: this.checkNan(this._histogram.mean / 1e9),
207+
stddev: this.checkNan(this._histogram.stddev / 1e9),
208+
p50: this.checkNan(this._histogram.percentile(90) / 1e9),
209+
p90: this.checkNan(this._histogram.percentile(90) / 1e9),
210+
p99: this.checkNan(this._histogram.percentile(99) / 1e9),
211+
};
212+
}
213+
214+
private _reportEventloopLag(start: [number, number]): number {
215+
const delta = process.hrtime(start);
216+
const nanosec = delta[0] * 1e9 + delta[1];
217+
const seconds = nanosec / 1e9;
218+
return seconds;
219+
}
220+
221+
private checkNan(value: number) {
222+
return isNaN(value) ? 0 : value;
223+
}
224+
}

0 commit comments

Comments
 (0)