Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement parent based sampler #1577

Merged
merged 11 commits into from
Oct 13, 2020
33 changes: 29 additions & 4 deletions packages/opentelemetry-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,41 @@ const tracerProvider = new NodeTracerProvider({
});
```

#### ParentOrElse
#### ParentBasedSampler

A composite sampler that either respects the parent span's sampling decision or delegates to `delegateSampler` for root spans.
* 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/node");
const { ParentOrElseSampler, AlwaysOffSampler } = require("@opentelemetry/core");
const { ParentBasedSampler, AlwaysOffSampler, TraceIdRatioBasedSampler } = require("@opentelemetry/core");

const tracerProvider = new NodeTracerProvider({
sampler: new ParentOrElseSampler(new AlwaysOffSampler())
sampler: new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(0.5) })
dyladan marked this conversation as resolved.
Show resolved Hide resolved
});
```

Expand Down
2 changes: 1 addition & 1 deletion packages/opentelemetry-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export * from './platform';
export * from './trace/NoRecordingSpan';
export * from './trace/sampler/AlwaysOffSampler';
export * from './trace/sampler/AlwaysOnSampler';
export * from './trace/sampler/ParentOrElseSampler';
export * from './trace/sampler/ParentBasedSampler';
export * from './trace/sampler/TraceIdRatioBasedSampler';
export * from './trace/TraceState';
export * from './trace/IdGenerator';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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, Link, Sampler, SamplingResult, SpanContext, SpanKind, TraceFlags } from '@opentelemetry/api';
import { AlwaysOffSampler } from './AlwaysOffSampler';
import { AlwaysOnSampler } from './AlwaysOnSampler';

