Skip to content

Commit 246e6ea

Browse files
authored
[XRay Sampler] Fix - Ensure all XRay Sampler functionality is under ParentBased logic (#80)
1 parent 62b1f1b commit 246e6ea

File tree

6 files changed

+192
-89
lines changed

6 files changed

+192
-89
lines changed

aws-distro-opentelemetry-node-autoinstrumentation/src/sampler/aws-xray-remote-sampler.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,38 @@ const DEFAULT_RULES_POLLING_INTERVAL_SECONDS: number = 5 * 60;
2222
// Default endpoint for awsproxy : https://aws-otel.github.io/docs/getting-started/remote-sampling#enable-awsproxy-extension
2323
const DEFAULT_AWS_PROXY_ENDPOINT: string = 'http://localhost:2000';
2424

25+
// Wrapper class to ensure that all XRay Sampler Functionality in _AwsXRayRemoteSampler
26+
// uses ParentBased logic to respect the parent span's sampling decision
2527
export class AwsXRayRemoteSampler implements Sampler {
28+
private _root: ParentBasedSampler;
29+
constructor(samplerConfig: AwsXRayRemoteSamplerConfig) {
30+
this._root = new ParentBasedSampler({ root: new _AwsXRayRemoteSampler(samplerConfig) });
31+
}
32+
public shouldSample(
33+
context: Context,
34+
traceId: string,
35+
spanName: string,
36+
spanKind: SpanKind,
37+
attributes: Attributes,
38+
links: Link[]
39+
): SamplingResult {
40+
return this._root.shouldSample(context, traceId, spanName, spanKind, attributes, links);
41+
}
42+
43+
public toString(): string {
44+
return `AwsXRayRemoteSampler{root=${this._root.toString()}`;
45+
}
46+
}
47+
48+
// _AwsXRayRemoteSampler contains all core XRay Sampler Functionality,
49+
// however it is NOT Parent-based (e.g. Sample logic runs for each span)
50+
// Not intended for external use, use Parent-based `AwsXRayRemoteSampler` instead.
51+
export class _AwsXRayRemoteSampler implements Sampler {
2652
private rulePollingIntervalMillis: number;
2753
private targetPollingInterval: number;
2854
private awsProxyEndpoint: string;
2955
private ruleCache: RuleCache;
30-
private fallbackSampler: ParentBasedSampler;
56+
private fallbackSampler: FallbackSampler;
3157
private samplerDiag: DiagLogger;
3258
private rulePoller: NodeJS.Timer | undefined;
3359
private targetPoller: NodeJS.Timer | undefined;
@@ -53,8 +79,8 @@ export class AwsXRayRemoteSampler implements Sampler {
5379
this.targetPollingJitterMillis = (Math.random() / 10) * 1000;
5480

5581
this.awsProxyEndpoint = samplerConfig.endpoint ? samplerConfig.endpoint : DEFAULT_AWS_PROXY_ENDPOINT;
56-
this.fallbackSampler = new ParentBasedSampler({ root: new FallbackSampler() });
57-
this.clientId = AwsXRayRemoteSampler.generateClientId();
82+
this.fallbackSampler = new FallbackSampler();
83+
this.clientId = _AwsXRayRemoteSampler.generateClientId();
5884
this.ruleCache = new RuleCache(samplerConfig.resource);
5985

6086
this.samplingClient = new AwsXraySamplingClient(this.awsProxyEndpoint, this.samplerDiag);
@@ -95,7 +121,9 @@ export class AwsXRayRemoteSampler implements Sampler {
95121
}
96122

97123
public toString(): string {
98-
return 'AwsXRayRemoteSampler{remote sampling with AWS X-Ray}';
124+
return `_AwsXRayRemoteSampler{awsProxyEndpoint=${
125+
this.awsProxyEndpoint
126+
}, rulePollingIntervalMillis=${this.rulePollingIntervalMillis.toString()}}`;
99127
}
100128

101129
private startSamplingRulesPoller(): void {

aws-distro-opentelemetry-node-autoinstrumentation/src/sampler/sampling-rule-applier.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@
33

44
import { AttributeValue, Attributes, Context, Link, SpanKind } from '@opentelemetry/api';
55
import { Resource } from '@opentelemetry/resources';
6-
import {
7-
ParentBasedSampler,
8-
SamplingDecision,
9-
SamplingResult,
10-
TraceIdRatioBasedSampler,
11-
} from '@opentelemetry/sdk-trace-base';
6+
import { SamplingDecision, SamplingResult, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
127
import {
138
ATTR_CLIENT_ADDRESS,
149
ATTR_HTTP_REQUEST_METHOD,
@@ -39,20 +34,20 @@ const MAX_DATE_TIME_MILLIS: number = new Date(8_640_000_000_000_000).getTime();
3934

4035
export class SamplingRuleApplier {
4136
public samplingRule: SamplingRule;
42-
private reservoirSampler: ParentBasedSampler;
43-
private fixedRateSampler: ParentBasedSampler;
37+
private reservoirSampler: RateLimitingSampler;
38+
private fixedRateSampler: TraceIdRatioBasedSampler;
4439
private statistics: Statistics;
4540
private borrowingEnabled: boolean;
4641
private reservoirExpiryTimeInMillis: number;
4742

4843
constructor(samplingRule: ISamplingRule, statistics: Statistics = new Statistics(), target?: SamplingTargetDocument) {
4944
this.samplingRule = new SamplingRule(samplingRule);
5045

51-
this.fixedRateSampler = new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(this.samplingRule.FixedRate) });
46+
this.fixedRateSampler = new TraceIdRatioBasedSampler(this.samplingRule.FixedRate);
5247
if (samplingRule.ReservoirSize > 0) {
53-
this.reservoirSampler = new ParentBasedSampler({ root: new RateLimitingSampler(1) });
48+
this.reservoirSampler = new RateLimitingSampler(1);
5449
} else {
55-
this.reservoirSampler = new ParentBasedSampler({ root: new RateLimitingSampler(0) });
50+
this.reservoirSampler = new RateLimitingSampler(0);
5651
}
5752

5853
this.reservoirExpiryTimeInMillis = MAX_DATE_TIME_MILLIS;
@@ -63,7 +58,7 @@ export class SamplingRuleApplier {
6358
if (target) {
6459
this.borrowingEnabled = false;
6560
if (target.ReservoirQuota) {
66-
this.reservoirSampler = new ParentBasedSampler({ root: new RateLimitingSampler(target.ReservoirQuota) });
61+
this.reservoirSampler = new RateLimitingSampler(target.ReservoirQuota);
6762
}
6863

6964
if (target.ReservoirQuotaTTL) {
@@ -73,7 +68,7 @@ export class SamplingRuleApplier {
7368
}
7469

7570
if (target.FixedRate) {
76-
this.fixedRateSampler = new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(target.FixedRate) });
71+
this.fixedRateSampler = new TraceIdRatioBasedSampler(target.FixedRate);
7772
}
7873
}
7974
}
@@ -156,7 +151,7 @@ export class SamplingRuleApplier {
156151
}
157152

158153
if (result.decision === SamplingDecision.NOT_RECORD) {
159-
result = this.fixedRateSampler.shouldSample(context, traceId, spanName, spanKind, attributes, links);
154+
result = this.fixedRateSampler.shouldSample(context, traceId);
160155
}
161156

162157
this.statistics.SampleCount += result.decision !== SamplingDecision.NOT_RECORD ? 1 : 0;

aws-distro-opentelemetry-node-autoinstrumentation/test/aws-opentelemetry-configurator.test.ts

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import expect from 'expect';
1919
import * as sinon from 'sinon';
2020
import { AlwaysRecordSampler } from '../src/always-record-sampler';
2121
import { AttributePropagatingSpanProcessor } from '../src/attribute-propagating-span-processor';
22+
import { AwsBatchUnsampledSpanProcessor } from '../src/aws-batch-unsampled-span-processor';
2223
import { AwsMetricAttributesSpanExporter } from '../src/aws-metric-attributes-span-exporter';
2324
import {
2425
ApplicationSignalsExporterProvider,
@@ -27,12 +28,11 @@ import {
2728
customBuildSamplerFromEnv,
2829
} from '../src/aws-opentelemetry-configurator';
2930
import { AwsSpanMetricsProcessor } from '../src/aws-span-metrics-processor';
31+
import { OTLPUdpSpanExporter } from '../src/otlp-udp-exporter';
3032
import { setAwsDefaultEnvironmentVariables } from '../src/register';
3133
import { AwsXRayRemoteSampler } from '../src/sampler/aws-xray-remote-sampler';
3234
import { AwsXraySamplingClient } from '../src/sampler/aws-xray-sampling-client';
3335
import { GetSamplingRulesResponse } from '../src/sampler/remote-sampler.types';
34-
import { OTLPUdpSpanExporter } from '../src/otlp-udp-exporter';
35-
import { AwsBatchUnsampledSpanProcessor } from '../src/aws-batch-unsampled-span-processor';
3636

3737
// Tests AwsOpenTelemetryConfigurator after running Environment Variable setup in register.ts
3838
describe('AwsOpenTelemetryConfiguratorTest', () => {
@@ -73,6 +73,7 @@ describe('AwsOpenTelemetryConfiguratorTest', () => {
7373
const startTimeSec: number = Math.floor(new Date().getTime() / 1000.0);
7474
const span: Span = tracer.startSpan('test');
7575
const traceId: string = span.spanContext().traceId;
76+
span.end();
7677
const traceId4ByteHex: string = traceId.substring(0, 8);
7778
const traceId4ByteNumber: number = Number(`0x${traceId4ByteHex}`);
7879
expect(traceId4ByteNumber).toBeGreaterThanOrEqual(startTimeSec);
@@ -118,11 +119,11 @@ describe('AwsOpenTelemetryConfiguratorTest', () => {
118119
const sampler = customBuildSamplerFromEnv(Resource.empty());
119120

120121
expect(sampler).toBeInstanceOf(AwsXRayRemoteSampler);
121-
expect((sampler as any).awsProxyEndpoint).toEqual('http://localhost:2000');
122-
expect((sampler as any).rulePollingIntervalMillis).toEqual(300000); // ms
122+
expect((sampler as any)._root._root.awsProxyEndpoint).toEqual('http://localhost:2000');
123+
expect((sampler as any)._root._root.rulePollingIntervalMillis).toEqual(300000); // ms
123124

124-
clearInterval((sampler as any).rulePoller);
125-
clearInterval((sampler as any).targetPoller);
125+
clearInterval((sampler as any)._root._root.rulePoller);
126+
clearInterval((sampler as any)._root._root.targetPoller);
126127
});
127128

128129
it('ImportXRaySamplerWhenSamplerArgsSet', () => {
@@ -133,17 +134,17 @@ describe('AwsOpenTelemetryConfiguratorTest', () => {
133134
const sampler = customBuildSamplerFromEnv(Resource.empty());
134135

135136
expect(sampler).toBeInstanceOf(AwsXRayRemoteSampler);
136-
expect((sampler as any).awsProxyEndpoint).toEqual('http://asdfghjkl:2000');
137-
expect((sampler as any).rulePollingIntervalMillis).toEqual(600000); // ms
138-
expect(((sampler as any).samplingClient as any).getSamplingRulesEndpoint).toEqual(
137+
expect((sampler as any)._root._root.awsProxyEndpoint).toEqual('http://asdfghjkl:2000');
138+
expect((sampler as any)._root._root.rulePollingIntervalMillis).toEqual(600000); // ms
139+
expect(((sampler as any)._root._root.samplingClient as any).getSamplingRulesEndpoint).toEqual(
139140
'http://asdfghjkl:2000/GetSamplingRules'
140141
);
141-
expect(((sampler as any).samplingClient as any).samplingTargetsEndpoint).toEqual(
142+
expect(((sampler as any)._root._root.samplingClient as any).samplingTargetsEndpoint).toEqual(
142143
'http://asdfghjkl:2000/SamplingTargets'
143144
);
144145

145-
clearInterval((sampler as any).rulePoller);
146-
clearInterval((sampler as any).targetPoller);
146+
clearInterval((sampler as any)._root._root.rulePoller);
147+
clearInterval((sampler as any)._root._root.targetPoller);
147148
});
148149

149150
it('ImportXRaySamplerWithInvalidPollingIntervalSet', () => {
@@ -156,17 +157,17 @@ describe('AwsOpenTelemetryConfiguratorTest', () => {
156157
const sampler = customBuildSamplerFromEnv(Resource.empty());
157158

158159
expect(sampler).toBeInstanceOf(AwsXRayRemoteSampler);
159-
expect((sampler as any).awsProxyEndpoint).toEqual('http://asdfghjkl:2000');
160-
expect((sampler as any).rulePollingIntervalMillis).toEqual(300000); // default value
161-
expect(((sampler as any).samplingClient as any).getSamplingRulesEndpoint).toEqual(
160+
expect((sampler as any)._root._root.awsProxyEndpoint).toEqual('http://asdfghjkl:2000');
161+
expect((sampler as any)._root._root.rulePollingIntervalMillis).toEqual(300000); // default value
162+
expect(((sampler as any)._root._root.samplingClient as any).getSamplingRulesEndpoint).toEqual(
162163
'http://asdfghjkl:2000/GetSamplingRules'
163164
);
164-
expect(((sampler as any).samplingClient as any).samplingTargetsEndpoint).toEqual(
165+
expect(((sampler as any)._root._root.samplingClient as any).samplingTargetsEndpoint).toEqual(
165166
'http://asdfghjkl:2000/SamplingTargets'
166167
);
167168

168-
clearInterval((sampler as any).rulePoller);
169-
clearInterval((sampler as any).targetPoller);
169+
clearInterval((sampler as any)._root._root.rulePoller);
170+
clearInterval((sampler as any)._root._root.targetPoller);
170171
});
171172

172173
// test_import_xray_sampler_with_invalid_environment_arguments
@@ -188,12 +189,12 @@ describe('AwsOpenTelemetryConfiguratorTest', () => {
188189
let sampler = customBuildSamplerFromEnv(Resource.empty());
189190

190191
expect(sampler).toBeInstanceOf(AwsXRayRemoteSampler);
191-
expect((sampler as any).awsProxyEndpoint).toEqual('http://lo=cal=host=:2000');
192-
expect((sampler as any).rulePollingIntervalMillis).toEqual(600000);
193-
expect(((sampler as any).samplingClient as any).getSamplingRulesEndpoint).toEqual(
192+
expect((sampler as any)._root._root.awsProxyEndpoint).toEqual('http://lo=cal=host=:2000');
193+
expect((sampler as any)._root._root.rulePollingIntervalMillis).toEqual(600000);
194+
expect(((sampler as any)._root._root.samplingClient as any).getSamplingRulesEndpoint).toEqual(
194195
'http://lo=cal=host=:2000/GetSamplingRules'
195196
);
196-
expect(((sampler as any).samplingClient as any).samplingTargetsEndpoint).toEqual(
197+
expect(((sampler as any)._root._root.samplingClient as any).samplingTargetsEndpoint).toEqual(
197198
'http://lo=cal=host=:2000/SamplingTargets'
198199
);
199200

@@ -202,12 +203,12 @@ describe('AwsOpenTelemetryConfiguratorTest', () => {
202203
sampler = customBuildSamplerFromEnv(Resource.empty());
203204

204205
expect(sampler).toBeInstanceOf(AwsXRayRemoteSampler);
205-
expect((sampler as any).awsProxyEndpoint).toEqual('http://localhost:2000');
206-
expect((sampler as any).rulePollingIntervalMillis).toEqual(550000);
207-
expect(((sampler as any).samplingClient as any).getSamplingRulesEndpoint).toEqual(
206+
expect((sampler as any)._root._root.awsProxyEndpoint).toEqual('http://localhost:2000');
207+
expect((sampler as any)._root._root.rulePollingIntervalMillis).toEqual(550000);
208+
expect(((sampler as any)._root._root.samplingClient as any).getSamplingRulesEndpoint).toEqual(
208209
'http://localhost:2000/GetSamplingRules'
209210
);
210-
expect(((sampler as any).samplingClient as any).samplingTargetsEndpoint).toEqual(
211+
expect(((sampler as any)._root._root.samplingClient as any).samplingTargetsEndpoint).toEqual(
211212
'http://localhost:2000/SamplingTargets'
212213
);
213214

0 commit comments

Comments
 (0)