Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { BaseCollector } from './baseCollector';
import {
ATTR_V8JS_HEAP_SPACE_NAME,
METRIC_V8JS_MEMORY_HEAP_LIMIT,
METRIC_V8JS_MEMORY_HEAP_MAX,
METRIC_V8JS_MEMORY_HEAP_USED,
METRIC_V8JS_MEMORY_HEAP_SPACE_AVAILABLE_SIZE,
METRIC_V8JS_MEMORY_HEAP_SPACE_PHYSICAL_SIZE,
Expand Down Expand Up @@ -59,10 +60,19 @@ export class HeapSpacesSizeAndUsedCollector extends BaseCollector {
}
);

const heapMax = meter.createObservableGauge(METRIC_V8JS_MEMORY_HEAP_MAX, {
description:
'Maximum heap size allowed by the V8 engine, as set by --max-old-space-size or V8 defaults.',
unit: 'By',
});

meter.addBatchObservableCallback(
observableResult => {
if (!this._config.enabled) return;

const heapStats = v8.getHeapStatistics();
observableResult.observe(heapMax, heapStats.heap_size_limit);

const data = this.scrape();
if (data === undefined) return;
for (const space of data) {
Expand Down Expand Up @@ -93,7 +103,7 @@ export class HeapSpacesSizeAndUsedCollector extends BaseCollector {
);
}
},
[heapLimit, heapSpaceUsed, heapSpaceAvailable, heapSpacePhysical]
[heapMax, heapLimit, heapSpaceUsed, heapSpaceAvailable, heapSpacePhysical]
);
}

Expand Down
10 changes: 10 additions & 0 deletions packages/instrumentation-runtime-node/src/semconv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,13 @@ export const NODEJS_EVENTLOOP_STATE_VALUE_ACTIVE = 'active' as const;
* @experimental This enum value is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*/
export const NODEJS_EVENTLOOP_STATE_VALUE_IDLE = 'idle' as const;

/**
* Maximum heap size allowed by the V8 engine.
*
* @note The value can be retrieved from value `heap_size_limit` of [`v8.getHeapStatistics()`](https://nodejs.org/api/v8.html#v8getheapstatistics).
* This is the absolute ceiling the heap can grow to, controlled by `--max-old-space-size` or V8 defaults.
*
* @experimental This metric is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*/
export const METRIC_V8JS_MEMORY_HEAP_MAX = 'v8js.memory.heap.max' as const;
142 changes: 142 additions & 0 deletions packages/instrumentation-runtime-node/test/heap_size_limit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* 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 * as assert from 'assert';
import { DataPointType, MeterProvider } from '@opentelemetry/sdk-metrics';
import { RuntimeNodeInstrumentation } from '../src';
import { TestMetricReader } from './testMetricsReader';
import { METRIC_V8JS_MEMORY_HEAP_MAX } from '../src/semconv';

const MEASUREMENT_INTERVAL = 10;

describe('v8js.memory.heap.max', function () {
let metricReader: TestMetricReader;
let meterProvider: MeterProvider;

beforeEach(() => {
metricReader = new TestMetricReader();
meterProvider = new MeterProvider({
readers: [metricReader],
});
});

it(`should write ${METRIC_V8JS_MEMORY_HEAP_MAX} after monitoringPrecision`, async function () {
// arrange
const instrumentation = new RuntimeNodeInstrumentation({
monitoringPrecision: MEASUREMENT_INTERVAL,
});
instrumentation.setMeterProvider(meterProvider);

// act
await new Promise(resolve => setTimeout(resolve, MEASUREMENT_INTERVAL * 5));
const { resourceMetrics, errors } = await metricReader.collect();

// assert
assert.deepEqual(
errors,
[],
'expected no errors from the callback during collection'
);
const scopeMetrics = resourceMetrics.scopeMetrics;
const metric = scopeMetrics[0].metrics.find(
x => x.descriptor.name === METRIC_V8JS_MEMORY_HEAP_MAX
);

assert.notEqual(
metric,
undefined,
`${METRIC_V8JS_MEMORY_HEAP_MAX} not found`
);

assert.strictEqual(
metric!.dataPointType,
DataPointType.GAUGE,
'expected gauge'
);

assert.strictEqual(
metric!.descriptor.name,
METRIC_V8JS_MEMORY_HEAP_MAX,
'descriptor.name'
);
});

it('should have a positive value representing the heap size limit', async function () {
// arrange
const instrumentation = new RuntimeNodeInstrumentation({
monitoringPrecision: MEASUREMENT_INTERVAL,
});
instrumentation.setMeterProvider(meterProvider);

// act
await new Promise(resolve => setTimeout(resolve, MEASUREMENT_INTERVAL * 5));
const { resourceMetrics, errors } = await metricReader.collect();

// assert
assert.deepEqual(errors, []);
const scopeMetrics = resourceMetrics.scopeMetrics;
const metric = scopeMetrics[0].metrics.find(
x => x.descriptor.name === METRIC_V8JS_MEMORY_HEAP_MAX
);

assert.notEqual(
metric,
undefined,
`${METRIC_V8JS_MEMORY_HEAP_MAX} not found`
);

if (metric!.dataPointType === DataPointType.GAUGE) {
assert.strictEqual(
metric!.dataPoints.length,
1,
'expected exactly one data point (global, not per-space)'
);
const value = metric!.dataPoints[0].value as number;
assert.ok(value > 0, `expected positive heap_size_limit, got ${value}`);
}
});

it('should not have v8js.heap.space.name attribute (global metric)', async function () {
// arrange
const instrumentation = new RuntimeNodeInstrumentation({
monitoringPrecision: MEASUREMENT_INTERVAL,
});
instrumentation.setMeterProvider(meterProvider);

// act
await new Promise(resolve => setTimeout(resolve, MEASUREMENT_INTERVAL * 5));
const { resourceMetrics, errors } = await metricReader.collect();

// assert
assert.deepEqual(errors, []);
const scopeMetrics = resourceMetrics.scopeMetrics;
const metric = scopeMetrics[0].metrics.find(
x => x.descriptor.name === METRIC_V8JS_MEMORY_HEAP_MAX
);

assert.notEqual(metric, undefined);

if (metric!.dataPointType === DataPointType.GAUGE) {
for (const dp of metric!.dataPoints) {
assert.strictEqual(
dp.attributes['v8js.heap.space.name'],
undefined,
'v8js.memory.heap.max should not have v8js.heap.space.name attribute'
);
}
}
});
});
Loading