1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-03 04:48:52 -05:00

feat(unstable): add OTEL MeterProvider (#27240)

This commit replaces `Deno.telemetry.MetricsExporter` with
`Deno.telemetry.MeterProvider`.

Signed-off-by: Luca Casonato <hello@lcas.dev>
Co-authored-by: snek <snek@deno.com>
This commit is contained in:
Luca Casonato 2024-12-26 09:01:39 +01:00 committed by GitHub
parent 91150706d8
commit f4e321342f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 1764 additions and 933 deletions

View file

@ -1006,6 +1006,8 @@ impl Flags {
OtelConfig {
tracing_enabled: !disabled
&& otel_var("OTEL_DENO_TRACING").unwrap_or(default),
metrics_enabled: !disabled
&& otel_var("OTEL_DENO_METRICS").unwrap_or(default),
console: match std::env::var("OTEL_DENO_CONSOLE").as_deref() {
Ok(_) if disabled => OtelConsoleConfig::Ignore,
Ok("ignore") => OtelConsoleConfig::Ignore,

File diff suppressed because it is too large Load diff

View file

@ -7,23 +7,24 @@ 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_metric_attribute3,
op_otel_metric_create_counter,
op_otel_metric_create_gauge,
op_otel_metric_create_histogram,
op_otel_metric_create_observable_counter,
op_otel_metric_create_observable_gauge,
op_otel_metric_create_observable_up_down_counter,
op_otel_metric_create_up_down_counter,
op_otel_metric_observable_record0,
op_otel_metric_observable_record1,
op_otel_metric_observable_record2,
op_otel_metric_observable_record3,
op_otel_metric_observation_done,
op_otel_metric_record0,
op_otel_metric_record1,
op_otel_metric_record2,
op_otel_metric_record3,
op_otel_metric_wait_to_observe,
op_otel_span_attribute,
op_otel_span_attribute2,
op_otel_span_attribute3,
@ -36,25 +37,32 @@ import { Console } from "ext:deno_console/01_console.js";
import { performance } from "ext:deno_web/15_performance.js";
const {
SafeWeakMap,
Array,
ObjectEntries,
ReflectApply,
SymbolFor,
ArrayPrototypePush,
Error,
Uint8Array,
TypedArrayPrototypeSubarray,
ObjectAssign,
ObjectDefineProperty,
WeakRefPrototypeDeref,
ObjectEntries,
ObjectPrototypeIsPrototypeOf,
ReflectApply,
SafeIterator,
SafeMap,
SafePromiseAll,
SafeSet,
SafeWeakMap,
SafeWeakRef,
SafeWeakSet,
String,
StringPrototypePadStart,
ObjectPrototypeIsPrototypeOf,
SafeWeakRef,
SymbolFor,
TypedArrayPrototypeSubarray,
Uint8Array,
WeakRefPrototypeDeref,
} = primordials;
const { AsyncVariable, setAsyncContext } = core;
export let TRACING_ENABLED = false;
export let METRICS_ENABLED = false;
let DETERMINISTIC = false;
// Note: These start at 0 in the JS library,
@ -202,30 +210,9 @@ const instrumentationScopes = new SafeWeakMap<
>();
let activeInstrumentationLibrary: WeakRef<InstrumentationLibrary> | null = null;
function submitSpan(
spanId: string | Uint8Array,
traceId: string | Uint8Array,
traceFlags: number,
parentSpanId: string | Uint8Array | null,
span: Omit<
ReadableSpan,
| "spanContext"
| "startTime"
| "endTime"
| "parentSpanId"
| "duration"
| "ended"
| "resource"
>,
startTime: number,
endTime: number,
function activateInstrumentationLibrary(
instrumentationLibrary: InstrumentationLibrary,
) {
if (!TRACING_ENABLED) return;
if (!(traceFlags & TRACE_FLAG_SAMPLED)) return;
// TODO(@lucacasonato): `resource` is ignored for now, should we implement it?
const instrumentationLibrary = span.instrumentationLibrary;
if (
!activeInstrumentationLibrary ||
WeakRefPrototypeDeref(activeInstrumentationLibrary) !==
@ -255,6 +242,32 @@ function submitSpan(
}
}
}
}
function submitSpan(
spanId: string | Uint8Array,
traceId: string | Uint8Array,
traceFlags: number,
parentSpanId: string | Uint8Array | null,
span: Omit<
ReadableSpan,
| "spanContext"
| "startTime"
| "endTime"
| "parentSpanId"
| "duration"
| "ended"
| "resource"
>,
startTime: number,
endTime: number,
) {
if (!TRACING_ENABLED) return;
if (!(traceFlags & TRACE_FLAG_SAMPLED)) return;
// TODO(@lucacasonato): `resource` is ignored for now, should we implement it?
activateInstrumentationLibrary(span.instrumentationLibrary);
op_otel_span_start(
traceId,
@ -368,7 +381,7 @@ export let endSpan: (span: Span) => void;
export class Span {
#traceId: string | Uint8Array;
#spanId: Uint8Array;
#spanId: string | Uint8Array;
#traceFlags = TRACE_FLAG_SAMPLED;
#spanContext: SpanContext | null = null;
@ -687,260 +700,510 @@ class ContextManager {
}
}
function attributeValue(value: IAnyValue) {
return value.boolValue ?? value.stringValue ?? value.doubleValue ??
value.intValue;
// metrics
interface MeterOptions {
schemaUrl?: string;
}
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;
}
interface MetricOptions {
description?: string;
unit?: string;
valueType?: ValueType;
advice?: MetricAdvice;
}
enum ValueType {
INT = 0,
DOUBLE = 1,
}
interface MetricAdvice {
/**
* Hint the explicit bucket boundaries for SDK if the metric is been
* aggregated with a HistogramAggregator.
*/
explicitBucketBoundaries?: number[];
}
export class MeterProvider {
getMeter(name: string, version?: string, options?: MeterOptions): Meter {
return new Meter({ name, version, schemaUrl: options?.schemaUrl });
}
}
type MetricAttributes = Attributes;
type Instrument = { __key: "instrument" };
let batchResultHasObservables: (
res: BatchObservableResult,
observables: Observable[],
) => boolean;
class BatchObservableResult {
#observables: WeakSet<Observable>;
constructor(observables: WeakSet<Observable>) {
this.#observables = observables;
}
for (let smi = 0; smi < scopeMetrics.length; smi += 1) {
const { scope, metrics } = scopeMetrics[smi];
static {
batchResultHasObservables = (cb, observables) => {
for (const observable of new SafeIterator(observables)) {
if (!cb.#observables.has(observable)) return false;
}
return true;
};
}
op_otel_metrics_scope(scope.name, scope.schemaUrl, scope.version);
observe(
metric: Observable,
value: number,
attributes?: MetricAttributes,
): void {
if (!this.#observables.has(metric)) return;
getObservableResult(metric).observe(value, attributes);
}
}
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;
const BATCH_CALLBACKS = new SafeMap<
BatchObservableCallback,
BatchObservableResult
>();
const INDIVIDUAL_CALLBACKS = new SafeMap<Observable, Set<ObservableCallback>>();
class Meter {
#instrumentationLibrary: InstrumentationLibrary;
constructor(instrumentationLibrary: InstrumentationLibrary) {
this.#instrumentationLibrary = instrumentationLibrary;
}
createCounter(
name: string,
options?: MetricOptions,
): Counter {
if (options?.valueType !== undefined && options?.valueType !== 1) {
throw new Error("Only valueType: DOUBLE is supported");
}
if (!METRICS_ENABLED) return new Counter(null, false);
activateInstrumentationLibrary(this.#instrumentationLibrary);
const instrument = op_otel_metric_create_counter(
name,
// deno-lint-ignore prefer-primordials
options?.description,
options?.unit,
) as Instrument;
return new Counter(instrument, false);
}
createUpDownCounter(
name: string,
options?: MetricOptions,
): Counter {
if (options?.valueType !== undefined && options?.valueType !== 1) {
throw new Error("Only valueType: DOUBLE is supported");
}
if (!METRICS_ENABLED) return new Counter(null, true);
activateInstrumentationLibrary(this.#instrumentationLibrary);
const instrument = op_otel_metric_create_up_down_counter(
name,
// deno-lint-ignore prefer-primordials
options?.description,
options?.unit,
) as Instrument;
return new Counter(instrument, true);
}
createGauge(
name: string,
options?: MetricOptions,
): Gauge {
if (options?.valueType !== undefined && options?.valueType !== 1) {
throw new Error("Only valueType: DOUBLE is supported");
}
if (!METRICS_ENABLED) return new Gauge(null);
activateInstrumentationLibrary(this.#instrumentationLibrary);
const instrument = op_otel_metric_create_gauge(
name,
// deno-lint-ignore prefer-primordials
options?.description,
options?.unit,
) as Instrument;
return new Gauge(instrument);
}
createHistogram(
name: string,
options?: MetricOptions,
): Histogram {
if (options?.valueType !== undefined && options?.valueType !== 1) {
throw new Error("Only valueType: DOUBLE is supported");
}
if (!METRICS_ENABLED) return new Histogram(null);
activateInstrumentationLibrary(this.#instrumentationLibrary);
const instrument = op_otel_metric_create_histogram(
name,
// deno-lint-ignore prefer-primordials
options?.description,
options?.unit,
options?.advice?.explicitBucketBoundaries,
) as Instrument;
return new Histogram(instrument);
}
createObservableCounter(
name: string,
options?: MetricOptions,
): Observable {
if (options?.valueType !== undefined && options?.valueType !== 1) {
throw new Error("Only valueType: DOUBLE is supported");
}
if (!METRICS_ENABLED) new Observable(new ObservableResult(null, true));
activateInstrumentationLibrary(this.#instrumentationLibrary);
const instrument = op_otel_metric_create_observable_counter(
name,
// deno-lint-ignore prefer-primordials
options?.description,
options?.unit,
) as Instrument;
return new Observable(new ObservableResult(instrument, true));
}
createObservableGauge(
name: string,
options?: MetricOptions,
): Observable {
if (options?.valueType !== undefined && options?.valueType !== 1) {
throw new Error("Only valueType: DOUBLE is supported");
}
if (!METRICS_ENABLED) new Observable(new ObservableResult(null, false));
activateInstrumentationLibrary(this.#instrumentationLibrary);
const instrument = op_otel_metric_create_observable_gauge(
name,
// deno-lint-ignore prefer-primordials
options?.description,
options?.unit,
) as Instrument;
return new Observable(new ObservableResult(instrument, false));
}
createObservableUpDownCounter(
name: string,
options?: MetricOptions,
): Observable {
if (options?.valueType !== undefined && options?.valueType !== 1) {
throw new Error("Only valueType: DOUBLE is supported");
}
if (!METRICS_ENABLED) new Observable(new ObservableResult(null, false));
activateInstrumentationLibrary(this.#instrumentationLibrary);
const instrument = op_otel_metric_create_observable_up_down_counter(
name,
// deno-lint-ignore prefer-primordials
options?.description,
options?.unit,
) as Instrument;
return new Observable(new ObservableResult(instrument, false));
}
addBatchObservableCallback(
callback: BatchObservableCallback,
observables: Observable[],
): void {
if (!METRICS_ENABLED) return;
const result = new BatchObservableResult(new SafeWeakSet(observables));
startObserving();
BATCH_CALLBACKS.set(callback, result);
}
removeBatchObservableCallback(
callback: BatchObservableCallback,
observables: Observable[],
): void {
if (!METRICS_ENABLED) return;
const result = BATCH_CALLBACKS.get(callback);
if (result && batchResultHasObservables(result, observables)) {
BATCH_CALLBACKS.delete(callback);
}
}
}
type BatchObservableCallback = (
observableResult: BatchObservableResult,
) => void | Promise<void>;
function record(
instrument: Instrument | null,
value: number,
attributes?: MetricAttributes,
) {
if (instrument === null) return;
if (attributes === undefined) {
op_otel_metric_record0(instrument, value);
} else {
const attrs = ObjectEntries(attributes);
if (attrs.length === 0) {
op_otel_metric_record0(instrument, value);
}
let i = 0;
while (i < attrs.length) {
const remaining = attrs.length - i;
if (remaining > 3) {
op_otel_metric_attribute3(
instrument,
value,
attrs[i][0],
attrs[i][1],
attrs[i + 1][0],
attrs[i + 1][1],
attrs[i + 2][0],
attrs[i + 2][1],
);
i += 3;
} else if (remaining === 3) {
op_otel_metric_record3(
instrument,
value,
attrs[i][0],
attrs[i][1],
attrs[i + 1][0],
attrs[i + 1][1],
attrs[i + 2][0],
attrs[i + 2][1],
);
i += 3;
} else if (remaining === 2) {
op_otel_metric_record2(
instrument,
value,
attrs[i][0],
attrs[i][1],
attrs[i + 1][0],
attrs[i + 1][1],
);
i += 2;
} else if (remaining === 1) {
op_otel_metric_record1(
instrument,
value,
attrs[i][0],
attrs[i][1],
);
i += 1;
}
}
}
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)),
});
function recordObservable(
instrument: Instrument | null,
value: number,
attributes?: MetricAttributes,
) {
if (instrument === null) return;
if (attributes === undefined) {
op_otel_metric_observable_record0(instrument, value);
} else {
const attrs = ObjectEntries(attributes);
if (attrs.length === 0) {
op_otel_metric_observable_record0(instrument, value);
}
let i = 0;
while (i < attrs.length) {
const remaining = attrs.length - i;
if (remaining > 3) {
op_otel_metric_attribute3(
instrument,
value,
attrs[i][0],
attrs[i][1],
attrs[i + 1][0],
attrs[i + 1][1],
attrs[i + 2][0],
attrs[i + 2][1],
);
i += 3;
} else if (remaining === 3) {
op_otel_metric_observable_record3(
instrument,
value,
attrs[i][0],
attrs[i][1],
attrs[i + 1][0],
attrs[i + 1][1],
attrs[i + 2][0],
attrs[i + 2][1],
);
i += 3;
} else if (remaining === 2) {
op_otel_metric_observable_record2(
instrument,
value,
attrs[i][0],
attrs[i][1],
attrs[i + 1][0],
attrs[i + 1][1],
);
i += 2;
} else if (remaining === 1) {
op_otel_metric_observable_record1(
instrument,
value,
attrs[i][0],
attrs[i][1],
);
i += 1;
}
}
}
}
async forceFlush() {}
class Counter {
#instrument: Instrument | null;
#upDown: boolean;
async shutdown() {}
constructor(instrument: Instrument | null, upDown: boolean) {
this.#instrument = instrument;
this.#upDown = upDown;
}
add(value: number, attributes?: MetricAttributes, _context?: Context): void {
if (value < 0 && !this.#upDown) {
throw new Error("Counter can only be incremented");
}
record(this.#instrument, value, attributes);
}
}
class Gauge {
#instrument: Instrument | null;
constructor(instrument: Instrument | null) {
this.#instrument = instrument;
}
record(
value: number,
attributes?: MetricAttributes,
_context?: Context,
): void {
record(this.#instrument, value, attributes);
}
}
class Histogram {
#instrument: Instrument | null;
constructor(instrument: Instrument | null) {
this.#instrument = instrument;
}
record(
value: number,
attributes?: MetricAttributes,
_context?: Context,
): void {
record(this.#instrument, value, attributes);
}
}
type ObservableCallback = (
observableResult: ObservableResult,
) => void | Promise<void>;
let getObservableResult: (observable: Observable) => ObservableResult;
class Observable {
#result: ObservableResult;
constructor(result: ObservableResult) {
this.#result = result;
}
static {
getObservableResult = (observable) => observable.#result;
}
addCallback(callback: ObservableCallback): void {
const res = INDIVIDUAL_CALLBACKS.get(this);
if (res) res.add(callback);
else INDIVIDUAL_CALLBACKS.set(this, new SafeSet([callback]));
startObserving();
}
removeCallback(callback: ObservableCallback): void {
const res = INDIVIDUAL_CALLBACKS.get(this);
if (res) res.delete(callback);
if (res?.size === 0) INDIVIDUAL_CALLBACKS.delete(this);
}
}
class ObservableResult {
#instrument: Instrument | null;
#isRegularCounter: boolean;
constructor(instrument: Instrument | null, isRegularCounter: boolean) {
this.#instrument = instrument;
this.#isRegularCounter = isRegularCounter;
}
observe(
this: ObservableResult,
value: number,
attributes?: MetricAttributes,
): void {
if (this.#isRegularCounter) {
if (value < 0) {
throw new Error("Observable counters can only be incremented");
}
}
recordObservable(this.#instrument, value, attributes);
}
}
async function observe(): Promise<void> {
const promises: Promise<void>[] = [];
// Primordials are not needed, because this is a SafeMap.
// deno-lint-ignore prefer-primordials
for (const { 0: observable, 1: callbacks } of INDIVIDUAL_CALLBACKS) {
const result = getObservableResult(observable);
// Primordials are not needed, because this is a SafeSet.
// deno-lint-ignore prefer-primordials
for (const callback of callbacks) {
// PromiseTry is not in primordials?
// deno-lint-ignore prefer-primordials
ArrayPrototypePush(promises, Promise.try(callback, result));
}
}
// Primordials are not needed, because this is a SafeMap.
// deno-lint-ignore prefer-primordials
for (const { 0: callback, 1: result } of BATCH_CALLBACKS) {
// PromiseTry is not in primordials?
// deno-lint-ignore prefer-primordials
ArrayPrototypePush(promises, Promise.try(callback, result));
}
await SafePromiseAll(promises);
}
let isObserving = false;
function startObserving() {
if (!isObserving) {
isObserving = true;
(async () => {
while (true) {
const promise = op_otel_metric_wait_to_observe();
core.unrefOpPromise(promise);
const ok = await promise;
if (!ok) break;
await observe();
op_otel_metric_observation_done();
}
})();
}
}
const otelConsoleConfig = {
@ -951,14 +1214,21 @@ const otelConsoleConfig = {
export function bootstrap(
config: [
0 | 1,
0 | 1,
typeof otelConsoleConfig[keyof typeof otelConsoleConfig],
0 | 1,
],
): void {
const { 0: tracingEnabled, 1: consoleConfig, 2: deterministic } = config;
const {
0: tracingEnabled,
1: metricsEnabled,
2: consoleConfig,
3: deterministic,
} = config;
TRACING_ENABLED = tracingEnabled === 1;
METRICS_ENABLED = metricsEnabled === 1;
DETERMINISTIC = deterministic === 1;
switch (consoleConfig) {
@ -980,5 +1250,5 @@ export function bootstrap(
export const telemetry = {
SpanExporter,
ContextManager,
MetricExporter,
MeterProvider,
};

View file

@ -1,28 +1,27 @@
{
"steps": [
{
"tests": {
"basic": {
"args": "run -A main.ts basic.ts",
"output": "basic.out"
},
{
"natural_exit": {
"args": "run -A main.ts natural_exit.ts",
"output": "natural_exit.out"
},
{
"deno_dot_exit": {
"args": "run -A main.ts deno_dot_exit.ts",
"output": "deno_dot_exit.out"
},
{
"uncaught": {
"args": "run -A main.ts uncaught.ts",
"output": "uncaught.out"
},
{
"metric": {
"envs": {
"OTEL_METRIC_EXPORT_INTERVAL": "1000"
},
"args": "run -A main.ts metric.ts",
"output": "metric.out"
},
{
"args": "run -A --unstable-otel context.ts",
"output": ""
}
]
}
}

View file

@ -56,6 +56,31 @@
"isMonotonic": false
}
},
{
"name": "gauge",
"description": "Example of a Gauge",
"unit": "",
"metadata": [],
"gauge": {
"dataPoints": [
{
"attributes": [
{
"key": "attribute",
"value": {
"doubleValue": 1
}
}
],
"startTimeUnixNano": "[WILDCARD]",
"timeUnixNano": "[WILDCARD]",
"exemplars": [],
"flags": 0,
"asDouble": 1
}
]
}
},
{
"name": "histogram",
"description": "Example of a Histogram",
@ -119,6 +144,265 @@
],
"aggregationTemporality": 2
}
},
{
"name": "observable_counter",
"description": "Example of a ObservableCounter",
"unit": "",
"metadata": [],
"sum": {
"dataPoints": [
{
"attributes": [],
"startTimeUnixNano": "[WILDCARD]",
"timeUnixNano": "[WILDCARD]",
"exemplars": [],
"flags": 0,
"asDouble": 1
}
],
"aggregationTemporality": 2,
"isMonotonic": true
}
},
{
"name": "observable_up_down_counter",
"description": "Example of a ObservableUpDownCounter",
"unit": "",
"metadata": [],
"sum": {
"dataPoints": [
{
"attributes": [],
"startTimeUnixNano": "[WILDCARD]",
"timeUnixNano": "[WILDCARD]",
"exemplars": [],
"flags": 0,
"asDouble": 1
}
],
"aggregationTemporality": 2,
"isMonotonic": false
}
},
{
"name": "observable_gauge",
"description": "Example of a ObservableGauge",
"unit": "",
"metadata": [],
"gauge": {
"dataPoints": [
{
"attributes": [],
"startTimeUnixNano": "[WILDCARD]",
"timeUnixNano": "[WILDCARD]",
"exemplars": [],
"flags": 0,
"asDouble": 1
}
]
}
},
{
"name": "counter",
"description": "Example of a Counter",
"unit": "",
"metadata": [],
"sum": {
"dataPoints": [
{
"attributes": [
{
"key": "attribute",
"value": {
"doubleValue": 1
}
}
],
"startTimeUnixNano": "[WILDCARD]",
"timeUnixNano": "[WILDCARD]",
"exemplars": [],
"flags": 0,
"asDouble": 1
}
],
"aggregationTemporality": 2,
"isMonotonic": true
}
},
{
"name": "up_down_counter",
"description": "Example of a UpDownCounter",
"unit": "",
"metadata": [],
"sum": {
"dataPoints": [
{
"attributes": [
{
"key": "attribute",
"value": {
"doubleValue": 1
}
}
],
"startTimeUnixNano": "[WILDCARD]",
"timeUnixNano": "[WILDCARD]",
"exemplars": [],
"flags": 0,
"asDouble": -1
}
],
"aggregationTemporality": 2,
"isMonotonic": false
}
},
{
"name": "gauge",
"description": "Example of a Gauge",
"unit": "",
"metadata": [],
"gauge": {
"dataPoints": [
{
"attributes": [
{
"key": "attribute",
"value": {
"doubleValue": 1
}
}
],
"startTimeUnixNano": "[WILDCARD]",
"timeUnixNano": "[WILDCARD]",
"exemplars": [],
"flags": 0,
"asDouble": 1
}
]
}
},
{
"name": "histogram",
"description": "Example of a Histogram",
"unit": "",
"metadata": [],
"histogram": {
"dataPoints": [
{
"attributes": [
{
"key": "attribute",
"value": {
"doubleValue": 1
}
}
],
"startTimeUnixNano": "[WILDCARD]",
"timeUnixNano": "[WILDCARD]",
"count": 1,
"sum": 1,
"bucketCounts": [
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"explicitBounds": [
0,
5,
10,
25,
50,
75,
100,
250,
500,
750,
1000,
2500,
5000,
7500,
10000
],
"exemplars": [],
"flags": 0,
"min": 1,
"max": 1
}
],
"aggregationTemporality": 2
}
},
{
"name": "observable_counter",
"description": "Example of a ObservableCounter",
"unit": "",
"metadata": [],
"sum": {
"dataPoints": [
{
"attributes": [],
"startTimeUnixNano": "[WILDCARD]",
"timeUnixNano": "[WILDCARD]",
"exemplars": [],
"flags": 0,
"asDouble": 1
}
],
"aggregationTemporality": 2,
"isMonotonic": true
}
},
{
"name": "observable_up_down_counter",
"description": "Example of a ObservableUpDownCounter",
"unit": "",
"metadata": [],
"sum": {
"dataPoints": [
{
"attributes": [],
"startTimeUnixNano": "[WILDCARD]",
"timeUnixNano": "[WILDCARD]",
"exemplars": [],
"flags": 0,
"asDouble": 1
}
],
"aggregationTemporality": 2,
"isMonotonic": false
}
},
{
"name": "observable_gauge",
"description": "Example of a ObservableGauge",
"unit": "",
"metadata": [],
"gauge": {
"dataPoints": [
{
"attributes": [],
"startTimeUnixNano": "[WILDCARD]",
"timeUnixNano": "[WILDCARD]",
"exemplars": [],
"flags": 0,
"asDouble": 1
}
]
}
}
]
}

View file

@ -1,18 +1,8 @@
import {
MeterProvider,
PeriodicExportingMetricReader,
} from "npm:@opentelemetry/sdk-metrics@1.28.0";
import { metrics } from "npm:@opentelemetry/api@1";
const meterProvider = new MeterProvider();
metrics.setGlobalMeterProvider(new Deno.telemetry.MeterProvider());
meterProvider.addMetricReader(
new PeriodicExportingMetricReader({
exporter: new Deno.telemetry.MetricExporter(),
exportIntervalMillis: 100,
}),
);
const meter = meterProvider.getMeter("m");
const meter = metrics.getMeter("m");
const counter = meter.createCounter("counter", {
description: "Example of a Counter",
@ -22,13 +12,82 @@ const upDownCounter = meter.createUpDownCounter("up_down_counter", {
description: "Example of a UpDownCounter",
});
const gauge = meter.createGauge("gauge", {
description: "Example of a Gauge",
});
const histogram = meter.createHistogram("histogram", {
description: "Example of a Histogram",
});
const observableCounterPromise = Promise.withResolvers<void>();
const observableCounter = meter.createObservableCounter("observable_counter", {
description: "Example of a ObservableCounter",
});
observableCounter.addCallback((res) => {
res.observe(1);
observableCounterPromise.resolve();
});
const observableUpDownCounterPromise = Promise.withResolvers<void>();
const observableUpDownCounter = meter
.createObservableUpDownCounter("observable_up_down_counter", {
description: "Example of a ObservableUpDownCounter",
});
observableUpDownCounter.addCallback((res) => {
res.observe(1);
observableUpDownCounterPromise.resolve();
});
const observableGaugePromise = Promise.withResolvers<void>();
const observableGauge = meter.createObservableGauge("observable_gauge", {
description: "Example of a ObservableGauge",
});
observableGauge.addCallback((res) => {
res.observe(1);
observableGaugePromise.resolve();
});
const observableCounterBatch = meter.createObservableCounter(
"observable_counter_batch",
{ description: "Example of a ObservableCounter, written in batch" },
);
const observableUpDownCounterBatch = meter.createObservableUpDownCounter(
"observable_up_down_counter_batch",
{ description: "Example of a ObservableUpDownCounter, written in batch" },
);
const observableGaugeBatch = meter.createObservableGauge(
"observable_gauge_batch",
{
description: "Example of a ObservableGauge, written in batch",
},
);
const observableBatchPromise = Promise.withResolvers<void>();
meter.addBatchObservableCallback((observer) => {
observer.observe(observableCounter, 2);
observer.observe(observableUpDownCounter, 2);
observer.observe(observableGauge, 2);
observableBatchPromise.resolve();
}, [
observableCounterBatch,
observableUpDownCounterBatch,
observableGaugeBatch,
]);
const attributes = { attribute: 1 };
counter.add(1, attributes);
upDownCounter.add(-1, attributes);
gauge.record(1, attributes);
histogram.record(1, attributes);
await meterProvider.forceFlush();
const timer = setTimeout(() => {}, 100000);
await Promise.all([
observableCounterPromise.promise,
observableUpDownCounterPromise.promise,
observableGaugePromise.promise,
observableBatchPromise.promise,
]);
clearTimeout(timer);