Skip to content
Draft
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
22 changes: 22 additions & 0 deletions packages/core/src/otlpExporter/common/semconv/base/mappings.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,28 @@ const MAPPINGS = {

error: {
TYPE: 'error.type'
},

metrics: {
v8js: {
GC_DURATION: 'v8js.gc.duration',
HEAP_SPACE_AVAILABLE_SIZE: 'v8js.memory.heap.space.available_size',
HEAP_SPACE_PHYSICAL_SIZE: 'v8js.memory.heap.space.physical_size',
HEAP_SPACE_SIZE: 'v8js.memory.heap.space.size',
HEAP_USED: 'v8js.memory.heap.used',
RESOURCE_ACTIVE: 'v8js.resource.active',

attributes: {
GC_TYPE: 'v8js.gc.type',
HEAP_SPACE_NAME: 'v8js.heap.space.name',
RESOURCE_TYPE: 'v8js.resource.type'
}
},
nodejs: {
EVENTLOOP_DELAY_MIN: 'nodejs.eventloop.delay.min',
EVENTLOOP_DELAY_MAX: 'nodejs.eventloop.delay.max',
EVENTLOOP_DELAY_MEAN: 'nodejs.eventloop.delay.mean'
}
}
};

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/otlpExporter/metrics/converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
const otlpCtx = require('../common/context');
const { normalizeMetrics } = require('./util');
const transformers = require('./transformers');
const mappers = require('./mappers');

const { INSTRUMENTATION_SCOPE } = transformers.resource;

Expand Down Expand Up @@ -54,8 +55,7 @@ function convert(metrics) {
scopeMetrics: [
{
scope: INSTRUMENTATION_SCOPE,
// TODO: implement metrics transformation later in phase2
metrics: []
metrics: transformers.extractMetrics(metrics, mappers.allMappings)
}
]
}
Expand Down
17 changes: 17 additions & 0 deletions packages/core/src/otlpExporter/metrics/mappers/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* (c) Copyright IBM Corp. 2026
*/

'use strict';

exports.METRIC_TYPES = {
GAUGE: 'gauge',
UPDOWNCOUNTER: 'updowncounter',
HISTOGRAM: 'histogram'
};

exports.METRIC_UNITS = {
SECONDS: 's',
BYTES: 'By',
RESOURCES: '{resource}'
};
16 changes: 16 additions & 0 deletions packages/core/src/otlpExporter/metrics/mappers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* (c) Copyright IBM Corp. 2026
*/

'use strict';

const runtimeMetricsMappings = require('./runtimeMetricsMappings');

