Skip to content

Commit

Permalink
chore(instrumentation-runtime-node): sync with conventions
Browse files Browse the repository at this point in the history
  • Loading branch information
pikalovArtemN committed May 17, 2024
1 parent 922d677 commit 3cba596
Show file tree
Hide file tree
Showing 10 changed files with 370 additions and 170 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const NODE_JS_VERSION_ATTRIBUTE = "nodejs.version"
export const NODE_JS_VERSION_ATTRIBUTE = "nodejsruntime.version"
14 changes: 7 additions & 7 deletions plugins/node/instrumentation-runtime-node/src/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ import { VERSION } from './version';
import { RuntimeNodeInstrumentationConfig } from './types';
import { MetricCollector } from './types/metricCollector';
import { EventLoopUtilizationCollector } from './metrics/eventLoopUtilizationCollector';
import { EventLoopLagCollector } from './metrics/eventLoopLagCollector';
import { EventLoopDelayCollector } from './metrics/eventLoopDelayCollector';
import { GCCollector } from './metrics/gcCollector';
import { HeapSizeAndUsedCollector } from './metrics/heapSizeAndUsedCollector';
import { HeapSpacesSizeAndUsedCollector } from './metrics/heapSpacesSizeAndUsedCollector';
import {ConventionalNamePrefix} from "./types/ConventionalNamePrefix";

const DEFAULT_CONFIG: RuntimeNodeInstrumentationConfig = {
monitoringPrecision: 5000,
};

const namePrefix = 'jsruntime';