/**
* 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;
dyladan marked this conversation as resolved.
Show resolved Hide resolved
private remoteParentNotSampled: Sampler;
private localParentSampled: Sampler;
private localParentNotSampled: Sampler;

constructor(config: ParentBasedSamplerConfig) {
this.root = config.root;
dyladan marked this conversation as resolved.
Show resolved Hide resolved
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(
parentContext: SpanContext | undefined,
traceId: string,
spanName: string,
spanKind: SpanKind,
attributes: Attributes,
links: Link[]
): SamplingResult {
if (!parentContext) {
return this.root.shouldSample(parentContext, traceId, spanName, spanKind, attributes, links);
}

if (parentContext.isRemote) {
if (parentContext.traceFlags & TraceFlags.SAMPLED) {
return this.remoteParentSampled.shouldSample(parentContext, traceId, spanName, spanKind, attributes, links)
}
return this.remoteParentNotSampled.shouldSample(parentContext, traceId, spanName, spanKind, attributes, links);
}

if (parentContext.traceFlags & TraceFlags.SAMPLED) {
return this.localParentSampled.shouldSample(parentContext, traceId, spanName, spanKind, attributes, links);
}

return this.localParentNotSampled.shouldSample(parentContext, 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;
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import * as assert from 'assert';
import * as api from '@opentelemetry/api';
import { AlwaysOnSampler } from '../../src/trace/sampler/AlwaysOnSampler';
import { ParentOrElseSampler } from '../../src/trace/sampler/ParentOrElseSampler';
import { ParentBasedSampler } from '../../src/trace/sampler/ParentBasedSampler';
import { TraceFlags, SpanKind } from '@opentelemetry/api';
import { AlwaysOffSampler } from '../../src/trace/sampler/AlwaysOffSampler';
import { TraceIdRatioBasedSampler } from '../../src';
Expand All @@ -25,23 +25,23 @@ const traceId = 'd4cda95b652f4a1592b449d5929fda1b';
const spanId = '6e0c63257de34c92';
const spanName = 'foobar';

describe('ParentOrElseSampler', () => {
describe('ParentBasedSampler', () => {
it('should reflect sampler name with delegate sampler', () => {
let sampler = new ParentOrElseSampler(new AlwaysOnSampler());
assert.strictEqual(sampler.toString(), 'ParentOrElse{AlwaysOnSampler}');
let sampler = new ParentBasedSampler({ root: new AlwaysOnSampler() });
assert.strictEqual(sampler.toString(), 'ParentBased{root=AlwaysOnSampler, remoteParentSampled=AlwaysOnSampler, remoteParentNotSampled=AlwaysOffSampler, localParentSampled=AlwaysOnSampler, localParentNotSampled=AlwaysOffSampler}');

sampler = new ParentOrElseSampler(new AlwaysOnSampler());
assert.strictEqual(sampler.toString(), 'ParentOrElse{AlwaysOnSampler}');
sampler = new ParentBasedSampler({ root: new AlwaysOffSampler() });
assert.strictEqual(sampler.toString(), 'ParentBased{root=AlwaysOffSampler, remoteParentSampled=AlwaysOnSampler, remoteParentNotSampled=AlwaysOffSampler, localParentSampled=AlwaysOnSampler, localParentNotSampled=AlwaysOffSampler}');

sampler = new ParentOrElseSampler(new TraceIdRatioBasedSampler(0.5));
sampler = new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(0.5) });
assert.strictEqual(
sampler.toString(),
'ParentOrElse{TraceIdRatioBased{0.5}}'
'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 ParentOrElseSampler(new AlwaysOnSampler());
const sampler = new ParentBasedSampler({ root: new AlwaysOnSampler() });

const spanContext = {
traceId,
Expand All @@ -64,7 +64,7 @@ describe('ParentOrElseSampler', () => {
});

it('should return api.SamplingDecision.RECORD_AND_SAMPLED while composited with AlwaysOnSampler', () => {
const sampler = new ParentOrElseSampler(new AlwaysOnSampler());
const sampler = new ParentBasedSampler({ root: new AlwaysOnSampler() });

assert.deepStrictEqual(
sampler.shouldSample(
Expand All @@ -82,7 +82,7 @@ describe('ParentOrElseSampler', () => {
});

it('should return api.SamplingDecision.RECORD_AND_SAMPLED for sampled parent while composited with AlwaysOffSampler', () => {
const sampler = new ParentOrElseSampler(new AlwaysOffSampler());
const sampler = new ParentBasedSampler({ root: new AlwaysOffSampler() });

const spanContext = {
traceId,
Expand All @@ -105,7 +105,7 @@ describe('ParentOrElseSampler', () => {
});

it('should return api.SamplingDecision.RECORD_AND_SAMPLED while composited with AlwaysOffSampler', () => {
const sampler = new ParentOrElseSampler(new AlwaysOffSampler());
const sampler = new ParentBasedSampler({ root: new AlwaysOffSampler() });

assert.deepStrictEqual(
sampler.shouldSample(
Expand Down
2 changes: 1 addition & 1 deletion packages/opentelemetry-tracing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ span.end();
Tracing configuration is a merge of user supplied configuration with both the default
configuration as specified in [config.ts](./src/config.ts) and an
environmentally configurable (via `OTEL_SAMPLING_PROBABILITY`) probability
sampler delegate of a [ParentOrElse](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk.md#parentorelse) sampler.
sampler delegate of a [ParentBased](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk.md#parentbased) sampler.

## Example

Expand Down
8 changes: 4 additions & 4 deletions packages/opentelemetry-tracing/src/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from './config';
import { TracerConfig } from './types';
import {
ParentOrElseSampler,
ParentBasedSampler,
TraceIdRatioBasedSampler,
getEnv,
} from '@opentelemetry/core';
Expand All @@ -40,9 +40,9 @@ export function mergeConfig(userConfig: TracerConfig) {
// use default AlwaysOnSampler if otelSamplingProbability is 1
otelSamplingProbability !== undefined && otelSamplingProbability < 1
? {
sampler: new ParentOrElseSampler(
new TraceIdRatioBasedSampler(otelSamplingProbability)
),
sampler: new ParentBasedSampler({
root: new TraceIdRatioBasedSampler(otelSamplingProbability)
}),
}
: {},
userConfig
Expand Down