module.exports = {
get allMappings() {
return [
runtimeMetricsMappings
// future: httpMetricsMappings,
];
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* (c) Copyright IBM Corp. 2026
*/

'use strict';

const ctx = require('../../common/context');
const { METRIC_TYPES, METRIC_UNITS } = require('./constants');
const { msToSeconds, computeMean } = require('./util');

const OTLP = /** @type {any} */ (ctx.semConv);

/**
* A single-value metric — maps one Instana payload field to one OTLP data point.
*
* @typedef {Object} SinglePointMapping
* @property {'single'} pointType
* @property {string} name
* @property {string} unit
* @property {string} type
* @property {string} instana
* @property {Record<string, any>} [attributes]
* @property {(value: any, payload: Record<string, any>) => any} [transform]
*/

/**
* A histogram metric — maps one Instana payload field to an OTLP histogram data point
* (produces `{ count, sum }` instead of a plain number).
*
* @typedef {Object} HistogramMapping
* @property {'histogram'} pointType
* @property {string} name
* @property {string} unit
* @property {string} type
* @property {string} instana
* @property {Record<string, any>} [attributes]
* @property {(value: any, payload: Record<string, any>) => any} [transform]
*/

/**
* A fan-out metric — iterates over a map of heap spaces and emits one data point per space.
*
* @typedef {Object} HeapSpaceMapping
* @property {'heapSpace'} pointType
* @property {string} name
* @property {string} unit
* @property {string} type
* @property {string} instana
* @property {string} field
* @property {string} attributeKey
*/

/**
* @typedef {SinglePointMapping | HistogramMapping | HeapSpaceMapping} MetricMapping
*/

const OTLP_V8 = OTLP.metrics.v8js;
const OTLP_NODEJS = OTLP.metrics.nodejs;

/** @type {MetricMapping[]} */
const v8Mappings = [
{
pointType: 'histogram',
name: OTLP_V8.GC_DURATION,
unit: METRIC_UNITS.SECONDS,
type: METRIC_TYPES.HISTOGRAM,
instana: 'gc.gcPause',
attributes: { [OTLP_V8.attributes.GC_TYPE]: 'all' },
transform: msToSeconds
},

{
pointType: 'heapSpace',
name: OTLP_V8.HEAP_SPACE_AVAILABLE_SIZE,
unit: METRIC_UNITS.BYTES,
type: METRIC_TYPES.UPDOWNCOUNTER,
instana: 'heapSpaces',
field: 'available',
attributeKey: OTLP_V8.attributes.HEAP_SPACE_NAME
},

{
pointType: 'heapSpace',
name: OTLP_V8.HEAP_SPACE_PHYSICAL_SIZE,
unit: METRIC_UNITS.BYTES,
type: METRIC_TYPES.UPDOWNCOUNTER,
instana: 'heapSpaces',
field: 'physical',
attributeKey: OTLP_V8.attributes.HEAP_SPACE_NAME
},

{
pointType: 'heapSpace',
name: OTLP_V8.HEAP_SPACE_SIZE,
unit: METRIC_UNITS.BYTES,
type: METRIC_TYPES.UPDOWNCOUNTER,
instana: 'heapSpaces',
field: 'current',
attributeKey: OTLP_V8.attributes.HEAP_SPACE_NAME
},

{
pointType: 'heapSpace',
name: OTLP_V8.HEAP_USED,
unit: METRIC_UNITS.BYTES,
type: METRIC_TYPES.UPDOWNCOUNTER,
instana: 'heapSpaces',
field: 'used',
attributeKey: OTLP_V8.attributes.HEAP_SPACE_NAME
},

{
pointType: 'single',
name: OTLP_V8.RESOURCE_ACTIVE,
unit: METRIC_UNITS.RESOURCES,
type: METRIC_TYPES.GAUGE,
instana: 'activeResources.count',
attributes: { [OTLP_V8.attributes.RESOURCE_TYPE]: 'all' }
}
];

/** @type {MetricMapping[]} */
const nodejsMappings = [
{
pointType: 'single',
name: OTLP_NODEJS.EVENTLOOP_DELAY_MIN,
unit: METRIC_UNITS.SECONDS,
type: METRIC_TYPES.GAUGE,
instana: 'libuv.min',
attributes: {},
transform: msToSeconds
},

{
pointType: 'single',
name: OTLP_NODEJS.EVENTLOOP_DELAY_MAX,
unit: METRIC_UNITS.SECONDS,
type: METRIC_TYPES.GAUGE,
instana: 'libuv.max',
attributes: {},
transform: msToSeconds
},

{
pointType: 'single',
name: OTLP_NODEJS.EVENTLOOP_DELAY_MEAN,
unit: METRIC_UNITS.SECONDS,
type: METRIC_TYPES.GAUGE,
instana: 'libuv',
attributes: {},
transform: computeMean
}
];

module.exports = {
metricMappings: [...v8Mappings, ...nodejsMappings]
};
40 changes: 40 additions & 0 deletions packages/core/src/otlpExporter/metrics/mappers/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* (c) Copyright IBM Corp. 2026
*/

'use strict';

/**
* @param {any} ms
* @returns {number | undefined}
*/
function msToSeconds(ms) {
if (typeof ms !== 'number') return undefined;
return ms / 1000;
}

/**
* @param {any} libuv - The `libuv` sub-object from the metrics payload
* @returns {number | undefined}
*/
function computeMean(libuv) {
if (!libuv || typeof libuv.sum !== 'number' || typeof libuv.num !== 'number' || libuv.num === 0) {
return undefined;
}
return msToSeconds(libuv.sum / libuv.num);
}

/**
* @param {Record<string, any>} payload
* @param {string} path
* @returns {any}
*/
function resolvePath(payload, path) {
return path.split('.').reduce((obj, key) => (obj != null ? obj[key] : undefined), payload);
}

module.exports = {
msToSeconds,
computeMean,
resolvePath
};
14 changes: 13 additions & 1 deletion packages/core/src/otlpExporter/metrics/transformers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,19 @@
'use strict';

const resource = require('../../common/transformers/resource');
const runtimeMetrics = require('./runtimeMetrics');

/**
* @param {Record<string, any>} metricsPayload
* @param {Array<{ metricMappings: any[] }>} allMappings
* @returns {Array<Record<string, any>>} OTLP metric objects
*/
function extractMetrics(metricsPayload, allMappings) {
return allMappings.flatMap(mapper => runtimeMetrics.extractMetrics(metricsPayload, mapper));
}

module.exports = {
resource
resource,
runtimeMetrics,
extractMetrics
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* (c) Copyright IBM Corp. 2026
*/

'use strict';

const { extractMappedMetrics } = require('./util');

/**
* @param {Record<string, any>} metricsPayload
* @param {{ metricMappings: import('../mappers/runtimeMetricsMappings').MetricMapping[] }} mapper
* @returns {Array<Record<string, any>>} OTLP metric objects
*/
function extractMetrics(metricsPayload, mapper) {
const timeUnixNano = (metricsPayload?.timestamp ?? Date.now()) * 1e6;
return extractMappedMetrics(metricsPayload, mapper, timeUnixNano);
}

module.exports = {
extractMetrics
};
Loading