-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(sampler-composite): add ComposableAnnotatingSampler and ComposableRuleBasedSampler #6305
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
Changes from 4 commits
a2685ff
cf329d5
4bbacf9
cd13e5b
e967ead
ce2b062
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| /* | ||
| * 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, Link, SpanKind } from '@opentelemetry/api'; | ||
| import type { ComposableSampler, SamplingIntent } from './types'; | ||
|
|
||
| class ComposableAnnotatingSampler implements ComposableSampler { | ||
| private readonly delegate: ComposableSampler; | ||
| private readonly attributes: Attributes; | ||
| private readonly description: string; | ||
|
|
||
| constructor(delegate: ComposableSampler, attributes: Attributes) { | ||
| // Shallow copy `attributes` to avoid changes to the original object (at | ||
| // least top-level fields) impacting the sampler. Freeze the object so | ||
| // we can return it in `getSamplingIntent` without copying there. | ||
| this.attributes = Object.freeze({ ...attributes }); | ||
| this.delegate = delegate; | ||
| this.description = `ComposableAnnotatingSampler(${delegate}, attributes)`; | ||
| } | ||
|
|
||
| getSamplingIntent( | ||
| context: Context, | ||
| traceId: string, | ||
| spanName: string, | ||
| spanKind: SpanKind, | ||
| attributes: Attributes, | ||
| links: Link[] | ||
| ): SamplingIntent { | ||
| const intent = this.delegate.getSamplingIntent( | ||
| context, | ||
| traceId, | ||
| spanName, | ||
| spanKind, | ||
| attributes, | ||
| links | ||
| ); | ||
| return { | ||
| threshold: intent.threshold, | ||
| thresholdReliable: intent.thresholdReliable, | ||
| attributes: intent.attributes | ||
| ? { ...intent.attributes, ...this.attributes } | ||
| : this.attributes, | ||
| updateTraceState: intent.updateTraceState, | ||
| }; | ||
| } | ||
|
|
||
| toString(): string { | ||
| return this.description; | ||
| } | ||
| } | ||
|
|
||
| export function createComposableAnnotatingSampler( | ||
| delegate: ComposableSampler, | ||
| attributes: Attributes | ||
| ): ComposableSampler { | ||
| return new ComposableAnnotatingSampler(delegate, attributes); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| /* | ||
| * 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, Link, SpanKind } from '@opentelemetry/api'; | ||
|
|
||
| import type { ComposableSampler, SamplingIntent, SamplingRule } from './types'; | ||
| import { INVALID_THRESHOLD } from './util'; | ||
|
|
||
| const NON_SAMPLING_INTENT: SamplingIntent = Object.freeze({ | ||
| threshold: INVALID_THRESHOLD, | ||
| thresholdReliable: false, | ||
| }); | ||
|
|
||
| class ComposableRuleBasedSampler implements ComposableSampler { | ||
| private readonly rules: SamplingRule[]; | ||
| private readonly description: string; | ||
|
|
||
| constructor(rules: SamplingRule[]) { | ||
| this.rules = rules; | ||
| const ruleReprs = rules.map( | ||
| rule => `(${rule[0].name || '(anonymous)'}, ${rule[1].toString()})` | ||
| ); | ||
| this.description = `ComposableRuleBasedSampler([${ruleReprs.join(', ')}])`; | ||
| } | ||
|
|
||
| getSamplingIntent( | ||
| context: Context, | ||
| traceId: string, | ||
| spanName: string, | ||
| spanKind: SpanKind, | ||
| attributes: Attributes, | ||
| links: Link[] | ||
| ): SamplingIntent { | ||
| for (const [predicate, sampler] of this.rules) { | ||
| if (predicate(context, traceId, spanName, spanKind, attributes, links)) { | ||
| return sampler.getSamplingIntent( | ||
| context, | ||
| traceId, | ||
| spanName, | ||
| spanKind, | ||
| attributes, | ||
| links | ||
| ); | ||
| } | ||
| } | ||
| return NON_SAMPLING_INTENT; | ||
| } | ||
|
|
||
| toString(): string { | ||
| return this.description; | ||
| } | ||
| } | ||
|
|
||
| export function createComposableRuleBasedSampler( | ||
| rules: SamplingRule[] | ||
| ): ComposableSampler { | ||
| return new ComposableRuleBasedSampler(rules); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,3 +42,8 @@ export interface ComposableSampler { | |
| /** Returns the sampler name or short description with the configuration. */ | ||
| toString(): string; | ||
| } | ||
|
|
||
| export type SamplingPredicate = ( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (same thing about the reference The docs define a SpanMatches method in the predicate. So its defined as an interface rather than a function. My guess is by having interface SamplingPredicate {
spanMatches: (...args: Parameters<Sampler['shouldSample']>) => boolean;
}implementors are able to provide a predicate that holds state... Writing that last sentence I realised this can be achieved using closures so you can ignore my comment.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same answer as above. The language that made it into the spec is |
||
| ...args: Parameters<Sampler['shouldSample']> | ||
| ) => boolean; | ||
| export type SamplingRule = [SamplingPredicate, ComposableSampler]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 { | ||
| createComposableAlwaysOnSampler, | ||
| createComposableAnnotatingSampler, | ||
| } from '../src'; | ||
|
|
||
| describe('ComposableAnnotatingSampler', () => { | ||
| const alwaysOnSampler = createComposableAlwaysOnSampler(); | ||
|
|
||
| it('should have a description', () => { | ||
| const sampler = createComposableAnnotatingSampler(alwaysOnSampler, {}); | ||
| assert.strictEqual( | ||
| sampler.toString(), | ||
| 'ComposableAnnotatingSampler(ComposableAlwaysOnSampler, attributes)' | ||
| ); | ||
| }); | ||
|
|
||
| it('should add attributes to SamplingIntent', () => { | ||
| const delegateIntent = alwaysOnSampler.getSamplingIntent( | ||
| context.active(), | ||
| 'unused', | ||
| 'span-name', | ||
| SpanKind.SERVER, | ||
| {}, | ||
| [] | ||
| ); | ||
| const sampler = createComposableAnnotatingSampler(alwaysOnSampler, { | ||
| foo: 'bar', | ||
| }); | ||
| const intent = sampler.getSamplingIntent( | ||
| context.active(), | ||
| 'unused', | ||
| 'span-name', | ||
| SpanKind.SERVER, | ||
| {}, | ||
| [] | ||
| ); | ||
| assert.strictEqual(intent.threshold, delegateIntent.threshold); | ||
| assert.strictEqual( | ||
| intent.thresholdReliable, | ||
| delegateIntent.thresholdReliable | ||
| ); | ||
| assert.deepStrictEqual(intent.attributes, { foo: 'bar' }); | ||
| }); | ||
|
|
||
| it('should merge attributes', () => { | ||
| const sampler = createComposableAnnotatingSampler( | ||
| createComposableAnnotatingSampler(alwaysOnSampler, { | ||
| foo: 'baz', | ||
| wuz: 'here', | ||
| }), | ||
| { foo: 'bar', spam: 'eggs' } | ||
| ); | ||
| const intent = sampler.getSamplingIntent( | ||
| context.active(), | ||
| 'unused', | ||
| 'span-name', | ||
| SpanKind.SERVER, | ||
| {}, | ||
| [] | ||
| ); | ||
| assert.deepStrictEqual(intent.attributes, { | ||
| foo: 'bar', // outer annotating sampler wins | ||
| spam: 'eggs', | ||
| wuz: 'here', | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is the good reference but I read
ComposableSamplergets 2 params:I the span kind is provided the
getSamplingIntentlogic changes a bit. From the docsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed, the specifics from the OTEPs changed in some cases when making it to the spec. https://opentelemetry.io/docs/specs/otel/trace/sdk/#composablerulebased says:
Hence no separate
span kindargument. Note that the predicate (typeSamplingPredicatedoes get the spanKind, along with other arguments for its decision).