feat(unstable): add metrics to otel (#27143)

Refs: https://github.com/denoland/deno/issues/26852

Initial support for exporting metrics.

Co-authored-by: Luca Casonato <hello@lcas.dev>
This commit is contained in:
snek 2024-12-02 20:45:41 +01:00 committed by GitHub
parent 6dd2d5e49e
commit 7c036772df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 1174 additions and 35 deletions

View file

@ -7,6 +7,23 @@ import {
op_otel_instrumentation_scope_enter,
op_otel_instrumentation_scope_enter_builtin,
op_otel_log,
op_otel_metrics_data_point_attribute,
op_otel_metrics_data_point_attribute2,
op_otel_metrics_data_point_attribute3,
op_otel_metrics_gauge,
op_otel_metrics_histogram,
op_otel_metrics_histogram_data_point,
op_otel_metrics_histogram_data_point_entry1,
op_otel_metrics_histogram_data_point_entry2,
op_otel_metrics_histogram_data_point_entry3,
op_otel_metrics_histogram_data_point_entry_final,
op_otel_metrics_resource_attribute,
op_otel_metrics_resource_attribute2,
op_otel_metrics_resource_attribute3,
op_otel_metrics_scope,
op_otel_metrics_submit,
op_otel_metrics_sum,
op_otel_metrics_sum_or_gauge_data_point,
op_otel_span_attribute,
op_otel_span_attribute2,
op_otel_span_attribute3,
@ -186,7 +203,7 @@ const instrumentationScopes = new SafeWeakMap<
>();
let activeInstrumentationLibrary: WeakRef<InstrumentationLibrary> | null = null;
function submit(
function submitSpan(
spanId: string | Uint8Array,
traceId: string | Uint8Array,
traceFlags: number,
@ -411,7 +428,7 @@ export class Span {
endSpan = (span: Span) => {
const endTime = now();
submit(
submitSpan(
span.#spanId,
span.#traceId,
span.#traceFlags,
@ -571,7 +588,7 @@ class SpanExporter {
for (let i = 0; i < spans.length; i += 1) {
const span = spans[i];
const context = span.spanContext();
submit(
submitSpan(
context.spanId,
context.traceId,
context.traceFlags,
@ -671,6 +688,262 @@ class ContextManager {
}
}
function attributeValue(value: IAnyValue) {
return value.boolValue ?? value.stringValue ?? value.doubleValue ??
value.intValue;
}
function submitMetrics(resource, scopeMetrics) {
let i = 0;
while (i < resource.attributes.length) {
if (i + 2 < resource.attributes.length) {
op_otel_metrics_resource_attribute3(
resource.attributes.length,
resource.attributes[i].key,
attributeValue(resource.attributes[i].value),
resource.attributes[i + 1].key,
attributeValue(resource.attributes[i + 1].value),
resource.attributes[i + 2].key,
attributeValue(resource.attributes[i + 2].value),
);
i += 3;
} else if (i + 1 < resource.attributes.length) {
op_otel_metrics_resource_attribute2(
resource.attributes.length,
resource.attributes[i].key,
attributeValue(resource.attributes[i].value),
resource.attributes[i + 1].key,
attributeValue(resource.attributes[i + 1].value),
);
i += 2;
} else {
op_otel_metrics_resource_attribute(
resource.attributes.length,
resource.attributes[i].key,
attributeValue(resource.attributes[i].value),
);
i += 1;
}
}
for (let smi = 0; smi < scopeMetrics.length; smi += 1) {
const { scope, metrics } = scopeMetrics[smi];
op_otel_metrics_scope(scope.name, scope.schemaUrl, scope.version);
for (let mi = 0; mi < metrics.length; mi += 1) {
const metric = metrics[mi];
switch (metric.dataPointType) {
case 3:
op_otel_metrics_sum(
metric.descriptor.name,
// deno-lint-ignore prefer-primordials
metric.descriptor.description,
metric.descriptor.unit,
metric.aggregationTemporality,
metric.isMonotonic,
);
for (let di = 0; di < metric.dataPoints.length; di += 1) {
const dataPoint = metric.dataPoints[di];
op_otel_metrics_sum_or_gauge_data_point(
dataPoint.value,
hrToSecs(dataPoint.startTime),
hrToSecs(dataPoint.endTime),
);
const attributes = ObjectEntries(dataPoint.attributes);
let i = 0;
while (i < attributes.length) {
if (i + 2 < attributes.length) {
op_otel_metrics_data_point_attribute3(
attributes.length,
attributes[i][0],
attributes[i][1],
attributes[i + 1][0],
attributes[i + 1][1],
attributes[i + 2][0],
attributes[i + 2][1],
);
i += 3;
} else if (i + 1 < attributes.length) {
op_otel_metrics_data_point_attribute2(
attributes.length,
attributes[i][0],
attributes[i][1],
attributes[i + 1][0],
attributes[i + 1][1],
);
i += 2;
} else {
op_otel_metrics_data_point_attribute(
attributes.length,
attributes[i][0],
attributes[i][1],
);
i += 1;
}
}
}
break;
case 2:
op_otel_metrics_gauge(
metric.descriptor.name,
// deno-lint-ignore prefer-primordials
metric.descriptor.description,
metric.descriptor.unit,
);
for (let di = 0; di < metric.dataPoints.length; di += 1) {
const dataPoint = metric.dataPoints[di];
op_otel_metrics_sum_or_gauge_data_point(
dataPoint.value,
hrToSecs(dataPoint.startTime),
hrToSecs(dataPoint.endTime),
);
const attributes = ObjectEntries(dataPoint.attributes);
let i = 0;
while (i < attributes.length) {
if (i + 2 < attributes.length) {
op_otel_metrics_data_point_attribute3(
attributes.length,
attributes[i][0],
attributes[i][1],
attributes[i + 1][0],
attributes[i + 1][1],
attributes[i + 2][0],
attributes[i + 2][1],
);
i += 3;
} else if (i + 1 < attributes.length) {
op_otel_metrics_data_point_attribute2(
attributes.length,
attributes[i][0],
attributes[i][1],
attributes[i + 1][0],
attributes[i + 1][1],
);
i += 2;
} else {
op_otel_metrics_data_point_attribute(
attributes.length,
attributes[i][0],
attributes[i][1],
);
i += 1;
}
}
}
break;
case 0:
op_otel_metrics_histogram(
metric.descriptor.name,
// deno-lint-ignore prefer-primordials
metric.descriptor.description,
metric.descriptor.unit,
metric.aggregationTemporality,
);
for (let di = 0; di < metric.dataPoints.length; di += 1) {
const dataPoint = metric.dataPoints[di];
const { boundaries, counts } = dataPoint.value.buckets;
op_otel_metrics_histogram_data_point(
dataPoint.value.count,
dataPoint.value.min ?? NaN,
dataPoint.value.max ?? NaN,
dataPoint.value.sum,
hrToSecs(dataPoint.startTime),
hrToSecs(dataPoint.endTime),
boundaries.length,
);
let j = 0;
while (j < boundaries.length) {
if (j + 3 < boundaries.length) {
op_otel_metrics_histogram_data_point_entry3(
counts[j],
boundaries[j],
counts[j + 1],
boundaries[j + 1],
counts[j + 2],
boundaries[j + 2],
);
j += 3;
} else if (j + 2 < boundaries.length) {
op_otel_metrics_histogram_data_point_entry2(
counts[j],
boundaries[j],
counts[j + 1],
boundaries[j + 1],
);
j += 2;
} else {
op_otel_metrics_histogram_data_point_entry1(
counts[j],
boundaries[j],
);
j += 1;
}
}
op_otel_metrics_histogram_data_point_entry_final(counts[j]);
const attributes = ObjectEntries(dataPoint.attributes);
let i = 0;
while (i < attributes.length) {
if (i + 2 < attributes.length) {
op_otel_metrics_data_point_attribute3(
attributes.length,
attributes[i][0],
attributes[i][1],
attributes[i + 1][0],
attributes[i + 1][1],
attributes[i + 2][0],
attributes[i + 2][1],
);
i += 3;
} else if (i + 1 < attributes.length) {
op_otel_metrics_data_point_attribute2(
attributes.length,
attributes[i][0],
attributes[i][1],
attributes[i + 1][0],
attributes[i + 1][1],
);
i += 2;
} else {
op_otel_metrics_data_point_attribute(
attributes.length,
attributes[i][0],
attributes[i][1],
);
i += 1;
}
}
}
break;
default:
continue;
}
}
}
op_otel_metrics_submit();
}
class MetricExporter {
export(metrics, resultCallback: (result: ExportResult) => void) {
try {
submitMetrics(metrics.resource, metrics.scopeMetrics);
resultCallback({ code: 0 });
} catch (error) {
resultCallback({
code: 1,
error: ObjectPrototypeIsPrototypeOf(error, Error)
? error as Error
: new Error(String(error)),
});
}
}
async forceFlush() {}
async shutdown() {}
}
const otelConsoleConfig = {
ignore: 0,
capture: 1,
@ -708,4 +981,5 @@ export function bootstrap(
export const telemetry = {
SpanExporter,
ContextManager,
MetricExporter,
};