diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md
index 978ca31b371..7e800b4936e 100644
--- a/experimental/CHANGELOG.md
+++ b/experimental/CHANGELOG.md
@@ -10,6 +10,8 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
### :rocket: Features
+* feat(sampler-composite): Added experimental implementations of draft composite sampling spec [#5839](https://github.com/open-telemetry/opentelemetry-js/pull/5839) @anuraaga
+
### :bug: Bug Fixes
* fix(instrumentation-http): respect requireParent flag when INVALID_SPAN_CONTEXT is used [#4788](https://github.com/open-telemetry/opentelemetry-js/pull/4788) @reberhardt7
diff --git a/experimental/packages/sampler-composite/.eslintignore b/experimental/packages/sampler-composite/.eslintignore
new file mode 100644
index 00000000000..378eac25d31
--- /dev/null
+++ b/experimental/packages/sampler-composite/.eslintignore
@@ -0,0 +1 @@
+build
diff --git a/experimental/packages/sampler-composite/.eslintrc.js b/experimental/packages/sampler-composite/.eslintrc.js
new file mode 100644
index 00000000000..0fe1bbf975f
--- /dev/null
+++ b/experimental/packages/sampler-composite/.eslintrc.js
@@ -0,0 +1,8 @@
+module.exports = {
+ "env": {
+ "mocha": true,
+ "commonjs": true,
+ "shared-node-browser": true
+ },
+ ...require('../../../eslint.base.js')
+}
diff --git a/experimental/packages/sampler-composite/README.md b/experimental/packages/sampler-composite/README.md
new file mode 100644
index 00000000000..6fce8346e41
--- /dev/null
+++ b/experimental/packages/sampler-composite/README.md
@@ -0,0 +1,57 @@
+# OpenTelemetry Composite Sampling
+
+[![NPM Published Version][npm-img]][npm-url]
+[![Apache License][license-image]][license-image]
+
+**Note: This is an experimental package under active development. New releases may include breaking changes.**
+
+This package provides implementations of composite samplers that propagate sampling information across a trace.
+This implements the [experimental specification][probability-sampling].
+
+Currently `ComposableRuleBased` and `ComposableAnnotating` are not implemented.
+
+## Quick Start
+
+To get started you will need to install a compatible OpenTelemetry SDK.
+
+### Samplers
+
+This module exports samplers that follow the general behavior of the standard SDK samplers, but ensuring
+it is consistent across a trace by using the tracestate header. Notably, the tracestate can be examined
+in exported spans to reconstruct population metrics.
+
+```typescript
+import {
+ createCompositeSampler,
+ createComposableAlwaysOffSampler,
+ createComposableAlwaysOnSampler,
+ createComposableParentThresholdSampler,
+ createComposableTraceIDRatioBasedSampler,
+} from '@opentelemetry/sampler-composite';
+
+// never sample
+const sampler = createCompositeSampler(createComposableAlwaysOffSampler());
+// always sample
+const sampler = createCompositeSampler(createComposableAlwaysOnSampler());
+// follow the parent, or otherwise sample with a probability if root
+const sampler = createCompositeSampler(
+ createComposableParentThresholdSampler(createComposableTraceIDRatioBasedSampler(0.3)));
+```
+
+## Useful links
+
+- For more information on OpenTelemetry, visit:
+- For more about OpenTelemetry JavaScript:
+- For help or feedback on this project, join us in [GitHub Discussions][discussions-url]
+
+## License
+
+Apache 2.0 - See [LICENSE][license-url] for more information.
+
+[discussions-url]: https://github.com/open-telemetry/opentelemetry-js/discussions
+[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/main/LICENSE
+[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat
+[npm-url]: https://www.npmjs.com/package/@opentelemetry/sampler-composite
+[npm-img]: https://badge.fury.io/js/%40opentelemetry%sampler-composite.svg
+
+[probability-sampling]: https://opentelemetry.io/docs/specs/otel/trace/tracestate-probability-sampling/
diff --git a/experimental/packages/sampler-composite/package.json b/experimental/packages/sampler-composite/package.json
new file mode 100644
index 00000000000..fef7b992500
--- /dev/null
+++ b/experimental/packages/sampler-composite/package.json
@@ -0,0 +1,70 @@
+{
+ "name": "@opentelemetry/sampler-composite",
+ "private": false,
+ "publishConfig": {
+ "access": "public"
+ },
+ "version": "0.205.0",
+ "description": "Composite samplers for OpenTelemetry tracing",
+ "module": "build/esm/index.js",
+ "esnext": "build/esnext/index.js",
+ "types": "build/src/index.d.ts",
+ "main": "build/src/index.js",
+ "repository": "open-telemetry/opentelemetry-js",
+ "scripts": {
+ "prepublishOnly": "npm run compile",
+ "compile": "tsc --build",
+ "clean": "tsc --build --clean",
+ "test": "nyc mocha 'test/**/*.test.ts'",
+ "tdd": "npm run test -- --watch-extensions ts --watch",
+ "lint": "eslint . --ext .ts",
+ "lint:fix": "eslint . --ext .ts --fix",
+ "version": "node ../../../scripts/version-update.js",
+ "watch": "tsc --build --watch",
+ "precompile": "lerna run version --scope @opentelemetry/sampler-composite --include-dependencies",
+ "prewatch": "npm run precompile",
+ "peer-api-check": "node ../../../scripts/peer-api-check.js",
+ "align-api-deps": "node ../../../scripts/align-api-deps.js"
+ },
+ "keywords": [
+ "opentelemetry",
+ "nodejs",
+ "sampling",
+ "tracing"
+ ],
+ "author": "OpenTelemetry Authors",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.19.0 || >=20.6.0"
+ },
+ "files": [
+ "build/esm/**/*.js",
+ "build/esm/**/*.js.map",
+ "build/esm/**/*.d.ts",
+ "build/esnext/**/*.js",
+ "build/esnext/**/*.js.map",
+ "build/esnext/**/*.d.ts",
+ "build/src/**/*.js",
+ "build/src/**/*.js.map",
+ "build/src/**/*.d.ts",
+ "LICENSE",
+ "README.md"
+ ],
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.3.0"
+ },
+ "devDependencies": {
+ "@opentelemetry/api": "1.9.0",
+ "@types/mocha": "10.0.10",
+ "@types/node": "18.6.5",
+ "lerna": "6.6.2",
+ "mocha": "11.1.0",
+ "nyc": "17.1.0"
+ },
+ "dependencies": {
+ "@opentelemetry/core": "2.0.1",
+ "@opentelemetry/sdk-trace-base": "2.0.1"
+ },
+ "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/sampler-composite",
+ "sideEffects": false
+}
diff --git a/experimental/packages/sampler-composite/src/alwaysoff.ts b/experimental/packages/sampler-composite/src/alwaysoff.ts
new file mode 100644
index 00000000000..2c41635cdee
--- /dev/null
+++ b/experimental/packages/sampler-composite/src/alwaysoff.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 type { ComposableSampler, SamplingIntent } from './types';
+import { INVALID_THRESHOLD } from './util';
+
+const intent: SamplingIntent = {
+ threshold: INVALID_THRESHOLD,
+ thresholdReliable: false,
+};
+
+class ComposableAlwaysOffSampler implements ComposableSampler {
+ getSamplingIntent(): SamplingIntent {
+ return intent;
+ }
+
+ toString(): string {
+ return 'ComposableAlwaysOffSampler';
+ }
+}
+
+const _sampler = new ComposableAlwaysOffSampler();
+
+/**
+ * Returns a composable sampler that does not sample any span.
+ */
+export function createComposableAlwaysOffSampler(): ComposableSampler {
+ return _sampler;
+}
diff --git a/experimental/packages/sampler-composite/src/alwayson.ts b/experimental/packages/sampler-composite/src/alwayson.ts
new file mode 100644
index 00000000000..832d81e665b
--- /dev/null
+++ b/experimental/packages/sampler-composite/src/alwayson.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 type { ComposableSampler, SamplingIntent } from './types';
+import { MIN_THRESHOLD } from './util';
+
+const intent: SamplingIntent = {
+ threshold: MIN_THRESHOLD,
+ thresholdReliable: true,
+};
+
+class ComposableAlwaysOnSampler implements ComposableSampler {
+ getSamplingIntent(): SamplingIntent {
+ return intent;
+ }
+
+ toString(): string {
+ return 'ComposableAlwaysOnSampler';
+ }
+}
+
+const _sampler = new ComposableAlwaysOnSampler();
+
+/**
+ * Returns a composable sampler that samples all span.
+ */
+export function createComposableAlwaysOnSampler(): ComposableSampler {
+ return _sampler;
+}
diff --git a/experimental/packages/sampler-composite/src/composite.ts b/experimental/packages/sampler-composite/src/composite.ts
new file mode 100644
index 00000000000..0a8a90e0620
--- /dev/null
+++ b/experimental/packages/sampler-composite/src/composite.ts
@@ -0,0 +1,126 @@
+/*
+ * 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,
+ Attributes,
+ Link,
+ TraceState,
+ trace,
+} from '@opentelemetry/api';
+import { TraceState as CoreTraceState } from '@opentelemetry/core';
+import {
+ Sampler,
+ SamplingDecision,
+ SamplingResult,
+} from '@opentelemetry/sdk-trace-base';
+import { ComposableSampler } from './types';
+import { parseOtelTraceState, serializeTraceState } from './tracestate';
+import {
+ INVALID_THRESHOLD,
+ isValidRandomValue,
+ isValidThreshold,
+} from './util';
+
+class CompositeSampler implements Sampler {
+ constructor(private readonly delegate: ComposableSampler) {}
+
+ shouldSample(
+ context: Context,
+ traceId: string,
+ spanName: string,
+ spanKind: SpanKind,
+ attributes: Attributes,
+ links: Link[]
+ ): SamplingResult {
+ const spanContext = trace.getSpanContext(context);
+
+ const traceState = spanContext?.traceState;
+ let otTraceState = parseOtelTraceState(traceState);
+
+ const intent = this.delegate.getSamplingIntent(
+ context,
+ traceId,
+ spanName,
+ spanKind,
+ attributes,
+ links
+ );
+
+ let adjustedCountCorrect = false;
+ let sampled = false;
+ if (isValidThreshold(intent.threshold)) {
+ adjustedCountCorrect = intent.thresholdReliable;
+ let randomness: bigint;
+ if (isValidRandomValue(otTraceState.randomValue)) {
+ randomness = otTraceState.randomValue;
+ } else {
+ // Use last 56 bits of trace_id as randomness.
+ randomness = BigInt(`0x${traceId.slice(-14)}`);
+ }
+ sampled = intent.threshold <= randomness;
+ }
+
+ const decision = sampled
+ ? SamplingDecision.RECORD_AND_SAMPLED
+ : SamplingDecision.NOT_RECORD;
+ if (sampled && adjustedCountCorrect) {
+ otTraceState = {
+ ...otTraceState,
+ threshold: intent.threshold,
+ };
+ } else {
+ otTraceState = {
+ ...otTraceState,
+ threshold: INVALID_THRESHOLD,
+ };
+ }
+
+ const otts = serializeTraceState(otTraceState);
+
+ let newTraceState: TraceState | undefined;
+ if (traceState) {
+ newTraceState = traceState;
+ if (intent.updateTraceState) {
+ newTraceState = intent.updateTraceState(newTraceState);
+ }
+ }
+ if (otts) {
+ if (!newTraceState) {
+ newTraceState = new CoreTraceState();
+ }
+ newTraceState = newTraceState.set('ot', otts);
+ }
+
+ return {
+ decision,
+ attributes: intent.attributes,
+ traceState: newTraceState,
+ };
+ }
+
+ toString(): string {
+ return this.delegate.toString();
+ }
+}
+
+/**
+ * Returns a composite sampler that uses a composable sampler to make its
+ * sampling decisions while handling tracestate.
+ */
+export function createCompositeSampler(delegate: ComposableSampler): Sampler {
+ return new CompositeSampler(delegate);
+}
diff --git a/experimental/packages/sampler-composite/src/index.ts b/experimental/packages/sampler-composite/src/index.ts
new file mode 100644
index 00000000000..91e68bf22e6
--- /dev/null
+++ b/experimental/packages/sampler-composite/src/index.ts
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+export { createComposableAlwaysOffSampler } from './alwaysoff';
+export { createComposableAlwaysOnSampler } from './alwayson';
+export { createComposableTraceIDRatioBasedSampler } from './traceidratio';
+export { createComposableParentThresholdSampler } from './parentthreshold';
+export { createCompositeSampler } from './composite';
+export type { ComposableSampler, SamplingIntent } from './types';
diff --git a/experimental/packages/sampler-composite/src/parentthreshold.ts b/experimental/packages/sampler-composite/src/parentthreshold.ts
new file mode 100644
index 00000000000..a01a8d4eda1
--- /dev/null
+++ b/experimental/packages/sampler-composite/src/parentthreshold.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 {
+ Attributes,
+ Context,
+ isSpanContextValid,
+ Link,
+ SpanKind,
+ TraceFlags,
+ trace,
+} from '@opentelemetry/api';
+import { ComposableSampler, SamplingIntent } from './types';
+import { parseOtelTraceState } from './tracestate';
+import { INVALID_THRESHOLD, isValidThreshold, MIN_THRESHOLD } from './util';
+
+class ComposableParentThresholdSampler implements ComposableSampler {
+ private readonly description: string;
+
+ constructor(private readonly rootSampler: ComposableSampler) {
+ this.description = `ComposableParentThresholdSampler(rootSampler=${rootSampler})`;
+ }
+
+ getSamplingIntent(
+ context: Context,
+ traceId: string,
+ spanName: string,
+ spanKind: SpanKind,
+ attributes: Attributes,
+ links: Link[]
+ ): SamplingIntent {
+ const parentSpanContext = trace.getSpanContext(context);
+ if (!parentSpanContext || !isSpanContextValid(parentSpanContext)) {
+ return this.rootSampler.getSamplingIntent(
+ context,
+ traceId,
+ spanName,
+ spanKind,
+ attributes,
+ links
+ );
+ }
+
+ const otTraceState = parseOtelTraceState(parentSpanContext.traceState);
+
+ if (isValidThreshold(otTraceState.threshold)) {
+ return {
+ threshold: otTraceState.threshold,
+ thresholdReliable: true,
+ };
+ }
+
+ const threshold =
+ parentSpanContext.traceFlags & TraceFlags.SAMPLED
+ ? MIN_THRESHOLD
+ : INVALID_THRESHOLD;
+ return {
+ threshold,
+ thresholdReliable: false,
+ };
+ }
+
+ toString(): string {
+ return this.description;
+ }
+}
+
+/**
+ * Returns a composable sampler that respects the sampling decision of the
+ * parent span or falls back to the given sampler if it is a root span.
+ */
+export function createComposableParentThresholdSampler(
+ rootSampler: ComposableSampler
+): ComposableSampler {
+ return new ComposableParentThresholdSampler(rootSampler);
+}
diff --git a/experimental/packages/sampler-composite/src/traceidratio.ts b/experimental/packages/sampler-composite/src/traceidratio.ts
new file mode 100644
index 00000000000..85f342c8c14
--- /dev/null
+++ b/experimental/packages/sampler-composite/src/traceidratio.ts
@@ -0,0 +1,78 @@
+/*
+ * 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 { ComposableSampler, SamplingIntent } from './types';
+import { INVALID_THRESHOLD, MAX_THRESHOLD } from './util';
+import { serializeTh } from './tracestate';
+
+class ComposableTraceIDRatioBasedSampler implements ComposableSampler {
+ private readonly intent: SamplingIntent;
+ private readonly description: string;
+
+ constructor(ratio: number) {
+ if (ratio < 0 || ratio > 1) {
+ throw new Error(
+ `Invalid sampling probability: ${ratio}. Must be between 0 and 1.`
+ );
+ }
+ const threshold = calculateThreshold(ratio);
+ const thresholdStr =
+ threshold === MAX_THRESHOLD ? 'max' : serializeTh(threshold);
+ if (threshold !== MAX_THRESHOLD) {
+ this.intent = {
+ threshold: threshold,
+ thresholdReliable: true,
+ };
+ } else {
+ // Same as AlwaysOff, notably the threshold is not considered reliable. The spec mentions
+ // returning an instance of ComposableAlwaysOffSampler in this case but it seems clearer
+ // if the description of the sampler matches the user's request.
+ this.intent = {
+ threshold: INVALID_THRESHOLD,
+ thresholdReliable: false,
+ };
+ }
+ this.description = `ComposableTraceIDRatioBasedSampler(threshold=${thresholdStr}, ratio=${ratio})`;
+ }
+
+ getSamplingIntent(): SamplingIntent {
+ return this.intent;
+ }
+
+ toString(): string {
+ return this.description;
+ }
+}
+
+/**
+ * Returns a composable sampler that samples each span with a fixed ratio.
+ */
+export function createComposableTraceIDRatioBasedSampler(
+ ratio: number
+): ComposableSampler {
+ return new ComposableTraceIDRatioBasedSampler(ratio);
+}
+
+const probabilityThresholdScale = Math.pow(2, 56);
+
+// TODO: Reduce threshold precision following spec recommendation of 4
+// to reduce size of serialized tracestate.
+function calculateThreshold(samplingProbability: number): bigint {
+ return (
+ MAX_THRESHOLD -
+ BigInt(Math.round(samplingProbability * probabilityThresholdScale))
+ );
+}
diff --git a/experimental/packages/sampler-composite/src/tracestate.ts b/experimental/packages/sampler-composite/src/tracestate.ts
new file mode 100644
index 00000000000..8d12d218a65
--- /dev/null
+++ b/experimental/packages/sampler-composite/src/tracestate.ts
@@ -0,0 +1,156 @@
+/*
+ * 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 { TraceState } from '@opentelemetry/api';
+import {
+ INVALID_RANDOM_VALUE,
+ INVALID_THRESHOLD,
+ isValidRandomValue,
+ isValidThreshold,
+ MAX_THRESHOLD,
+} from './util';
+
+export type OtelTraceState = {
+ /** The random value for sampling decisions in the trace. */
+ randomValue: bigint;
+ /** The upstream threshold for sampling decisions. */
+ threshold: bigint;
+ /** The rest of the "ot" tracestate value. */
+ rest?: string[];
+};
+
+export const INVALID_TRACE_STATE: OtelTraceState = Object.freeze({
+ randomValue: INVALID_RANDOM_VALUE,
+ threshold: INVALID_THRESHOLD,
+});
+
+const TRACE_STATE_SIZE_LIMIT = 256;
+const MAX_VALUE_LENGTH = 14; // 56 bits, 4 bits per hex digit
+
+export function parseOtelTraceState(
+ traceState: TraceState | undefined
+): OtelTraceState {
+ const ot = traceState?.get('ot');
+ if (!ot || ot.length > TRACE_STATE_SIZE_LIMIT) {
+ return INVALID_TRACE_STATE;
+ }
+
+ let threshold = INVALID_THRESHOLD;
+ let randomValue = INVALID_RANDOM_VALUE;
+
+ // Parse based on https://opentelemetry.io/docs/specs/otel/trace/tracestate-handling/
+ const members = ot.split(';');
+ let rest: string[] | undefined;
+ for (const member of members) {
+ if (member.startsWith('th:')) {
+ threshold = parseTh(member.slice('th:'.length), INVALID_THRESHOLD);
+ continue;
+ }
+ if (member.startsWith('rv:')) {
+ randomValue = parseRv(member.slice('rv:'.length), INVALID_RANDOM_VALUE);
+ continue;
+ }
+ if (!rest) {
+ rest = [];
+ }
+ rest.push(member);
+ }
+
+ return {
+ randomValue,
+ threshold,
+ rest,
+ };
+}
+
+export function serializeTraceState(otTraceState: OtelTraceState): string {
+ if (
+ !isValidThreshold(otTraceState.threshold) &&
+ !isValidRandomValue(otTraceState.randomValue) &&
+ !otTraceState.rest
+ ) {
+ return '';
+ }
+
+ const parts: string[] = [];
+ if (
+ isValidThreshold(otTraceState.threshold) &&
+ otTraceState.threshold !== MAX_THRESHOLD
+ ) {
+ parts.push(`th:${serializeTh(otTraceState.threshold)}`);
+ }
+ if (isValidRandomValue(otTraceState.randomValue)) {
+ parts.push(`rv:${serializeRv(otTraceState.randomValue)}`);
+ }
+ if (otTraceState.rest) {
+ parts.push(...otTraceState.rest);
+ }
+ let res = parts.join(';');
+ while (res.length > TRACE_STATE_SIZE_LIMIT) {
+ const lastSemicolon = res.lastIndexOf(';');
+ if (lastSemicolon === -1) {
+ break;
+ }
+ res = res.slice(0, lastSemicolon);
+ }
+ return res;
+}
+
+function parseTh(value: string, defaultValue: bigint): bigint {
+ if (!value || value.length > MAX_VALUE_LENGTH) {
+ return defaultValue;
+ }
+
+ try {
+ return BigInt('0x' + value.padEnd(MAX_VALUE_LENGTH, '0'));
+ } catch {
+ return defaultValue;
+ }
+}
+
+function parseRv(value: string, defaultValue: bigint): bigint {
+ if (!value || value.length !== MAX_VALUE_LENGTH) {
+ return defaultValue;
+ }
+
+ try {
+ return BigInt(`0x${value}`);
+ } catch {
+ return defaultValue;
+ }
+}
+
+// hex value without trailing zeros
+export function serializeTh(threshold: bigint): string {
+ if (threshold === 0n) {
+ return '0';
+ }
+
+ const value = threshold.toString(16).padStart(MAX_VALUE_LENGTH, '0');
+ let idxAfterNonZero = value.length;
+ for (let i = value.length - 1; i >= 0; i--) {
+ if (value[i] !== '0') {
+ idxAfterNonZero = i + 1;
+ break;
+ }
+ }
+ // Checked at beginning so there is definitely a nonzero.
+ return value.slice(0, idxAfterNonZero);
+}
+
+function serializeRv(randomValue: bigint): string {
+ return randomValue.toString(16).padStart(MAX_VALUE_LENGTH, '0');
+}
diff --git a/experimental/packages/sampler-composite/src/types.ts b/experimental/packages/sampler-composite/src/types.ts
new file mode 100644
index 00000000000..3743a38c071
--- /dev/null
+++ b/experimental/packages/sampler-composite/src/types.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { Attributes, TraceState } from '@opentelemetry/api';
+import { type Sampler } from '@opentelemetry/sdk-trace-base';
+
+/** Information to make a sampling decision. */
+export type SamplingIntent = {
+ /** The sampling threshold value. A lower threshold increases the likelihood of sampling. */
+ threshold: bigint;
+
+ /** Whether the threshold can be reliably used for Span-to-Metrics estimation. */
+ thresholdReliable: boolean;
+
+ /** Any attributes to add to the span for the sampling result. */
+ attributes?: Attributes;
+
+ /** How to update the TraceState for the span. */
+ updateTraceState?: (ts: TraceState | undefined) => TraceState | undefined;
+};
+
+/** A sampler that can be composed to make a final sampling decision. */
+export interface ComposableSampler {
+ /** Returns the information to make a sampling decision. */
+ getSamplingIntent(
+ ...args: Parameters
+ ): SamplingIntent;
+
+ /** Returns the sampler name or short description with the configuration. */
+ toString(): string;
+}
diff --git a/experimental/packages/sampler-composite/src/util.ts b/experimental/packages/sampler-composite/src/util.ts
new file mode 100644
index 00000000000..5ba900c427e
--- /dev/null
+++ b/experimental/packages/sampler-composite/src/util.ts
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+export const INVALID_THRESHOLD = -1n;
+export const INVALID_RANDOM_VALUE = -1n;
+
+const RANDOM_VALUE_BITS = 56n;
+export const MAX_THRESHOLD = 1n << RANDOM_VALUE_BITS; // 0% sampling
+export const MIN_THRESHOLD = 0n; // 100% sampling
+const MAX_RANDOM_VALUE = MAX_THRESHOLD - 1n;
+
+export function isValidThreshold(threshold: bigint): boolean {
+ return threshold >= MIN_THRESHOLD && threshold <= MAX_THRESHOLD;
+}
+
+export function isValidRandomValue(randomValue: bigint): boolean {
+ return randomValue >= 0n && randomValue <= MAX_RANDOM_VALUE;
+}
diff --git a/experimental/packages/sampler-composite/test/alwaysoff.test.ts b/experimental/packages/sampler-composite/test/alwaysoff.test.ts
new file mode 100644
index 00000000000..e10c9c28274
--- /dev/null
+++ b/experimental/packages/sampler-composite/test/alwaysoff.test.ts
@@ -0,0 +1,72 @@
+/*
+ * 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 { context, SpanKind } from '@opentelemetry/api';
+import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
+
+import {
+ createCompositeSampler,
+ createComposableAlwaysOffSampler,
+} from '../src';
+import { traceIdGenerator } from './util';
+
+describe('ComposableAlwaysOffSampler', () => {
+ const composableSampler = createComposableAlwaysOffSampler();
+
+ it('should have a description', () => {
+ assert.strictEqual(
+ composableSampler.toString(),
+ 'ComposableAlwaysOffSampler'
+ );
+ });
+
+ it('should have a constant threshold', () => {
+ assert.strictEqual(
+ composableSampler.getSamplingIntent(
+ context.active(),
+ 'unused',
+ 'span',
+ SpanKind.SERVER,
+ {},
+ []
+ ).threshold,
+ -1n
+ );
+ });
+
+ it('should never sample', () => {
+ const sampler = createCompositeSampler(composableSampler);
+ const generator = traceIdGenerator();
+ let numSampled = 0;
+ for (let i = 0; i < 10000; i++) {
+ const result = sampler.shouldSample(
+ context.active(),
+ generator(),
+ 'span',
+ SpanKind.SERVER,
+ {},
+ []
+ );
+ if (result.decision === SamplingDecision.RECORD_AND_SAMPLED) {
+ numSampled++;
+ }
+ assert.strictEqual(result.traceState, undefined);
+ }
+ assert.strictEqual(numSampled, 0);
+ });
+});
diff --git a/experimental/packages/sampler-composite/test/alwayson.test.ts b/experimental/packages/sampler-composite/test/alwayson.test.ts
new file mode 100644
index 00000000000..7a29a1fa8c3
--- /dev/null
+++ b/experimental/packages/sampler-composite/test/alwayson.test.ts
@@ -0,0 +1,72 @@
+/*
+ * 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 { context, SpanKind } from '@opentelemetry/api';
+import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
+
+import {
+ createCompositeSampler,
+ createComposableAlwaysOnSampler,
+} from '../src';
+import { traceIdGenerator } from './util';
+
+describe('ComposableAlwaysOnSampler', () => {
+ const composableSampler = createComposableAlwaysOnSampler();
+
+ it('should have a description', () => {
+ assert.strictEqual(
+ composableSampler.toString(),
+ 'ComposableAlwaysOnSampler'
+ );
+ });
+
+ it('should have a constant threshold', () => {
+ assert.strictEqual(
+ composableSampler.getSamplingIntent(
+ context.active(),
+ 'unused',
+ 'span',
+ SpanKind.SERVER,
+ {},
+ []
+ ).threshold,
+ 0n
+ );
+ });
+
+ it('should always sample', () => {
+ const sampler = createCompositeSampler(composableSampler);
+ const generator = traceIdGenerator();
+ let numSampled = 0;
+ for (let i = 0; i < 10000; i++) {
+ const result = sampler.shouldSample(
+ context.active(),
+ generator(),
+ 'span',
+ SpanKind.SERVER,
+ {},
+ []
+ );
+ if (result.decision === SamplingDecision.RECORD_AND_SAMPLED) {
+ numSampled++;
+ }
+ assert.strictEqual(result.traceState?.get('ot'), 'th:0');
+ }
+ assert.strictEqual(numSampled, 10000);
+ });
+});
diff --git a/experimental/packages/sampler-composite/test/sampler.test.ts b/experimental/packages/sampler-composite/test/sampler.test.ts
new file mode 100644
index 00000000000..86383d93ec7
--- /dev/null
+++ b/experimental/packages/sampler-composite/test/sampler.test.ts
@@ -0,0 +1,192 @@
+/*
+ * 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 {
+ context,
+ SpanContext,
+ SpanKind,
+ TraceFlags,
+ trace,
+} from '@opentelemetry/api';
+import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
+
+import {
+ createCompositeSampler,
+ createComposableAlwaysOffSampler,
+ createComposableAlwaysOnSampler,
+ createComposableParentThresholdSampler,
+ createComposableTraceIDRatioBasedSampler,
+} from '../src';
+import { INVALID_RANDOM_VALUE, INVALID_THRESHOLD } from '../src/util';
+import {
+ INVALID_TRACE_STATE,
+ parseOtelTraceState,
+ serializeTraceState,
+} from '../src/tracestate';
+import { TraceState } from '@opentelemetry/core';
+
+describe('ConsistentSampler', () => {
+ const traceId = '00112233445566778800000000000000';
+ const spanId = '0123456789abcdef';
+
+ [
+ {
+ sampler: createComposableAlwaysOnSampler(),
+ parentSampled: true,
+ parentThreshold: undefined,
+ parentRandomValue: undefined,
+ sampled: true,
+ threshold: 0n,
+ randomValue: INVALID_RANDOM_VALUE,
+ testId: 'min threshold no parent random value',
+ },
+ {
+ sampler: createComposableAlwaysOnSampler(),
+ parentSampled: true,
+ parentThreshold: undefined,
+ parentRandomValue: 0x7f99aa40c02744n,
+ sampled: true,
+ threshold: 0n,
+ randomValue: 0x7f99aa40c02744n,
+ testId: 'min threshold with parent random value',
+ },
+ {
+ sampler: createComposableAlwaysOffSampler(),
+ parentSampled: true,
+ parentThreshold: undefined,
+ parentRandomValue: undefined,
+ sampled: false,
+ threshold: INVALID_THRESHOLD,
+ randomValue: INVALID_RANDOM_VALUE,
+ testId: 'max threshold',
+ },
+ {
+ sampler: createComposableParentThresholdSampler(
+ createComposableAlwaysOnSampler()
+ ),
+ parentSampled: false,
+ parentThreshold: 0x7f99aa40c02744n,
+ parentRandomValue: 0x7f99aa40c02744n,
+ sampled: true,
+ threshold: 0x7f99aa40c02744n,
+ randomValue: 0x7f99aa40c02744n,
+ testId: 'parent based in consistent mode',
+ },
+ {
+ sampler: createComposableParentThresholdSampler(
+ createComposableAlwaysOnSampler()
+ ),
+ parentSampled: true,
+ parentThreshold: undefined,
+ parentRandomValue: undefined,
+ sampled: true,
+ threshold: INVALID_THRESHOLD,
+ randomValue: INVALID_RANDOM_VALUE,
+ testId: 'parent based in legacy mode',
+ },
+ {
+ sampler: createComposableTraceIDRatioBasedSampler(0.5),
+ parentSampled: true,
+ parentThreshold: undefined,
+ parentRandomValue: 0x7fffffffffffffn,
+ sampled: false,
+ threshold: INVALID_THRESHOLD,
+ randomValue: 0x7fffffffffffffn,
+ testId: 'half threshold not sampled',
+ },
+ {
+ sampler: createComposableTraceIDRatioBasedSampler(0.5),
+ parentSampled: false,
+ parentThreshold: undefined,
+ parentRandomValue: 0x80000000000000n,
+ sampled: true,
+ threshold: 0x80000000000000n,
+ randomValue: 0x80000000000000n,
+ testId: 'half threshold sampled',
+ },
+ {
+ sampler: createComposableTraceIDRatioBasedSampler(1.0),
+ parentSampled: false,
+ parentThreshold: 0x80000000000000n,
+ parentRandomValue: 0x80000000000000n,
+ sampled: true,
+ threshold: 0n,
+ randomValue: 0x80000000000000n,
+ testId: 'parent inviolating invariant',
+ },
+ ].forEach(
+ ({
+ sampler,
+ parentSampled,
+ parentThreshold,
+ parentRandomValue,
+ sampled,
+ threshold,
+ randomValue,
+ testId,
+ }) => {
+ it(`should sample with ${testId}`, () => {
+ let parentOtTraceState = INVALID_TRACE_STATE;
+ if (parentThreshold !== undefined) {
+ parentOtTraceState = {
+ ...parentOtTraceState,
+ threshold: parentThreshold,
+ };
+ }
+ if (parentRandomValue !== undefined) {
+ parentOtTraceState = {
+ ...parentOtTraceState,
+ randomValue: parentRandomValue,
+ };
+ }
+ const parentOt = serializeTraceState(parentOtTraceState);
+ const parentTraceState = parentOt
+ ? new TraceState().set('ot', parentOt)
+ : undefined;
+ const traceFlags = parentSampled ? TraceFlags.SAMPLED : TraceFlags.NONE;
+ const parentSpanContext: SpanContext = {
+ traceId,
+ spanId,
+ traceFlags,
+ traceState: parentTraceState,
+ };
+ const parentContext = trace.setSpanContext(
+ context.active(),
+ parentSpanContext
+ );
+
+ const result = createCompositeSampler(sampler).shouldSample(
+ parentContext,
+ traceId,
+ 'name',
+ SpanKind.INTERNAL,
+ {},
+ []
+ );
+ const expectedDecision = sampled
+ ? SamplingDecision.RECORD_AND_SAMPLED
+ : SamplingDecision.NOT_RECORD;
+ const state = parseOtelTraceState(result.traceState);
+
+ assert.strictEqual(result.decision, expectedDecision);
+ assert.strictEqual(state.threshold, threshold);
+ assert.strictEqual(state.randomValue, randomValue);
+ });
+ }
+ );
+});
diff --git a/experimental/packages/sampler-composite/test/traceidratio.test.ts b/experimental/packages/sampler-composite/test/traceidratio.test.ts
new file mode 100644
index 00000000000..fc9499aa088
--- /dev/null
+++ b/experimental/packages/sampler-composite/test/traceidratio.test.ts
@@ -0,0 +1,87 @@
+/*
+ * 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 { context, SpanKind } from '@opentelemetry/api';
+import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
+
+import {
+ createCompositeSampler,
+ createComposableTraceIDRatioBasedSampler,
+} from '../src';
+import { traceIdGenerator } from './util';
+import { parseOtelTraceState } from '../src/tracestate';
+import { INVALID_RANDOM_VALUE } from '../src/util';
+
+describe('ComposableTraceIDRatioBasedSampler', () => {
+ [
+ { ratio: 1.0, thresholdStr: '0' },
+ { ratio: 0.5, thresholdStr: '8' },
+ { ratio: 0.25, thresholdStr: 'c' },
+ { ratio: 1e-300, thresholdStr: 'max' },
+ { ratio: 0, thresholdStr: 'max' },
+ ].forEach(({ ratio, thresholdStr }) => {
+ it(`should have a description for ratio ${ratio}`, () => {
+ const sampler = createComposableTraceIDRatioBasedSampler(ratio);
+ assert.strictEqual(
+ sampler.toString(),
+ `ComposableTraceIDRatioBasedSampler(threshold=${thresholdStr}, ratio=${ratio})`
+ );
+ });
+ });
+
+ [
+ { ratio: 1.0, threshold: 0n },
+ { ratio: 0.5, threshold: 36028797018963968n },
+ { ratio: 0.25, threshold: 54043195528445952n },
+ { ratio: 0.125, threshold: 63050394783186944n },
+ { ratio: 0.0, threshold: 72057594037927936n },
+ { ratio: 0.45, threshold: 39631676720860364n },
+ { ratio: 0.2, threshold: 57646075230342348n },
+ { ratio: 0.13, threshold: 62690106812997304n },
+ { ratio: 0.05, threshold: 68454714336031539n },
+ ].forEach(({ ratio, threshold }) => {
+ it(`should sample spans with ratio ${ratio}`, () => {
+ const sampler = createCompositeSampler(
+ createComposableTraceIDRatioBasedSampler(ratio)
+ );
+
+ const generator = traceIdGenerator();
+ let numSampled = 0;
+ for (let i = 0; i < 10000; i++) {
+ const result = sampler.shouldSample(
+ context.active(),
+ generator(),
+ 'span',
+ SpanKind.SERVER,
+ {},
+ []
+ );
+ if (result.decision === SamplingDecision.RECORD_AND_SAMPLED) {
+ numSampled++;
+ const otTraceState = parseOtelTraceState(result.traceState);
+ assert.strictEqual(otTraceState?.threshold, threshold);
+ assert.strictEqual(otTraceState?.randomValue, INVALID_RANDOM_VALUE);
+ }
+ }
+ const expectedNumSampled = 10000 * ratio;
+ assert.ok(
+ Math.abs(numSampled - expectedNumSampled) < 50,
+ `expected ${expectedNumSampled}, have ${numSampled}`
+ );
+ });
+ });
+});
diff --git a/experimental/packages/sampler-composite/test/tracestate.test.ts b/experimental/packages/sampler-composite/test/tracestate.test.ts
new file mode 100644
index 00000000000..3e439029533
--- /dev/null
+++ b/experimental/packages/sampler-composite/test/tracestate.test.ts
@@ -0,0 +1,70 @@
+/*
+ * 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 { serializeTraceState, parseOtelTraceState } from '../src/tracestate';
+import { TraceState } from '@opentelemetry/core';
+
+describe('OtelTraceState', () => {
+ [
+ { input: 'a', output: 'a' },
+ { input: '#', output: '#' },
+ { input: 'rv:1234567890abcd', output: 'rv:1234567890abcd' },
+ { input: 'rv:01020304050607', output: 'rv:01020304050607' },
+ { input: 'rv:1234567890abcde', output: '' },
+ { input: 'th:1234567890abcd', output: 'th:1234567890abcd' },
+ { input: 'th:1234567890abcd', output: 'th:1234567890abcd' },
+ { input: 'th:10000000000000', output: 'th:1' },
+ { input: 'th:1234500000000', output: 'th:12345' },
+ { input: 'th:0', output: 'th:0' },
+ { input: 'th:100000000000000', output: '' },
+ { input: 'th:1234567890abcde', output: '' },
+ {
+ input: `a:${''.padEnd(214, 'X')};rv:1234567890abcd;th:1234567890abcd;x:3`,
+ output: `th:1234567890abcd;rv:1234567890abcd;a:${''.padEnd(214, 'X')};x:3`,
+ testId: 'long',
+ },
+ { input: 'th:x', output: '' },
+ { input: 'th:100000000000000', output: '' },
+ { input: 'th:10000000000000', output: 'th:1' },
+ { input: 'th:1000000000000', output: 'th:1' },
+ { input: 'th:100000000000', output: 'th:1' },
+ { input: 'th:10000000000', output: 'th:1' },
+ { input: 'th:1000000000', output: 'th:1' },
+ { input: 'th:100000000', output: 'th:1' },
+ { input: 'th:10000000', output: 'th:1' },
+ { input: 'th:1000000', output: 'th:1' },
+ { input: 'th:100000', output: 'th:1' },
+ { input: 'th:10000', output: 'th:1' },
+ { input: 'th:1000', output: 'th:1' },
+ { input: 'th:100', output: 'th:1' },
+ { input: 'th:10', output: 'th:1' },
+ { input: 'th:1', output: 'th:1' },
+ { input: 'th:10000000000001', output: 'th:10000000000001' },
+ { input: 'th:10000000000010', output: 'th:1000000000001' },
+ { input: 'rv:x', output: '' },
+ { input: 'rv:100000000000000', output: '' },
+ { input: 'rv:10000000000000', output: 'rv:10000000000000' },
+ { input: 'rv:1000000000000', output: '' },
+ ].forEach(({ input, output, testId }) => {
+ it(`should round trip ${testId || `from ${input} to ${output}`}`, () => {
+ const result = serializeTraceState(
+ parseOtelTraceState(new TraceState().set('ot', input))
+ );
+ assert.strictEqual(result, output);
+ });
+ });
+});
diff --git a/experimental/packages/sampler-composite/test/util.ts b/experimental/packages/sampler-composite/test/util.ts
new file mode 100644
index 00000000000..a4b393aa5d4
--- /dev/null
+++ b/experimental/packages/sampler-composite/test/util.ts
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+// Use a fixed seed simple but reasonable random number generator for consistent tests.
+// Unlike many languages, there isn't a way to set the seed of the built-in random.
+
+function splitmix32(a: number) {
+ return function () {
+ a |= 0;
+ a = (a + 0x9e3779b9) | 0;
+ let t = a ^ (a >>> 16);
+ t = Math.imul(t, 0x21f0aaad);
+ t = t ^ (t >>> 15);
+ t = Math.imul(t, 0x735a2d97);
+ return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296;
+ };
+}
+
+export function traceIdGenerator(): () => string {
+ const seed = 0xdeadbeef;
+ const random = splitmix32(seed);
+ // Pre-mix the state.
+ for (let i = 0; i < 15; i++) {
+ random();
+ }
+ return () => {
+ const parts: string[] = [];
+ // 32-bit randoms, concatenate 4 of them
+ for (let i = 0; i < 4; i++) {
+ const val = Math.round(random() * 0xffffffff);
+ parts.push(val.toString(16).padStart(8, '0'));
+ }
+ return parts.join('');
+ };
+}
diff --git a/experimental/packages/sampler-composite/tsconfig.esm.json b/experimental/packages/sampler-composite/tsconfig.esm.json
new file mode 100644
index 00000000000..5fe96d554ba
--- /dev/null
+++ b/experimental/packages/sampler-composite/tsconfig.esm.json
@@ -0,0 +1,23 @@
+{
+ "extends": "../../../tsconfig.base.esm.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "outDir": "build/esm",
+ "rootDir": "src",
+ "tsBuildInfoFile": "build/esm/tsconfig.esm.tsbuildinfo"
+ },
+ "include": [
+ "src/**/*.ts"
+ ],
+ "references": [
+ {
+ "path": "../../../api"
+ },
+ {
+ "path": "../../../packages/opentelemetry-core"
+ },
+ {
+ "path": "../../../packages/opentelemetry-sdk-trace-base"
+ }
+ ]
+}
diff --git a/experimental/packages/sampler-composite/tsconfig.esnext.json b/experimental/packages/sampler-composite/tsconfig.esnext.json
new file mode 100644
index 00000000000..17ed0461704
--- /dev/null
+++ b/experimental/packages/sampler-composite/tsconfig.esnext.json
@@ -0,0 +1,23 @@
+{
+ "extends": "../../../tsconfig.base.esnext.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "outDir": "build/esnext",
+ "rootDir": "src",
+ "tsBuildInfoFile": "build/esnext/tsconfig.esnext.tsbuildinfo"
+ },
+ "include": [
+ "src/**/*.ts"
+ ],
+ "references": [
+ {
+ "path": "../../../api"
+ },
+ {
+ "path": "../../../packages/opentelemetry-core"
+ },
+ {
+ "path": "../../../packages/opentelemetry-sdk-trace-base"
+ }
+ ]
+}
diff --git a/experimental/packages/sampler-composite/tsconfig.json b/experimental/packages/sampler-composite/tsconfig.json
new file mode 100644
index 00000000000..eb6f0a3a273
--- /dev/null
+++ b/experimental/packages/sampler-composite/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "outDir": "build",
+ "rootDir": "."
+ },
+ "files": [],
+ "include": [
+ "src/**/*.ts",
+ "test/**/*.ts"
+ ],
+ "references": [
+ {
+ "path": "../../../api"
+ },
+ {
+ "path": "../../../packages/opentelemetry-core"
+ },
+ {
+ "path": "../../../packages/opentelemetry-sdk-trace-base"
+ }
+ ]
+}
diff --git a/package-lock.json b/package-lock.json
index 91defe033e4..f768afef19e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1448,6 +1448,257 @@
"@opentelemetry/api": "^1.3.0"
}
},
+ "experimental/packages/sampler-composite": {
+ "name": "@opentelemetry/sampler-composite",
+ "version": "0.205.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@opentelemetry/core": "2.0.1",
+ "@opentelemetry/sdk-trace-base": "2.0.1"
+ },
+ "devDependencies": {
+ "@opentelemetry/api": "1.9.0",
+ "@types/mocha": "10.0.10",
+ "@types/node": "18.6.5",
+ "lerna": "6.6.2",
+ "mocha": "11.1.0",
+ "nyc": "17.1.0"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.6.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.3.0"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/@opentelemetry/core": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz",
+ "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@opentelemetry/semantic-conventions": "^1.29.0"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.6.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": ">=1.0.0 <1.10.0"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/@opentelemetry/resources": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz",
+ "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@opentelemetry/core": "2.0.1",
+ "@opentelemetry/semantic-conventions": "^1.29.0"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.6.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": ">=1.3.0 <1.10.0"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/@opentelemetry/sdk-trace-base": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz",
+ "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@opentelemetry/core": "2.0.1",
+ "@opentelemetry/resources": "2.0.1",
+ "@opentelemetry/semantic-conventions": "^1.29.0"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.6.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": ">=1.3.0 <1.10.0"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/@types/node": {
+ "version": "18.6.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz",
+ "integrity": "sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "experimental/packages/sampler-composite/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "experimental/packages/sampler-composite/node_modules/diff": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+ "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "experimental/packages/sampler-composite/node_modules/mocha": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz",
+ "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-colors": "^4.1.3",
+ "browser-stdout": "^1.3.1",
+ "chokidar": "^3.5.3",
+ "debug": "^4.3.5",
+ "diff": "^5.2.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-up": "^5.0.0",
+ "glob": "^10.4.5",
+ "he": "^1.2.0",
+ "js-yaml": "^4.1.0",
+ "log-symbols": "^4.1.0",
+ "minimatch": "^5.1.6",
+ "ms": "^2.1.3",
+ "serialize-javascript": "^6.0.2",
+ "strip-json-comments": "^3.1.1",
+ "supports-color": "^8.1.1",
+ "workerpool": "^6.5.1",
+ "yargs": "^17.7.2",
+ "yargs-parser": "^21.1.1",
+ "yargs-unparser": "^2.0.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/mocha/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "experimental/packages/sampler-composite/node_modules/workerpool": {
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
+ "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "experimental/packages/sampler-composite/node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"experimental/packages/sampler-jaeger-remote": {
"name": "@opentelemetry/sampler-jaeger-remote",
"version": "0.205.0",
@@ -5962,6 +6213,10 @@
"resolved": "packages/opentelemetry-resources",
"link": true
},
+ "node_modules/@opentelemetry/sampler-composite": {
+ "resolved": "experimental/packages/sampler-composite",
+ "link": true
+ },
"node_modules/@opentelemetry/sampler-jaeger-remote": {
"resolved": "experimental/packages/sampler-jaeger-remote",
"link": true
@@ -31040,6 +31295,175 @@
}
}
},
+ "@opentelemetry/sampler-composite": {
+ "version": "file:experimental/packages/sampler-composite",
+ "requires": {
+ "@opentelemetry/api": "1.9.0",
+ "@opentelemetry/core": "2.0.1",
+ "@opentelemetry/sdk-trace-base": "2.0.1",
+ "@types/mocha": "10.0.10",
+ "@types/node": "18.6.5",
+ "lerna": "6.6.2",
+ "mocha": "11.1.0",
+ "nyc": "17.1.0"
+ },
+ "dependencies": {
+ "@opentelemetry/core": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz",
+ "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==",
+ "requires": {
+ "@opentelemetry/semantic-conventions": "^1.29.0"
+ }
+ },
+ "@opentelemetry/resources": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz",
+ "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==",
+ "requires": {
+ "@opentelemetry/core": "2.0.1",
+ "@opentelemetry/semantic-conventions": "^1.29.0"
+ }
+ },
+ "@opentelemetry/sdk-trace-base": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz",
+ "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==",
+ "requires": {
+ "@opentelemetry/core": "2.0.1",
+ "@opentelemetry/resources": "2.0.1",
+ "@opentelemetry/semantic-conventions": "^1.29.0"
+ }
+ },
+ "@types/node": {
+ "version": "18.6.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz",
+ "integrity": "sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==",
+ "dev": true
+ },
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "diff": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+ "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+ "dev": true
+ },
+ "glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "dev": true,
+ "requires": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ }
+ },
+ "jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "requires": {
+ "@isaacs/cliui": "^8.0.2",
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true
+ },
+ "mocha": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz",
+ "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "^4.1.3",
+ "browser-stdout": "^1.3.1",
+ "chokidar": "^3.5.3",
+ "debug": "^4.3.5",
+ "diff": "^5.2.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-up": "^5.0.0",
+ "glob": "^10.4.5",
+ "he": "^1.2.0",
+ "js-yaml": "^4.1.0",
+ "log-symbols": "^4.1.0",
+ "minimatch": "^5.1.6",
+ "ms": "^2.1.3",
+ "serialize-javascript": "^6.0.2",
+ "strip-json-comments": "^3.1.1",
+ "supports-color": "^8.1.1",
+ "workerpool": "^6.5.1",
+ "yargs": "^17.7.2",
+ "yargs-parser": "^21.1.1",
+ "yargs-unparser": "^2.0.0"
+ },
+ "dependencies": {
+ "minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ }
+ }
+ },
+ "path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "workerpool": {
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
+ "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
+ "dev": true
+ },
+ "yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true
+ }
+ }
+ },
"@opentelemetry/sampler-jaeger-remote": {
"version": "file:experimental/packages/sampler-jaeger-remote",
"requires": {
diff --git a/tsconfig.esm.json b/tsconfig.esm.json
index 2bb983f4064..711743009ba 100644
--- a/tsconfig.esm.json
+++ b/tsconfig.esm.json
@@ -44,6 +44,9 @@
{
"path": "experimental/packages/otlp-transformer/tsconfig.esm.json"
},
+ {
+ "path": "experimental/packages/sampler-composite/tsconfig.esm.json"
+ },
{
"path": "experimental/packages/sdk-logs/tsconfig.esm.json"
},
diff --git a/tsconfig.esnext.json b/tsconfig.esnext.json
index ac540a88b2d..cfc96b3bda9 100644
--- a/tsconfig.esnext.json
+++ b/tsconfig.esnext.json
@@ -44,6 +44,9 @@
{
"path": "experimental/packages/otlp-transformer/tsconfig.esnext.json"
},
+ {
+ "path": "experimental/packages/sampler-composite/tsconfig.esnext.json"
+ },
{
"path": "experimental/packages/sdk-logs/tsconfig.esnext.json"
},
diff --git a/tsconfig.json b/tsconfig.json
index ee5c10de7b4..58f4ff7e340 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -26,6 +26,7 @@
"experimental/packages/otlp-exporter-base",
"experimental/packages/otlp-grpc-exporter-base",
"experimental/packages/otlp-transformer",
+ "experimental/packages/sampler-composite",
"experimental/packages/sampler-jaeger-remote",
"experimental/packages/sdk-logs",
"experimental/packages/shim-opencensus",
@@ -126,6 +127,9 @@
{
"path": "experimental/packages/otlp-transformer"
},
+ {
+ "path": "experimental/packages/sampler-composite"
+ },
{
"path": "experimental/packages/sampler-jaeger-remote"
},