diff --git a/CHANGELOG.md b/CHANGELOG.md index fff7a1c22e..0c18cad260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file. ### :rocket: (Enhancement) +feat(sdk-trace-base): move Sampler declaration into sdk-trace-base [#3088](https://github.com/open-telemetry/opentelemetry-js/pull/3088) @legendecas + ### :bug: (Bug Fix) ### :books: (Refine Doc) diff --git a/experimental/packages/opentelemetry-sdk-node/src/types.ts b/experimental/packages/opentelemetry-sdk-node/src/types.ts index 90c1e08401..705481174c 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/types.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/types.ts @@ -15,14 +15,15 @@ */ import type { ContextManager, SpanAttributes } from '@opentelemetry/api'; -import { Sampler, TextMapPropagator } from '@opentelemetry/api'; +import { TextMapPropagator } from '@opentelemetry/api'; import { InstrumentationOption } from '@opentelemetry/instrumentation'; import { Resource } from '@opentelemetry/resources'; import { MetricReader } from '@opentelemetry/sdk-metrics-base'; import { + Sampler, SpanExporter, SpanLimits, - SpanProcessor + SpanProcessor, } from '@opentelemetry/sdk-trace-base'; export interface NodeSDKConfiguration { diff --git a/packages/opentelemetry-core/README.md b/packages/opentelemetry-core/README.md index 573c3bc08b..1d543dca22 100644 --- a/packages/opentelemetry-core/README.md +++ b/packages/opentelemetry-core/README.md @@ -13,11 +13,6 @@ This package provides default implementations of the OpenTelemetry API for trace - [W3CTraceContextPropagator Propagator](#w3ctracecontextpropagator-propagator) - [Composite Propagator](#composite-propagator) - [Baggage Propagator](#baggage-propagator) - - [Built-in Sampler](#built-in-sampler) - - [AlwaysOn Sampler](#alwayson-sampler) - - [AlwaysOff Sampler](#alwaysoff-sampler) - - [TraceIdRatioBased Sampler](#traceidratiobased-sampler) - - [ParentBased Sampler](#parentbased-sampler) - [Useful links](#useful-links) - [License](#license) @@ -61,107 +56,6 @@ const { W3CBaggagePropagator } = require("@opentelemetry/core"); api.propagation.setGlobalPropagator(new W3CBaggagePropagator()); ``` -### Built-in Sampler - -Sampler is used to make decisions on `Span` sampling. - -#### AlwaysOn Sampler - -Samples every trace regardless of upstream sampling decisions. - -> This is used as a default Sampler - -```js -const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); -const { AlwaysOnSampler } = require("@opentelemetry/core"); - -const tracerProvider = new NodeTracerProvider({ - sampler: new AlwaysOnSampler() -}); -``` - -#### AlwaysOff Sampler - -Doesn't sample any trace, regardless of upstream sampling decisions. - -```js -const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); -const { AlwaysOffSampler } = require("@opentelemetry/core"); - -const tracerProvider = new NodeTracerProvider({ - sampler: new AlwaysOffSampler() -}); -``` - -#### TraceIdRatioBased Sampler - -Samples some percentage of traces, calculated deterministically using the trace ID. -Any trace that would be sampled at a given percentage will also be sampled at any higher percentage. - -The `TraceIDRatioSampler` may be used with the `ParentBasedSampler` to respect the sampled flag of an incoming trace. - -```js -const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); -const { TraceIdRatioBasedSampler } = require("@opentelemetry/core"); - -const tracerProvider = new NodeTracerProvider({ - // See details of ParentBasedSampler below - sampler: new ParentBasedSampler({ - // Trace ID Ratio Sampler accepts a positional argument - // which represents the percentage of traces which should - // be sampled. - root: new TraceIdRatioBasedSampler(0.5) - }); -}); -``` - -#### ParentBased Sampler - -- This is a composite sampler. `ParentBased` helps distinguished between the -following cases: - - No parent (root span). - - Remote parent with `sampled` flag `true` - - Remote parent with `sampled` flag `false` - - Local parent with `sampled` flag `true` - - Local parent with `sampled` flag `false` - -Required parameters: - -- `root(Sampler)` - Sampler called for spans with no parent (root spans) - -Optional parameters: - -- `remoteParentSampled(Sampler)` (default: `AlwaysOn`) -- `remoteParentNotSampled(Sampler)` (default: `AlwaysOff`) -- `localParentSampled(Sampler)` (default: `AlwaysOn`) -- `localParentNotSampled(Sampler)` (default: `AlwaysOff`) - -|Parent| parent.isRemote() | parent.isSampled()| Invoke sampler| -|--|--|--|--| -|absent| n/a | n/a |`root()`| -|present|true|true|`remoteParentSampled()`| -|present|true|false|`remoteParentNotSampled()`| -|present|false|true|`localParentSampled()`| -|present|false|false|`localParentNotSampled()`| - -```js -const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); -const { ParentBasedSampler, AlwaysOffSampler, TraceIdRatioBasedSampler } = require("@opentelemetry/core"); - -const tracerProvider = new NodeTracerProvider({ - sampler: new ParentBasedSampler({ - // By default, the ParentBasedSampler will respect the parent span's sampling - // decision. This is configurable by providing a different sampler to use - // based on the situation. See configuration details above. - // - // This will delegate the sampling decision of all root traces (no parent) - // to the TraceIdRatioBasedSampler. - // See details of TraceIdRatioBasedSampler above. - root: new TraceIdRatioBasedSampler(0.5) - }) -}); -``` - ## Useful links - For more information on OpenTelemetry, visit: diff --git a/packages/opentelemetry-core/src/platform/browser/RandomIdGenerator.ts b/packages/opentelemetry-core/src/platform/browser/RandomIdGenerator.ts index 1094fe4e83..634ba0a32f 100644 --- a/packages/opentelemetry-core/src/platform/browser/RandomIdGenerator.ts +++ b/packages/opentelemetry-core/src/platform/browser/RandomIdGenerator.ts @@ -18,6 +18,9 @@ import { IdGenerator } from '../../trace/IdGenerator'; const SPAN_ID_BYTES = 8; const TRACE_ID_BYTES = 16; +/** + * @deprecated Use the one defined in @opentelemetry/sdk-trace-base instead. + */ export class RandomIdGenerator implements IdGenerator { /** * Returns a random 16-byte trace ID formatted/encoded as a 32 lowercase hex diff --git a/packages/opentelemetry-core/src/platform/node/RandomIdGenerator.ts b/packages/opentelemetry-core/src/platform/node/RandomIdGenerator.ts index 219bcd0464..34d4971e55 100644 --- a/packages/opentelemetry-core/src/platform/node/RandomIdGenerator.ts +++ b/packages/opentelemetry-core/src/platform/node/RandomIdGenerator.ts @@ -18,6 +18,9 @@ import { IdGenerator } from '../../trace/IdGenerator'; const SPAN_ID_BYTES = 8; const TRACE_ID_BYTES = 16; +/** + * @deprecated Use the one defined in @opentelemetry/sdk-trace-base instead. + */ export class RandomIdGenerator implements IdGenerator { /** * Returns a random 16-byte trace ID formatted/encoded as a 32 lowercase hex diff --git a/packages/opentelemetry-core/src/trace/IdGenerator.ts b/packages/opentelemetry-core/src/trace/IdGenerator.ts index 65f63f9c0c..bf027f905a 100644 --- a/packages/opentelemetry-core/src/trace/IdGenerator.ts +++ b/packages/opentelemetry-core/src/trace/IdGenerator.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -/** IdGenerator provides an interface for generating Trace Id and Span Id */ +/** + * @deprecated Use the one defined in @opentelemetry/sdk-trace-base instead. + * IdGenerator provides an interface for generating Trace Id and Span Id. + */ export interface IdGenerator { /** Returns a trace ID composed of 32 lowercase hex characters. */ generateTraceId(): string; diff --git a/packages/opentelemetry-core/src/trace/sampler/AlwaysOffSampler.ts b/packages/opentelemetry-core/src/trace/sampler/AlwaysOffSampler.ts index 67c169efe9..ebe824d90b 100644 --- a/packages/opentelemetry-core/src/trace/sampler/AlwaysOffSampler.ts +++ b/packages/opentelemetry-core/src/trace/sampler/AlwaysOffSampler.ts @@ -16,7 +16,10 @@ import { Sampler, SamplingDecision, SamplingResult } from '@opentelemetry/api'; -/** Sampler that samples no traces. */ +/** + * @deprecated Use the one defined in @opentelemetry/sdk-trace-base instead. + * Sampler that samples no traces. + */ export class AlwaysOffSampler implements Sampler { shouldSample(): SamplingResult { return { diff --git a/packages/opentelemetry-core/src/trace/sampler/AlwaysOnSampler.ts b/packages/opentelemetry-core/src/trace/sampler/AlwaysOnSampler.ts index 5d15b88526..8967d49645 100644 --- a/packages/opentelemetry-core/src/trace/sampler/AlwaysOnSampler.ts +++ b/packages/opentelemetry-core/src/trace/sampler/AlwaysOnSampler.ts @@ -16,7 +16,10 @@ import { Sampler, SamplingDecision, SamplingResult } from '@opentelemetry/api'; -/** Sampler that samples all traces. */ +/** + * @deprecated Use the one defined in @opentelemetry/sdk-trace-base instead. + * Sampler that samples all traces. + */ export class AlwaysOnSampler implements Sampler { shouldSample(): SamplingResult { return { diff --git a/packages/opentelemetry-core/src/trace/sampler/ParentBasedSampler.ts b/packages/opentelemetry-core/src/trace/sampler/ParentBasedSampler.ts index 1097ac1476..4b2b6cec57 100644 --- a/packages/opentelemetry-core/src/trace/sampler/ParentBasedSampler.ts +++ b/packages/opentelemetry-core/src/trace/sampler/ParentBasedSampler.ts @@ -29,6 +29,7 @@ import { AlwaysOffSampler } from './AlwaysOffSampler'; import { AlwaysOnSampler } from './AlwaysOnSampler'; /** + * @deprecated Use the one defined in @opentelemetry/sdk-trace-base instead. * A composite sampler that either respects the parent span's sampling decision * or delegates to `delegateSampler` for root spans. */ diff --git a/packages/opentelemetry-core/src/trace/sampler/TraceIdRatioBasedSampler.ts b/packages/opentelemetry-core/src/trace/sampler/TraceIdRatioBasedSampler.ts index 5e8a66dbac..c4928d1cf0 100644 --- a/packages/opentelemetry-core/src/trace/sampler/TraceIdRatioBasedSampler.ts +++ b/packages/opentelemetry-core/src/trace/sampler/TraceIdRatioBasedSampler.ts @@ -21,7 +21,10 @@ import { isValidTraceId, } from '@opentelemetry/api'; -/** Sampler that samples a given fraction of traces based of trace id deterministically. */ +/** + * @deprecated Use the one defined in @opentelemetry/sdk-trace-base instead. + * Sampler that samples a given fraction of traces based of trace id deterministically. + */ export class TraceIdRatioBasedSampler implements Sampler { private _upperBound: number; diff --git a/packages/opentelemetry-sdk-trace-base/README.md b/packages/opentelemetry-sdk-trace-base/README.md index f9d2871874..c4a40cc6f5 100644 --- a/packages/opentelemetry-sdk-trace-base/README.md +++ b/packages/opentelemetry-sdk-trace-base/README.md @@ -46,6 +46,107 @@ Tracing configuration is a merge of user supplied configuration with both the de configuration as specified in [config.ts](./src/config.ts) and an environmentally configurable sampling (via `OTEL_TRACES_SAMPLER` and `OTEL_TRACES_SAMPLER_ARG`). +## Built-in Samplers + +Sampler is used to make decisions on `Span` sampling. + +### AlwaysOn Sampler + +Samples every trace regardless of upstream sampling decisions. + +> This is used as a default Sampler + +```js +const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); +const { AlwaysOnSampler } = require("@opentelemetry/core"); + +const tracerProvider = new NodeTracerProvider({ + sampler: new AlwaysOnSampler() +}); +``` + +### AlwaysOff Sampler + +Doesn't sample any trace, regardless of upstream sampling decisions. + +```js +const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); +const { AlwaysOffSampler } = require("@opentelemetry/core"); + +const tracerProvider = new NodeTracerProvider({ + sampler: new AlwaysOffSampler() +}); +``` + +### TraceIdRatioBased Sampler + +Samples some percentage of traces, calculated deterministically using the trace ID. +Any trace that would be sampled at a given percentage will also be sampled at any higher percentage. + +The `TraceIDRatioSampler` may be used with the `ParentBasedSampler` to respect the sampled flag of an incoming trace. + +```js +const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); +const { TraceIdRatioBasedSampler } = require("@opentelemetry/core"); + +const tracerProvider = new NodeTracerProvider({ + // See details of ParentBasedSampler below + sampler: new ParentBasedSampler({ + // Trace ID Ratio Sampler accepts a positional argument + // which represents the percentage of traces which should + // be sampled. + root: new TraceIdRatioBasedSampler(0.5) + }); +}); +``` + +### ParentBased Sampler + +- This is a composite sampler. `ParentBased` helps distinguished between the +following cases: + - No parent (root span). + - Remote parent with `sampled` flag `true` + - Remote parent with `sampled` flag `false` + - Local parent with `sampled` flag `true` + - Local parent with `sampled` flag `false` + +Required parameters: + +- `root(Sampler)` - Sampler called for spans with no parent (root spans) + +Optional parameters: + +- `remoteParentSampled(Sampler)` (default: `AlwaysOn`) +- `remoteParentNotSampled(Sampler)` (default: `AlwaysOff`) +- `localParentSampled(Sampler)` (default: `AlwaysOn`) +- `localParentNotSampled(Sampler)` (default: `AlwaysOff`) + +|Parent| parent.isRemote() | parent.isSampled()| Invoke sampler| +|--|--|--|--| +|absent| n/a | n/a |`root()`| +|present|true|true|`remoteParentSampled()`| +|present|true|false|`remoteParentNotSampled()`| +|present|false|true|`localParentSampled()`| +|present|false|false|`localParentNotSampled()`| + +```js +const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); +const { ParentBasedSampler, AlwaysOffSampler, TraceIdRatioBasedSampler } = require("@opentelemetry/core"); + +const tracerProvider = new NodeTracerProvider({ + sampler: new ParentBasedSampler({ + // By default, the ParentBasedSampler will respect the parent span's sampling + // decision. This is configurable by providing a different sampler to use + // based on the situation. See configuration details above. + // + // This will delegate the sampling decision of all root traces (no parent) + // to the TraceIdRatioBasedSampler. + // See details of TraceIdRatioBasedSampler above. + root: new TraceIdRatioBasedSampler(0.5) + }) +}); +``` + ## Example See [examples/basic-tracer-node](https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/basic-tracer-node) for an end-to-end example, including exporting created spans. diff --git a/packages/opentelemetry-sdk-trace-base/src/IdGenerator.ts b/packages/opentelemetry-sdk-trace-base/src/IdGenerator.ts new file mode 100644 index 0000000000..65f63f9c0c --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/src/IdGenerator.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** IdGenerator provides an interface for generating Trace Id and Span Id */ +export interface IdGenerator { + /** Returns a trace ID composed of 32 lowercase hex characters. */ + generateTraceId(): string; + /** Returns a span ID composed of 16 lowercase hex characters. */ + generateSpanId(): string; +} diff --git a/packages/opentelemetry-sdk-trace-base/src/Sampler.ts b/packages/opentelemetry-sdk-trace-base/src/Sampler.ts new file mode 100644 index 0000000000..8c610180a4 --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/src/Sampler.ts @@ -0,0 +1,89 @@ +/* + * 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 { Context, Link, SpanAttributes, SpanKind } from '@opentelemetry/api'; + +/** + * A sampling decision that determines how a {@link Span} will be recorded + * and collected. + */ +export enum SamplingDecision { + /** + * `Span.isRecording() === false`, span will not be recorded and all events + * and attributes will be dropped. + */ + NOT_RECORD, + /** + * `Span.isRecording() === true`, but `Sampled` flag in {@link TraceFlags} + * MUST NOT be set. + */ + RECORD, + /** + * `Span.isRecording() === true` AND `Sampled` flag in {@link TraceFlags} + * MUST be set. + */ + RECORD_AND_SAMPLED, +} + +/** + * A sampling result contains a decision for a {@link Span} and additional + * attributes the sampler would like to added to the Span. + */ +export interface SamplingResult { + /** + * A sampling decision, refer to {@link SamplingDecision} for details. + */ + decision: SamplingDecision; + /** + * The list of attributes returned by SamplingResult MUST be immutable. + * Caller may call {@link Sampler}.shouldSample any number of times and + * can safely cache the returned value. + */ + attributes?: Readonly; +} + +/** + * This interface represent a sampler. Sampling is a mechanism to control the + * noise and overhead introduced by OpenTelemetry by reducing the number of + * samples of traces collected and sent to the backend. + */ +export interface Sampler { + /** + * Checks whether span needs to be created and tracked. + * + * @param context Parent Context which may contain a span. + * @param traceId of the span to be created. It can be different from the + * traceId in the {@link SpanContext}. Typically in situations when the + * span to be created starts a new trace. + * @param spanName of the span to be created. + * @param spanKind of the span to be created. + * @param attributes Initial set of SpanAttributes for the Span being constructed. + * @param links Collection of links that will be associated with the Span to + * be created. Typically useful for batch operations. + * @returns a {@link SamplingResult}. + */ + shouldSample( + context: Context, + traceId: string, + spanName: string, + spanKind: SpanKind, + attributes: SpanAttributes, + links: Link[] + ): SamplingResult; + + /** Returns the sampler name or short description with the configuration. */ + toString(): string; +} diff --git a/packages/opentelemetry-sdk-trace-base/src/Tracer.ts b/packages/opentelemetry-sdk-trace-base/src/Tracer.ts index aa4321d5e5..b77b5346b5 100644 --- a/packages/opentelemetry-sdk-trace-base/src/Tracer.ts +++ b/packages/opentelemetry-sdk-trace-base/src/Tracer.ts @@ -16,9 +16,7 @@ import * as api from '@opentelemetry/api'; import { - IdGenerator, InstrumentationLibrary, - RandomIdGenerator, sanitizeAttributes, isTracingSuppressed, } from '@opentelemetry/core'; @@ -28,12 +26,15 @@ import { Span } from './Span'; import { GeneralLimits, SpanLimits, TracerConfig } from './types'; import { mergeConfig } from './utility'; import { SpanProcessor } from './SpanProcessor'; +import { Sampler } from './Sampler'; +import { IdGenerator } from './IdGenerator'; +import { RandomIdGenerator } from './platform'; /** * This class represents a basic tracer. */ export class Tracer implements api.Tracer { - private readonly _sampler: api.Sampler; + private readonly _sampler: Sampler; private readonly _generalLimits: GeneralLimits; private readonly _spanLimits: SpanLimits; private readonly _idGenerator: IdGenerator; diff --git a/packages/opentelemetry-sdk-trace-base/src/config.ts b/packages/opentelemetry-sdk-trace-base/src/config.ts index 7895514597..47e32f5b35 100644 --- a/packages/opentelemetry-sdk-trace-base/src/config.ts +++ b/packages/opentelemetry-sdk-trace-base/src/config.ts @@ -14,16 +14,17 @@ * limitations under the License. */ -import { diag, Sampler } from '@opentelemetry/api'; +import { diag } from '@opentelemetry/api'; import { - AlwaysOffSampler, - AlwaysOnSampler, getEnv, TracesSamplerValues, - ParentBasedSampler, ENVIRONMENT, - TraceIdRatioBasedSampler, } from '@opentelemetry/core'; +import { Sampler } from './Sampler'; +import { AlwaysOffSampler } from './sampler/AlwaysOffSampler'; +import { AlwaysOnSampler } from './sampler/AlwaysOnSampler'; +import { ParentBasedSampler } from './sampler/ParentBasedSampler'; +import { TraceIdRatioBasedSampler } from './sampler/TraceIdRatioBasedSampler'; const env = getEnv(); const FALLBACK_OTEL_TRACES_SAMPLER = TracesSamplerValues.AlwaysOn; diff --git a/packages/opentelemetry-sdk-trace-base/src/index.ts b/packages/opentelemetry-sdk-trace-base/src/index.ts index 3fb4bc2248..38a787950e 100644 --- a/packages/opentelemetry-sdk-trace-base/src/index.ts +++ b/packages/opentelemetry-sdk-trace-base/src/index.ts @@ -23,7 +23,13 @@ export * from './export/ReadableSpan'; export * from './export/SimpleSpanProcessor'; export * from './export/SpanExporter'; export * from './export/NoopSpanProcessor'; +export * from './sampler/AlwaysOffSampler'; +export * from './sampler/AlwaysOnSampler'; +export * from './sampler/ParentBasedSampler'; +export * from './sampler/TraceIdRatioBasedSampler'; +export * from './Sampler'; export * from './Span'; export * from './SpanProcessor'; export * from './TimedEvent'; export * from './types'; +export * from './IdGenerator'; diff --git a/packages/opentelemetry-sdk-trace-base/src/platform/browser/RandomIdGenerator.ts b/packages/opentelemetry-sdk-trace-base/src/platform/browser/RandomIdGenerator.ts new file mode 100644 index 0000000000..5f7e7ee98f --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/src/platform/browser/RandomIdGenerator.ts @@ -0,0 +1,51 @@ +/* + * 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 { IdGenerator } from '../../IdGenerator'; + +const SPAN_ID_BYTES = 8; +const TRACE_ID_BYTES = 16; + +export class RandomIdGenerator implements IdGenerator { + /** + * Returns a random 16-byte trace ID formatted/encoded as a 32 lowercase hex + * characters corresponding to 128 bits. + */ + generateTraceId = getIdGenerator(TRACE_ID_BYTES); + + /** + * Returns a random 8-byte span ID formatted/encoded as a 16 lowercase hex + * characters corresponding to 64 bits. + */ + generateSpanId = getIdGenerator(SPAN_ID_BYTES); +} + +const SHARED_CHAR_CODES_ARRAY = Array(32); +function getIdGenerator(bytes: number): () => string { + return function generateId() { + for (let i = 0; i < bytes * 2; i++) { + SHARED_CHAR_CODES_ARRAY[i] = Math.floor(Math.random() * 16) + 48; + // valid hex characters in the range 48-57 and 97-102 + if (SHARED_CHAR_CODES_ARRAY[i] >= 58) { + SHARED_CHAR_CODES_ARRAY[i] += 39; + } + } + return String.fromCharCode.apply( + null, + SHARED_CHAR_CODES_ARRAY.slice(0, bytes * 2) + ); + }; +} diff --git a/packages/opentelemetry-sdk-trace-base/src/platform/browser/index.ts b/packages/opentelemetry-sdk-trace-base/src/platform/browser/index.ts index 7992546b9d..652d686e04 100644 --- a/packages/opentelemetry-sdk-trace-base/src/platform/browser/index.ts +++ b/packages/opentelemetry-sdk-trace-base/src/platform/browser/index.ts @@ -15,3 +15,4 @@ */ export * from './export/BatchSpanProcessor'; +export * from './RandomIdGenerator'; diff --git a/packages/opentelemetry-sdk-trace-base/src/platform/node/RandomIdGenerator.ts b/packages/opentelemetry-sdk-trace-base/src/platform/node/RandomIdGenerator.ts new file mode 100644 index 0000000000..ee47d993a4 --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/src/platform/node/RandomIdGenerator.ts @@ -0,0 +1,56 @@ +/* + * 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 { IdGenerator } from '../../IdGenerator'; + +const SPAN_ID_BYTES = 8; +const TRACE_ID_BYTES = 16; + +export class RandomIdGenerator implements IdGenerator { + /** + * Returns a random 16-byte trace ID formatted/encoded as a 32 lowercase hex + * characters corresponding to 128 bits. + */ + generateTraceId = getIdGenerator(TRACE_ID_BYTES); + + /** + * Returns a random 8-byte span ID formatted/encoded as a 16 lowercase hex + * characters corresponding to 64 bits. + */ + generateSpanId = getIdGenerator(SPAN_ID_BYTES); +} + +const SHARED_BUFFER = Buffer.allocUnsafe(TRACE_ID_BYTES); +function getIdGenerator(bytes: number): () => string { + return function generateId() { + for (let i = 0; i < bytes / 4; i++) { + // unsigned right shift drops decimal part of the number + // it is required because if a number between 2**32 and 2**32 - 1 is generated, an out of range error is thrown by writeUInt32BE + SHARED_BUFFER.writeUInt32BE((Math.random() * 2 ** 32) >>> 0, i * 4); + } + + // If buffer is all 0, set the last byte to 1 to guarantee a valid w3c id is generated + for (let i = 0; i < bytes; i++) { + if (SHARED_BUFFER[i] > 0) { + break; + } else if (i === bytes - 1) { + SHARED_BUFFER[bytes - 1] = 1; + } + } + + return SHARED_BUFFER.toString('hex', 0, bytes); + }; +} diff --git a/packages/opentelemetry-sdk-trace-base/src/platform/node/index.ts b/packages/opentelemetry-sdk-trace-base/src/platform/node/index.ts index 7992546b9d..652d686e04 100644 --- a/packages/opentelemetry-sdk-trace-base/src/platform/node/index.ts +++ b/packages/opentelemetry-sdk-trace-base/src/platform/node/index.ts @@ -15,3 +15,4 @@ */ export * from './export/BatchSpanProcessor'; +export * from './RandomIdGenerator'; diff --git a/packages/opentelemetry-sdk-trace-base/src/sampler/AlwaysOffSampler.ts b/packages/opentelemetry-sdk-trace-base/src/sampler/AlwaysOffSampler.ts new file mode 100644 index 0000000000..1ea87b5bdc --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/src/sampler/AlwaysOffSampler.ts @@ -0,0 +1,30 @@ +/* + * 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 { Sampler, SamplingDecision, SamplingResult } from '../Sampler'; + +/** Sampler that samples no traces. */ +export class AlwaysOffSampler implements Sampler { + shouldSample(): SamplingResult { + return { + decision: SamplingDecision.NOT_RECORD, + }; + } + + toString(): string { + return 'AlwaysOffSampler'; + } +} diff --git a/packages/opentelemetry-sdk-trace-base/src/sampler/AlwaysOnSampler.ts b/packages/opentelemetry-sdk-trace-base/src/sampler/AlwaysOnSampler.ts new file mode 100644 index 0000000000..9f5d6da15d --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/src/sampler/AlwaysOnSampler.ts @@ -0,0 +1,30 @@ +/* + * 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 { Sampler, SamplingDecision, SamplingResult } from '../Sampler'; + +/** Sampler that samples all traces. */ +export class AlwaysOnSampler implements Sampler { + shouldSample(): SamplingResult { + return { + decision: SamplingDecision.RECORD_AND_SAMPLED, + }; + } + + toString(): string { + return 'AlwaysOnSampler'; + } +} diff --git a/packages/opentelemetry-sdk-trace-base/src/sampler/ParentBasedSampler.ts b/packages/opentelemetry-sdk-trace-base/src/sampler/ParentBasedSampler.ts new file mode 100644 index 0000000000..0b411024cd --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/src/sampler/ParentBasedSampler.ts @@ -0,0 +1,140 @@ +/* + * 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 { + Context, + isSpanContextValid, + Link, + SpanAttributes, + SpanKind, + TraceFlags, trace, +} from '@opentelemetry/api'; +import { globalErrorHandler } from '@opentelemetry/core'; +import { AlwaysOffSampler } from './AlwaysOffSampler'; +import { AlwaysOnSampler } from './AlwaysOnSampler'; +import { Sampler, SamplingResult } from '../Sampler'; + +/** + * A composite sampler that either respects the parent span's sampling decision + * or delegates to `delegateSampler` for root spans. + */ +export class ParentBasedSampler implements Sampler { + private _root: Sampler; + private _remoteParentSampled: Sampler; + private _remoteParentNotSampled: Sampler; + private _localParentSampled: Sampler; + private _localParentNotSampled: Sampler; + + constructor(config: ParentBasedSamplerConfig) { + this._root = config.root; + + if (!this._root) { + globalErrorHandler( + new Error('ParentBasedSampler must have a root sampler configured') + ); + this._root = new AlwaysOnSampler(); + } + + this._remoteParentSampled = + config.remoteParentSampled ?? new AlwaysOnSampler(); + this._remoteParentNotSampled = + config.remoteParentNotSampled ?? new AlwaysOffSampler(); + this._localParentSampled = + config.localParentSampled ?? new AlwaysOnSampler(); + this._localParentNotSampled = + config.localParentNotSampled ?? new AlwaysOffSampler(); + } + + shouldSample( + context: Context, + traceId: string, + spanName: string, + spanKind: SpanKind, + attributes: SpanAttributes, + links: Link[] + ): SamplingResult { + const parentContext = trace.getSpanContext(context); + + if (!parentContext || !isSpanContextValid(parentContext)) { + return this._root.shouldSample( + context, + traceId, + spanName, + spanKind, + attributes, + links + ); + } + + if (parentContext.isRemote) { + if (parentContext.traceFlags & TraceFlags.SAMPLED) { + return this._remoteParentSampled.shouldSample( + context, + traceId, + spanName, + spanKind, + attributes, + links + ); + } + return this._remoteParentNotSampled.shouldSample( + context, + traceId, + spanName, + spanKind, + attributes, + links + ); + } + + if (parentContext.traceFlags & TraceFlags.SAMPLED) { + return this._localParentSampled.shouldSample( + context, + traceId, + spanName, + spanKind, + attributes, + links + ); + } + + return this._localParentNotSampled.shouldSample( + context, + traceId, + spanName, + spanKind, + attributes, + links + ); + } + + toString(): string { + return `ParentBased{root=${this._root.toString()}, remoteParentSampled=${this._remoteParentSampled.toString()}, remoteParentNotSampled=${this._remoteParentNotSampled.toString()}, localParentSampled=${this._localParentSampled.toString()}, localParentNotSampled=${this._localParentNotSampled.toString()}}`; + } +} + +interface ParentBasedSamplerConfig { + /** Sampler called for spans with no parent */ + root: Sampler; + /** Sampler called for spans with a remote parent which was sampled. Default AlwaysOn */ + remoteParentSampled?: Sampler; + /** Sampler called for spans with a remote parent which was not sampled. Default AlwaysOff */ + remoteParentNotSampled?: Sampler; + /** Sampler called for spans with a local parent which was sampled. Default AlwaysOn */ + localParentSampled?: Sampler; + /** Sampler called for spans with a local parent which was not sampled. Default AlwaysOff */ + localParentNotSampled?: Sampler; +} diff --git a/packages/opentelemetry-sdk-trace-base/src/sampler/TraceIdRatioBasedSampler.ts b/packages/opentelemetry-sdk-trace-base/src/sampler/TraceIdRatioBasedSampler.ts new file mode 100644 index 0000000000..33344a90ae --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/src/sampler/TraceIdRatioBasedSampler.ts @@ -0,0 +1,57 @@ +/* + * 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 { isValidTraceId } from '@opentelemetry/api'; +import { Sampler, SamplingDecision, SamplingResult } from '../Sampler'; + + +/** Sampler that samples a given fraction of traces based of trace id deterministically. */ +export class TraceIdRatioBasedSampler implements Sampler { + private _upperBound: number; + + constructor(private readonly _ratio: number = 0) { + this._ratio = this._normalize(_ratio); + this._upperBound = Math.floor(this._ratio * 0xffffffff); + } + + shouldSample(context: unknown, traceId: string): SamplingResult { + return { + decision: + isValidTraceId(traceId) && this._accumulate(traceId) < this._upperBound + ? SamplingDecision.RECORD_AND_SAMPLED + : SamplingDecision.NOT_RECORD, + }; + } + + toString(): string { + return `TraceIdRatioBased{${this._ratio}}`; + } + + private _normalize(ratio: number): number { + if (typeof ratio !== 'number' || isNaN(ratio)) return 0; + return ratio >= 1 ? 1 : ratio <= 0 ? 0 : ratio; + } + + private _accumulate(traceId: string): number { + let accumulation = 0; + for (let i = 0; i < traceId.length / 8; i++) { + const pos = i * 8; + const part = parseInt(traceId.slice(pos, pos + 8), 16); + accumulation = (accumulation ^ part) >>> 0; + } + return accumulation; + } +} diff --git a/packages/opentelemetry-sdk-trace-base/src/types.ts b/packages/opentelemetry-sdk-trace-base/src/types.ts index d60df053ac..54eaaf97d9 100644 --- a/packages/opentelemetry-sdk-trace-base/src/types.ts +++ b/packages/opentelemetry-sdk-trace-base/src/types.ts @@ -14,11 +14,10 @@ * limitations under the License. */ -import { TextMapPropagator, Sampler } from '@opentelemetry/api'; -import { IdGenerator } from '@opentelemetry/core'; - -import { ContextManager } from '@opentelemetry/api'; +import { ContextManager, TextMapPropagator } from '@opentelemetry/api'; import { Resource } from '@opentelemetry/resources'; +import { IdGenerator } from './IdGenerator'; +import { Sampler } from './Sampler'; /** * TracerConfig provides an interface for configuring a Basic Tracer. diff --git a/packages/opentelemetry-sdk-trace-base/src/utility.ts b/packages/opentelemetry-sdk-trace-base/src/utility.ts index 684f68a210..819d725d49 100644 --- a/packages/opentelemetry-sdk-trace-base/src/utility.ts +++ b/packages/opentelemetry-sdk-trace-base/src/utility.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { Sampler } from '@opentelemetry/api'; import { buildSamplerFromEnv, loadDefaultConfig } from './config'; +import { Sampler } from './Sampler'; import { SpanLimits, TracerConfig, GeneralLimits } from './types'; import { DEFAULT_ATTRIBUTE_COUNT_LIMIT, diff --git a/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts index 88ec6fa16a..05ec1152de 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts @@ -29,8 +29,6 @@ import { } from '@opentelemetry/api'; import { CompositePropagator } from '@opentelemetry/core'; import { - AlwaysOffSampler, - AlwaysOnSampler, TraceState, W3CTraceContextPropagator, } from '@opentelemetry/core'; @@ -44,6 +42,8 @@ import { InMemorySpanExporter, SpanExporter, BatchSpanProcessor, + AlwaysOnSampler, + AlwaysOffSampler, } from '../../src'; class DummyPropagator implements TextMapPropagator { diff --git a/packages/opentelemetry-sdk-trace-base/test/common/Sampler.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/Sampler.test.ts new file mode 100644 index 0000000000..1e4682e356 --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/test/common/Sampler.test.ts @@ -0,0 +1,54 @@ +/* + * 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 { context, SpanKind } from '@opentelemetry/api'; +import { + AlwaysOffSampler, + AlwaysOnSampler, + ParentBasedSampler, + Sampler, + SamplingDecision, + SamplingResult, + TraceIdRatioBasedSampler, +} from '../../src'; +import { assertAssignable } from './util'; + +describe('Sampler', () => { + const samplers = [ + new AlwaysOffSampler(), + new AlwaysOnSampler(), + new ParentBasedSampler({ root: new AlwaysOffSampler() }), + new TraceIdRatioBasedSampler(), + ] as const; + + it('Samplers defined in @opentelemetry/core should fit the interface', () => { + for (const sampler of samplers) { + assertAssignable(sampler); + } + }); + + it('Sampler return values should fit SamplerResult', () => { + function assertResult(sampler: T) { + const result = sampler.shouldSample(context.active(), 'trace-id', 'span-name', SpanKind.INTERNAL, {}, []); + assertAssignable(result); + assertAssignable(result.decision); + } + + for (const sampler of samplers) { + assertResult(sampler); + } + }); +}); diff --git a/packages/opentelemetry-sdk-trace-base/test/common/Tracer.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/Tracer.test.ts index 31652f8b6e..d55374514c 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/Tracer.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/Tracer.test.ts @@ -21,8 +21,6 @@ import { INVALID_TRACEID, Link, ROOT_CONTEXT, - Sampler, - SamplingDecision, SpanContext, SpanKind, trace, @@ -30,14 +28,21 @@ import { } from '@opentelemetry/api'; import { getSpan } from '@opentelemetry/api/build/src/trace/context-utils'; import { - AlwaysOffSampler, - AlwaysOnSampler, InstrumentationLibrary, sanitizeAttributes, suppressTracing } from '@opentelemetry/core'; import * as assert from 'assert'; -import { BasicTracerProvider, Span, SpanProcessor, Tracer } from '../../src'; +import { + AlwaysOffSampler, + AlwaysOnSampler, + BasicTracerProvider, + Sampler, + SamplingDecision, + Span, + SpanProcessor, + Tracer, +} from '../../src'; import { TestStackContextManager } from './export/TestStackContextManager'; import * as sinon from 'sinon'; import { invalidAttributes, validAttributes } from './util'; diff --git a/packages/opentelemetry-sdk-trace-base/test/common/config.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/config.test.ts index f627cc18a7..c3c929fad2 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/config.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/config.test.ts @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import * as assert from 'assert'; import { AlwaysOffSampler, AlwaysOnSampler, ParentBasedSampler, TraceIdRatioBasedSampler, -} from '@opentelemetry/core'; -import * as assert from 'assert'; +} from '../../src'; import { buildSamplerFromEnv } from '../../src/config'; describe('config', () => { diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts index 52cd832a3e..8d8de40d32 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts @@ -16,14 +16,13 @@ import { diag, ROOT_CONTEXT } from '@opentelemetry/api'; import { - AlwaysOnSampler, ExportResultCode, loggingErrorHandler, setGlobalErrorHandler, } from '@opentelemetry/core'; import * as assert from 'assert'; import * as sinon from 'sinon'; -import { BasicTracerProvider, BufferConfig, InMemorySpanExporter, Span } from '../../../src'; +import { AlwaysOnSampler, BasicTracerProvider, BufferConfig, InMemorySpanExporter, Span } from '../../../src'; import { context } from '@opentelemetry/api'; import { TestRecordOnlySampler } from './TestRecordOnlySampler'; import { TestTracingSpanExporter } from './TestTracingSpanExporter'; diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts index 0a66b13fc2..e6712adb95 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts @@ -18,10 +18,10 @@ import { SpanContext, TraceFlags, } from '@opentelemetry/api'; -import { AlwaysOnSampler } from '@opentelemetry/core'; import * as assert from 'assert'; import * as sinon from 'sinon'; import { + AlwaysOnSampler, BasicTracerProvider, ConsoleSpanExporter, SimpleSpanProcessor, diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/TestRecordOnlySampler.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/TestRecordOnlySampler.ts index c456780e86..02d0626dc7 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/TestRecordOnlySampler.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/TestRecordOnlySampler.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Sampler, SamplingDecision, SamplingResult } from '@opentelemetry/api'; +import { Sampler, SamplingDecision, SamplingResult } from '../../../src'; /** Sampler that always records but doesn't sample spans. */ export class TestRecordOnlySampler implements Sampler { diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/TestTracingSpanExporter.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/TestTracingSpanExporter.ts index 9415c67fe7..89eae8d8b5 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/TestTracingSpanExporter.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/TestTracingSpanExporter.ts @@ -14,14 +14,15 @@ * limitations under the License. */ +import { ExportResult } from '@opentelemetry/core'; import { BasicTracerProvider, InMemorySpanExporter, ReadableSpan, Tracer, SpanProcessor, + AlwaysOnSampler, } from '../../../src'; -import { ExportResult, AlwaysOnSampler } from '@opentelemetry/core'; /** * A test-only span exporter that naively simulates triggering instrumentation diff --git a/packages/opentelemetry-sdk-trace-base/test/common/platform/RandomIdGenerator.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/platform/RandomIdGenerator.test.ts new file mode 100644 index 0000000000..ad1ba5028a --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/test/common/platform/RandomIdGenerator.test.ts @@ -0,0 +1,53 @@ +/* + * 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 { RandomIdGenerator } from '../../../src/platform'; + +const idGenerator = new RandomIdGenerator(); + +describe('randomTraceId', () => { + let traceId1: string, traceId2: string; + beforeEach(() => { + traceId1 = idGenerator.generateTraceId(); + traceId2 = idGenerator.generateTraceId(); + }); + + it('returns 32 character hex strings', () => { + assert.ok(traceId1.match(/[a-f0-9]{32}/)); + assert.ok(!traceId1.match(/^0+$/)); + }); + + it('returns different ids on each call', () => { + assert.notDeepStrictEqual(traceId1, traceId2); + }); +}); + +describe('randomSpanId', () => { + let spanId1: string, spanId2: string; + beforeEach(() => { + spanId1 = idGenerator.generateSpanId(); + spanId2 = idGenerator.generateSpanId(); + }); + + it('returns 16 character hex strings', () => { + assert.ok(spanId1.match(/[a-f0-9]{16}/)); + assert.ok(!spanId1.match(/^0+$/)); + }); + + it('returns different ids on each call', () => { + assert.notDeepStrictEqual(spanId1, spanId2); + }); +}); diff --git a/packages/opentelemetry-sdk-trace-base/test/common/sampler/AlwaysOffSampler.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/sampler/AlwaysOffSampler.test.ts new file mode 100644 index 0000000000..d591dc53e7 --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/test/common/sampler/AlwaysOffSampler.test.ts @@ -0,0 +1,32 @@ +/* + * 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 * as api from '@opentelemetry/api'; +import { AlwaysOffSampler } from '../../../src/sampler/AlwaysOffSampler'; + +describe('AlwaysOffSampler', () => { + it('should reflect sampler name', () => { + const sampler = new AlwaysOffSampler(); + assert.strictEqual(sampler.toString(), 'AlwaysOffSampler'); + }); + + it('should return decision: api.SamplingDecision.NOT_RECORD for AlwaysOffSampler', () => { + const sampler = new AlwaysOffSampler(); + assert.deepStrictEqual(sampler.shouldSample(), { + decision: api.SamplingDecision.NOT_RECORD, + }); + }); +}); diff --git a/packages/opentelemetry-sdk-trace-base/test/common/sampler/AlwaysOnSampler.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/sampler/AlwaysOnSampler.test.ts new file mode 100644 index 0000000000..95825dc798 --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/test/common/sampler/AlwaysOnSampler.test.ts @@ -0,0 +1,32 @@ +/* + * 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 * as api from '@opentelemetry/api'; +import { AlwaysOnSampler } from '../../../src/sampler/AlwaysOnSampler'; + +describe('AlwaysOnSampler', () => { + it('should reflect sampler name', () => { + const sampler = new AlwaysOnSampler(); + assert.strictEqual(sampler.toString(), 'AlwaysOnSampler'); + }); + + it('should return api.SamplingDecision.RECORD_AND_SAMPLED for AlwaysOnSampler', () => { + const sampler = new AlwaysOnSampler(); + assert.deepStrictEqual(sampler.shouldSample(), { + decision: api.SamplingDecision.RECORD_AND_SAMPLED, + }); + }); +}); diff --git a/packages/opentelemetry-sdk-trace-base/test/common/sampler/ParentBasedSampler.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/sampler/ParentBasedSampler.test.ts new file mode 100644 index 0000000000..22ebfb8092 --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/test/common/sampler/ParentBasedSampler.test.ts @@ -0,0 +1,170 @@ +/* + * 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 * as api from '@opentelemetry/api'; +import { TraceFlags, SpanKind, trace } from '@opentelemetry/api'; +import { + AlwaysOnSampler, + ParentBasedSampler, + AlwaysOffSampler, + TraceIdRatioBasedSampler, +} from '../../../src'; + +const traceId = 'd4cda95b652f4a1592b449d5929fda1b'; +const spanId = '6e0c63257de34c92'; +const spanName = 'foobar'; + +describe('ParentBasedSampler', () => { + it('should reflect sampler name with delegate sampler', () => { + let sampler = new ParentBasedSampler({ root: new AlwaysOnSampler() }); + assert.strictEqual( + sampler.toString(), + 'ParentBased{root=AlwaysOnSampler, remoteParentSampled=AlwaysOnSampler, remoteParentNotSampled=AlwaysOffSampler, localParentSampled=AlwaysOnSampler, localParentNotSampled=AlwaysOffSampler}' + ); + + sampler = new ParentBasedSampler({ root: new AlwaysOffSampler() }); + assert.strictEqual( + sampler.toString(), + 'ParentBased{root=AlwaysOffSampler, remoteParentSampled=AlwaysOnSampler, remoteParentNotSampled=AlwaysOffSampler, localParentSampled=AlwaysOnSampler, localParentNotSampled=AlwaysOffSampler}' + ); + + sampler = new ParentBasedSampler({ + root: new TraceIdRatioBasedSampler(0.5), + }); + assert.strictEqual( + sampler.toString(), + 'ParentBased{root=TraceIdRatioBased{0.5}, remoteParentSampled=AlwaysOnSampler, remoteParentNotSampled=AlwaysOffSampler, localParentSampled=AlwaysOnSampler, localParentNotSampled=AlwaysOffSampler}' + ); + }); + + it('should return api.SamplingDecision.NOT_RECORD for not sampled parent while composited with AlwaysOnSampler', () => { + const sampler = new ParentBasedSampler({ root: new AlwaysOnSampler() }); + + const spanContext = { + traceId, + spanId, + traceFlags: TraceFlags.NONE, + }; + assert.deepStrictEqual( + sampler.shouldSample( + trace.setSpanContext(api.ROOT_CONTEXT, spanContext), + traceId, + spanName, + SpanKind.CLIENT, + {}, + [] + ), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + }); + + it('should return api.SamplingDecision.RECORD_AND_SAMPLED for invalid parent spanContext while composited with AlwaysOnSampler', () => { + const sampler = new ParentBasedSampler({ root: new AlwaysOnSampler() }); + + assert.deepStrictEqual( + sampler.shouldSample( + trace.setSpanContext(api.ROOT_CONTEXT, api.INVALID_SPAN_CONTEXT), + traceId, + spanName, + SpanKind.CLIENT, + {}, + [] + ), + { + decision: api.SamplingDecision.RECORD_AND_SAMPLED, + } + ); + }); + + it('should return api.SamplingDecision.RECORD_AND_SAMPLED while composited with AlwaysOnSampler', () => { + const sampler = new ParentBasedSampler({ root: new AlwaysOnSampler() }); + + assert.deepStrictEqual( + sampler.shouldSample( + api.ROOT_CONTEXT, + traceId, + spanName, + SpanKind.CLIENT, + {}, + [] + ), + { + decision: api.SamplingDecision.RECORD_AND_SAMPLED, + } + ); + }); + + it('should return api.SamplingDecision.RECORD_AND_SAMPLED for sampled parent while composited with AlwaysOffSampler', () => { + const sampler = new ParentBasedSampler({ root: new AlwaysOffSampler() }); + + const spanContext = { + traceId, + spanId, + traceFlags: TraceFlags.SAMPLED, + }; + assert.deepStrictEqual( + sampler.shouldSample( + trace.setSpanContext(api.ROOT_CONTEXT, spanContext), + traceId, + spanName, + SpanKind.CLIENT, + {}, + [] + ), + { + decision: api.SamplingDecision.RECORD_AND_SAMPLED, + } + ); + }); + + it('should return api.SamplingDecision.NOT_RECORD for invalid parent spanContext while composited with AlwaysOffSampler', () => { + const sampler = new ParentBasedSampler({ root: new AlwaysOffSampler() }); + + assert.deepStrictEqual( + sampler.shouldSample( + trace.setSpanContext(api.ROOT_CONTEXT, api.INVALID_SPAN_CONTEXT), + traceId, + spanName, + SpanKind.CLIENT, + {}, + [] + ), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + }); + + it('should return api.SamplingDecision.RECORD_AND_SAMPLED while composited with AlwaysOffSampler', () => { + const sampler = new ParentBasedSampler({ root: new AlwaysOffSampler() }); + + assert.deepStrictEqual( + sampler.shouldSample( + api.ROOT_CONTEXT, + traceId, + spanName, + SpanKind.CLIENT, + {}, + [] + ), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + }); +}); diff --git a/packages/opentelemetry-sdk-trace-base/test/common/sampler/TraceIdRatioBasedSampler.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/sampler/TraceIdRatioBasedSampler.test.ts new file mode 100644 index 0000000000..d40219f956 --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/test/common/sampler/TraceIdRatioBasedSampler.test.ts @@ -0,0 +1,197 @@ +/* + * 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 * as api from '@opentelemetry/api'; +import { TraceIdRatioBasedSampler } from '../../../src/sampler/TraceIdRatioBasedSampler'; + +const spanContext = (traceId = '1') => ({ + traceId, + spanId: '1.1', + traceFlags: api.TraceFlags.NONE, +}); + +const traceId = (part: string) => ('0'.repeat(32) + part).slice(-32); + +describe('TraceIdRatioBasedSampler', () => { + it('should reflect sampler name with ratio', () => { + let sampler = new TraceIdRatioBasedSampler(1.0); + assert.strictEqual(sampler.toString(), 'TraceIdRatioBased{1}'); + + sampler = new TraceIdRatioBasedSampler(0.5); + assert.strictEqual(sampler.toString(), 'TraceIdRatioBased{0.5}'); + + sampler = new TraceIdRatioBasedSampler(0); + assert.strictEqual(sampler.toString(), 'TraceIdRatioBased{0}'); + + sampler = new TraceIdRatioBasedSampler(-0); + assert.strictEqual(sampler.toString(), 'TraceIdRatioBased{0}'); + + sampler = new TraceIdRatioBasedSampler(undefined); + assert.strictEqual(sampler.toString(), 'TraceIdRatioBased{0}'); + + sampler = new TraceIdRatioBasedSampler(NaN); + assert.strictEqual(sampler.toString(), 'TraceIdRatioBased{0}'); + + sampler = new TraceIdRatioBasedSampler(+Infinity); + assert.strictEqual(sampler.toString(), 'TraceIdRatioBased{1}'); + + sampler = new TraceIdRatioBasedSampler(-Infinity); + assert.strictEqual(sampler.toString(), 'TraceIdRatioBased{0}'); + }); + + it('should return a always sampler for 1', () => { + const sampler = new TraceIdRatioBasedSampler(1); + assert.deepStrictEqual( + sampler.shouldSample(spanContext(traceId('1')), traceId('1')), + { + decision: api.SamplingDecision.RECORD_AND_SAMPLED, + } + ); + }); + + it('should return a always sampler for >1', () => { + const sampler = new TraceIdRatioBasedSampler(100); + assert.deepStrictEqual( + sampler.shouldSample(spanContext(traceId('1')), traceId('1')), + { + decision: api.SamplingDecision.RECORD_AND_SAMPLED, + } + ); + }); + + it('should return a never sampler for 0', () => { + const sampler = new TraceIdRatioBasedSampler(0); + assert.deepStrictEqual( + sampler.shouldSample(spanContext(traceId('1')), traceId('1')), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + }); + + it('should return a never sampler for <0', () => { + const sampler = new TraceIdRatioBasedSampler(-1); + assert.deepStrictEqual( + sampler.shouldSample(spanContext(traceId('1')), traceId('1')), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + }); + + it('should handle NaN', () => { + const sampler = new TraceIdRatioBasedSampler(NaN); + assert.strictEqual(sampler.toString(), 'TraceIdRatioBased{0}'); + assert.deepStrictEqual( + sampler.shouldSample(spanContext(traceId('1')), traceId('1')), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + }); + + it('should handle -NaN', () => { + const sampler = new TraceIdRatioBasedSampler(-NaN); + assert.strictEqual(sampler.toString(), 'TraceIdRatioBased{0}'); + assert.deepStrictEqual( + sampler.shouldSample(spanContext(traceId('1')), traceId('1')), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + }); + + it('should handle undefined', () => { + const sampler = new TraceIdRatioBasedSampler(undefined); + assert.strictEqual(sampler.toString(), 'TraceIdRatioBased{0}'); + assert.deepStrictEqual( + sampler.shouldSample(spanContext(traceId('1')), traceId('1')), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + }); + + it('should sample based on trace id', () => { + const sampler = new TraceIdRatioBasedSampler(0.2); + assert.deepStrictEqual( + sampler.shouldSample(spanContext(traceId('1')), traceId('1')), + { + decision: api.SamplingDecision.RECORD_AND_SAMPLED, + } + ); + + assert.deepStrictEqual( + sampler.shouldSample( + spanContext(traceId('33333333')), + traceId('33333333') + ), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + }); + + it('should not sample with a invalid trace id', () => { + const sampler = new TraceIdRatioBasedSampler(1); + assert.deepStrictEqual(sampler.shouldSample(spanContext(''), ''), { + decision: api.SamplingDecision.NOT_RECORD, + }); + + assert.deepStrictEqual( + sampler.shouldSample(spanContext(traceId('g')), traceId('g')), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + }); + + it('should sample traces that a lower sampling ratio would sample', () => { + const sampler10 = new TraceIdRatioBasedSampler(0.1); + const sampler20 = new TraceIdRatioBasedSampler(0.2); + + const id1 = traceId((Math.floor(0xffffffff * 0.1) - 1).toString(16)); + assert.deepStrictEqual(sampler10.shouldSample(spanContext(id1), id1), { + decision: api.SamplingDecision.RECORD_AND_SAMPLED, + }); + assert.deepStrictEqual(sampler20.shouldSample(spanContext(id1), id1), { + decision: api.SamplingDecision.RECORD_AND_SAMPLED, + }); + + const id2 = traceId((Math.floor(0xffffffff * 0.2) - 1).toString(16)); + assert.deepStrictEqual(sampler10.shouldSample(spanContext(id2), id2), { + decision: api.SamplingDecision.NOT_RECORD, + }); + assert.deepStrictEqual(sampler20.shouldSample(spanContext(id2), id2), { + decision: api.SamplingDecision.RECORD_AND_SAMPLED, + }); + + const id2delta = traceId(Math.floor(0xffffffff * 0.2).toString(16)); + assert.deepStrictEqual( + sampler10.shouldSample(spanContext(id2delta), id2delta), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + assert.deepStrictEqual( + sampler20.shouldSample(spanContext(id2delta), id2delta), + { + decision: api.SamplingDecision.NOT_RECORD, + } + ); + }); +}); diff --git a/packages/opentelemetry-sdk-trace-base/test/common/util.ts b/packages/opentelemetry-sdk-trace-base/test/common/util.ts index 6b27471c28..ab34f2ab1b 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/util.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/util.ts @@ -31,3 +31,5 @@ export const invalidAttributes = { // This empty length attribute should not be set '': 'empty-key', }; + +export function assertAssignable(val: T): asserts val is T {} diff --git a/packages/opentelemetry-sdk-trace-node/test/NodeTracerProvider.test.ts b/packages/opentelemetry-sdk-trace-node/test/NodeTracerProvider.test.ts index 24ba20f4b0..3ae6011759 100644 --- a/packages/opentelemetry-sdk-trace-node/test/NodeTracerProvider.test.ts +++ b/packages/opentelemetry-sdk-trace-node/test/NodeTracerProvider.test.ts @@ -29,9 +29,10 @@ import { trace, TraceFlags, } from '@opentelemetry/api'; -import { AlwaysOnSampler, AlwaysOffSampler } from '@opentelemetry/core'; import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; import { + AlwaysOffSampler, + AlwaysOnSampler, BatchSpanProcessor, InMemorySpanExporter, Span,