77import io .opentelemetry .api .common .Attributes ;
88import io .opentelemetry .api .trace .SpanKind ;
99import io .opentelemetry .context .Context ;
10+ import io .opentelemetry .contrib .awsxray .GetSamplingRulesResponse .SamplingRuleRecord ;
11+ import io .opentelemetry .contrib .awsxray .GetSamplingTargetsRequest .SamplingStatisticsDocument ;
12+ import io .opentelemetry .contrib .awsxray .GetSamplingTargetsResponse .SamplingTargetDocument ;
13+ import io .opentelemetry .sdk .common .Clock ;
1014import io .opentelemetry .sdk .resources .Resource ;
1115import io .opentelemetry .sdk .trace .data .LinkData ;
1216import io .opentelemetry .sdk .trace .samplers .Sampler ;
1317import io .opentelemetry .sdk .trace .samplers .SamplingResult ;
1418import java .io .Closeable ;
19+ import java .time .Instant ;
20+ import java .util .Date ;
1521import java .util .List ;
22+ import java .util .Map ;
1623import java .util .Random ;
24+ import java .util .Set ;
1725import java .util .concurrent .Executors ;
1826import java .util .concurrent .ScheduledExecutorService ;
1927import java .util .concurrent .ScheduledFuture ;
2028import java .util .concurrent .TimeUnit ;
29+ import java .util .function .Function ;
2130import java .util .logging .Level ;
2231import java .util .logging .Logger ;
32+ import java .util .stream .Collectors ;
2333import org .checkerframework .checker .nullness .qual .Nullable ;
2434
2535/** Remote sampler that gets sampling configuration from AWS X-Ray. */
2636public final class AwsXrayRemoteSampler implements Sampler , Closeable {
2737
28- private static final Random RANDOM = new Random ( );
38+ static final long DEFAULT_TARGET_INTERVAL_NANOS = TimeUnit . SECONDS . toNanos ( 10 );
2939
40+ private static final Random RANDOM = new Random ();
3041 private static final Logger logger = Logger .getLogger (AwsXrayRemoteSampler .class .getName ());
3142
32- private static final String WORKER_THREAD_NAME =
33- AwsXrayRemoteSampler .class .getSimpleName () + "_WorkerThread" ;
34-
3543 // Unique per-process client ID, generated as a random string.
3644 private static final String CLIENT_ID = generateClientId ();
3745
3846 private final Resource resource ;
47+ private final Clock clock ;
3948 private final Sampler initialSampler ;
4049 private final XraySamplerClient client ;
4150 private final ScheduledExecutorService executor ;
4251 private final long pollingIntervalNanos ;
4352 private final int jitterNanos ;
4453
4554 @ Nullable private volatile ScheduledFuture <?> pollFuture ;
55+ @ Nullable private volatile ScheduledFuture <?> fetchTargetsFuture ;
4656 @ Nullable private volatile GetSamplingRulesResponse previousRulesResponse ;
4757 private volatile Sampler sampler ;
4858
@@ -57,8 +67,13 @@ public static AwsXrayRemoteSamplerBuilder newBuilder(Resource resource) {
5767 }
5868
5969 AwsXrayRemoteSampler (
60- Resource resource , String endpoint , Sampler initialSampler , long pollingIntervalNanos ) {
70+ Resource resource ,
71+ Clock clock ,
72+ String endpoint ,
73+ Sampler initialSampler ,
74+ long pollingIntervalNanos ) {
6175 this .resource = resource ;
76+ this .clock = clock ;
6277 this .initialSampler = initialSampler ;
6378 client = new XraySamplerClient (endpoint );
6479 executor =
@@ -107,8 +122,22 @@ private void getAndUpdateSampler() {
107122 client .getSamplingRules (GetSamplingRulesRequest .create (null ));
108123 if (!response .equals (previousRulesResponse )) {
109124 sampler =
110- new XrayRulesSampler (CLIENT_ID , resource , initialSampler , response .getSamplingRules ());
125+ new XrayRulesSampler (
126+ CLIENT_ID ,
127+ resource ,
128+ clock ,
129+ initialSampler ,
130+ response .getSamplingRules ().stream ()
131+ .map (SamplingRuleRecord ::getRule )
132+ .collect (Collectors .toList ()));
111133 previousRulesResponse = response ;
134+ ScheduledFuture <?> existingFetchTargetsFuture = fetchTargetsFuture ;
135+ if (existingFetchTargetsFuture != null ) {
136+ existingFetchTargetsFuture .cancel (false );
137+ }
138+ fetchTargetsFuture =
139+ executor .schedule (
140+ this ::fetchTargets , DEFAULT_TARGET_INTERVAL_NANOS , TimeUnit .NANOSECONDS );
112141 }
113142 } catch (Throwable t ) {
114143 logger .log (Level .FINE , "Failed to update sampler" , t );
@@ -121,6 +150,39 @@ private void scheduleSamplerUpdate() {
121150 pollFuture = executor .schedule (this ::getAndUpdateSampler , delay , TimeUnit .NANOSECONDS );
122151 }
123152
153+ private void fetchTargets () {
154+ if (!(sampler instanceof XrayRulesSampler )) {
155+ throw new IllegalStateException ("Programming bug." );
156+ }
157+
158+ XrayRulesSampler xrayRulesSampler = (XrayRulesSampler ) sampler ;
159+ try {
160+ Date now = Date .from (Instant .ofEpochSecond (0 , clock .now ()));
161+ List <SamplingStatisticsDocument > statistics = xrayRulesSampler .snapshot (now );
162+ Set <String > requestedTargetRuleNames =
163+ statistics .stream ()
164+ .map (SamplingStatisticsDocument ::getRuleName )
165+ .collect (Collectors .toSet ());
166+
167+ GetSamplingTargetsResponse response =
168+ client .getSamplingTargets (GetSamplingTargetsRequest .create (statistics ));
169+ Map <String , SamplingTargetDocument > targets =
170+ response .getDocuments ().stream ()
171+ .collect (Collectors .toMap (SamplingTargetDocument ::getRuleName , Function .identity ()));
172+ sampler =
173+ xrayRulesSampler = xrayRulesSampler .withTargets (targets , requestedTargetRuleNames , now );
174+ } catch (Throwable t ) {
175+ // Might be a transient API failure, try again after a default interval.
176+ executor .schedule (this ::fetchTargets , DEFAULT_TARGET_INTERVAL_NANOS , TimeUnit .NANOSECONDS );
177+ return ;
178+ }
179+
180+ long nextTargetFetchIntervalNanos =
181+ xrayRulesSampler .nextTargetFetchTimeNanos () - clock .nanoTime ();
182+ fetchTargetsFuture =
183+ executor .schedule (this ::fetchTargets , nextTargetFetchIntervalNanos , TimeUnit .NANOSECONDS );
184+ }
185+
124186 @ Override
125187 public void close () {
126188 ScheduledFuture <?> pollFuture = this .pollFuture ;
0 commit comments