export class RuntimeNodeInstrumentation extends InstrumentationBase {
private _collectors: MetricCollector[] = [];
Expand All @@ -40,11 +40,11 @@ export class RuntimeNodeInstrumentation extends InstrumentationBase {
Object.assign({}, DEFAULT_CONFIG, config)
);
this._collectors = [
new EventLoopUtilizationCollector(this._config, namePrefix),
new EventLoopLagCollector(this._config, namePrefix),
new GCCollector(this._config, namePrefix),
new HeapSizeAndUsedCollector(this._config, namePrefix),
new HeapSpacesSizeAndUsedCollector(this._config, namePrefix),
new EventLoopUtilizationCollector(this._config, ConventionalNamePrefix.NodeJsRuntime),
new EventLoopDelayCollector(this._config, ConventionalNamePrefix.NodeJsRuntime),
new GCCollector(this._config, ConventionalNamePrefix.V8EnjineRuntime),
new HeapSizeAndUsedCollector(this._config, ConventionalNamePrefix.V8EnjineRuntime),
new HeapSpacesSizeAndUsedCollector(this._config, ConventionalNamePrefix.V8EnjineRuntime),
];
if (this._config.enabled) {
for (const collector of this._collectors) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MetricCollector } from '../types/metricCollector';
import { Meter } from '@opentelemetry/api';
import { clearInterval } from 'node:timers';
import { RuntimeNodeInstrumentationConfig } from '../types';
import {MetricCollector} from '../types/metricCollector';
import {Meter} from '@opentelemetry/api';
import {clearInterval} from 'node:timers';
import {RuntimeNodeInstrumentationConfig} from '../types';
import {NODE_JS_VERSION_ATTRIBUTE} from "../consts/attributes";

type VersionAttribute = { [NODE_JS_VERSION_ATTRIBUTE]: string }

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

protected namePrefix: string;
private _interval: NodeJS.Timeout | undefined;
protected _scrapeQueue: T[] = [];
protected versionAttribute: VersionAttribute

constructor(
protected constructor(
config: RuntimeNodeInstrumentationConfig = {},
namePrefix: string
) {
this._config = config;
this.namePrefix = namePrefix;
this.versionAttribute = {[NODE_JS_VERSION_ATTRIBUTE]: process.version}
}

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

public abstract updateMetricInstruments(meter: Meter): void;

protected abstract internalEnable(): void;

protected abstract internalDisable(): void;

protected abstract scrape(): T;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {RuntimeNodeInstrumentationConfig} from '../types';
import {Meter} from '@opentelemetry/api';
import {IntervalHistogram} from 'node:perf_hooks';
import {BaseCollector} from './baseCollector';
import * as perf_hooks from 'node:perf_hooks';

enum NodeJsEventLoopDelay {
delay = 'eventloop.delay',
min = 'eventloop.delay.min',
max = 'eventloop.delay.max',
mean = 'eventloop.delay.mean',
stddev = 'eventloop.delay.stddev',
p50 = 'eventloop.delay.p50',
p90 = 'eventloop.delay.p90',
p99 = 'eventloop.delay.p99'
}

export const metricNames: Record<NodeJsEventLoopDelay, { description: string }> = {
[NodeJsEventLoopDelay.delay]:
{
description:
'Lag of event loop in seconds.'
},
[NodeJsEventLoopDelay.min]:
{
description:
'The minimum recorded event loop delay.',
},
[NodeJsEventLoopDelay.max]:
{
description:
'The maximum recorded event loop delay.',
}
,
[NodeJsEventLoopDelay.mean]:
{
description:
'The mean of the recorded event loop delays.',
}
,
[NodeJsEventLoopDelay.stddev]:
{
description:
'The standard deviation of the recorded event loop delays.',
}
,
[NodeJsEventLoopDelay.p50]:
{
description:
'The 50th percentile of the recorded event loop delays.',
}
,
[NodeJsEventLoopDelay.p90]:
{
description:
'The 90th percentile of the recorded event loop delays.',
}
,
[NodeJsEventLoopDelay.p99]:
{

description:
'The 99th percentile of the recorded event loop delays.',
}
,
};

export interface EventLoopLagInformation {
min: number;
max: number;
mean: number;
stddev: number;
p50: number;
p90: number;
p99: number;
}

export class EventLoopDelayCollector extends BaseCollector<EventLoopLagInformation> {
private _histogram: IntervalHistogram;


constructor(
config: RuntimeNodeInstrumentationConfig = {},
namePrefix: string
) {
super(config, namePrefix);
this._histogram = perf_hooks.monitorEventLoopDelay({
resolution: config.monitoringPrecision,
});
}

updateMetricInstruments(meter: Meter): void {
const delay = meter.createObservableGauge(
`${this.namePrefix}.${NodeJsEventLoopDelay.delay}`,
{
description: metricNames[NodeJsEventLoopDelay.delay].description,
unit: 's',
}
);
const delayMin = meter.createObservableGauge(
`${this.namePrefix}.${NodeJsEventLoopDelay.min}`,
{
description: metricNames[NodeJsEventLoopDelay.min].description,
unit: 's',
}
);
const delayMax = meter.createObservableGauge(
`${this.namePrefix}.${NodeJsEventLoopDelay.max}`,
{
description: metricNames[NodeJsEventLoopDelay.max].description,
unit: 's',
}
);
const delayMean = meter.createObservableGauge(
`${this.namePrefix}.${NodeJsEventLoopDelay.mean}`,
{
description: metricNames[NodeJsEventLoopDelay.mean].description,
unit: 's',
}
);
const delayStddev = meter.createObservableGauge(
`${this.namePrefix}.${NodeJsEventLoopDelay.stddev}`,
{
description: metricNames[NodeJsEventLoopDelay.stddev].description,
unit: 's',
}
);
const delayp50 = meter.createObservableGauge(
`${this.namePrefix}.${NodeJsEventLoopDelay.p50}`,
{
description: metricNames[NodeJsEventLoopDelay.p50].description,
unit: 's',
}
);
const delayp90 = meter.createObservableGauge(
`${this.namePrefix}.${NodeJsEventLoopDelay.p90}`,
{
description: metricNames[NodeJsEventLoopDelay.p90].description,
unit: 's',
}
);
const delayp99 = meter.createObservableGauge(
`${this.namePrefix}.${NodeJsEventLoopDelay.p99}`,
{
description: metricNames[NodeJsEventLoopDelay.p99].description,
unit: 's',
}
);

meter.addBatchObservableCallback(
async observableResult => {
if (this._scrapeQueue.length === 0) return;

const data = this._scrapeQueue.shift();
if (data === undefined) return;

const start = process.hrtime();
const delayResult = await new Promise<number>(res => {
setImmediate((start: [number, number]) => {
res(this._reportEventloopLag(start));
}, start);
});

observableResult.observe(delay, delayResult, this.versionAttribute);
observableResult.observe(delayMin, data.min, this.versionAttribute);
observableResult.observe(delayMax, data.max, this.versionAttribute);
observableResult.observe(delayMean, data.mean, this.versionAttribute);
observableResult.observe(delayStddev, data.stddev, this.versionAttribute);
observableResult.observe(delayp50, data.p50, this.versionAttribute);
observableResult.observe(delayp90, data.p90, this.versionAttribute);
observableResult.observe(delayp99, data.p99, this.versionAttribute);

this._histogram.reset();
},
[delay, delayMin, delayMax, delayMean, delayStddev, delayp50, delayp90, delayp99]
);
}

internalEnable(): void {
this._histogram.enable();
}

internalDisable(): void {
this._histogram.disable();
}

protected scrape(): EventLoopLagInformation {
return {
min: this.checkNan(this._histogram.min / 1e9),
max: this.checkNan(this._histogram.max / 1e9),
mean: this.checkNan(this._histogram.mean / 1e9),
stddev: this.checkNan(this._histogram.stddev / 1e9),
p50: this.checkNan(this._histogram.percentile(90) / 1e9),
p90: this.checkNan(this._histogram.percentile(90) / 1e9),
p99: this.checkNan(this._histogram.percentile(99) / 1e9),
};
}

private _reportEventloopLag(start: [number, number]): number {
const delta = process.hrtime(start);
const nanosec = delta[0] * 1e9 + delta[1];
const seconds = nanosec / 1e9;
return seconds;
}

private checkNan(value: number) {
return isNaN(value) ? 0 : value;
}
}
Loading

0 comments on commit 3cba596

Please sign in to comment.