Remove the AWS XRay sampler code (#3379)

* Remove the AWS XRay sampler code and deprecate the AwsXrayIdGenerator

* de-deprecate the AWS Xray ID Generator
This commit is contained in:
John Watson 2021-07-08 16:31:39 -07:00 committed by GitHub
parent b5d202a08c
commit d8999911fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1 additions and 2128 deletions

View File

@ -1,16 +1,2 @@
Comparing source compatibility of against
+++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.extension.aws.trace.AwsXrayRemoteSampler (not serializable)
+++ CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+++ NEW SUPERCLASS: java.lang.Object
+++ NEW METHOD: PUBLIC(+) void close()
+++ NEW METHOD: PUBLIC(+) java.lang.String getDescription()
+++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.extension.aws.trace.AwsXrayRemoteSamplerBuilder newBuilder(io.opentelemetry.sdk.resources.Resource)
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.samplers.SamplingResult shouldSample(io.opentelemetry.context.Context, java.lang.String, java.lang.String, io.opentelemetry.api.trace.SpanKind, io.opentelemetry.api.common.Attributes, java.util.List)
+++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.extension.aws.trace.AwsXrayRemoteSamplerBuilder (not serializable)
+++ CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+++ NEW SUPERCLASS: java.lang.Object
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.extension.aws.trace.AwsXrayRemoteSampler build()
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.extension.aws.trace.AwsXrayRemoteSamplerBuilder setEndpoint(java.lang.String)
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.extension.aws.trace.AwsXrayRemoteSamplerBuilder setInitialSampler(io.opentelemetry.sdk.trace.samplers.Sampler)
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.extension.aws.trace.AwsXrayRemoteSamplerBuilder setPollingInterval(java.time.Duration)
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.extension.aws.trace.AwsXrayRemoteSamplerBuilder setPollingInterval(long, java.util.concurrent.TimeUnit)
No changes.

View File

@ -1,120 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.internal.OtelEncodingUtils;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.internal.DaemonThreadFactory;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import java.io.Closeable;
import java.security.SecureRandom;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/** Remote sampler that gets sampling configuration from AWS X-Ray. */
public final class AwsXrayRemoteSampler implements Sampler, Closeable {
private static final Logger logger = Logger.getLogger(AwsXrayRemoteSampler.class.getName());
private static final String WORKER_THREAD_NAME =
AwsXrayRemoteSampler.class.getSimpleName() + "_WorkerThread";
// Unique per-process client ID, generated as a random string.
private static final String CLIENT_ID = generateClientId();
private final Resource resource;
private final Sampler initialSampler;
private final XraySamplerClient client;
private final ScheduledExecutorService executor;
private final ScheduledFuture<?> pollFuture;
@Nullable private volatile GetSamplingRulesResponse previousRulesResponse;
private volatile Sampler sampler;
/**
* Returns a {@link AwsXrayRemoteSamplerBuilder} with the given {@link Resource}. This {@link
* Resource} should be the same as what the {@linkplain io.opentelemetry.sdk.OpenTelemetrySdk
* OpenTelemetry SDK} is configured with.
*/
// TODO(anuraaga): Deprecate after
// https://github.com/open-telemetry/opentelemetry-specification/issues/1588
public static AwsXrayRemoteSamplerBuilder newBuilder(Resource resource) {
return new AwsXrayRemoteSamplerBuilder(resource);
}
AwsXrayRemoteSampler(
Resource resource, String endpoint, Sampler initialSampler, long pollingIntervalNanos) {
this.resource = resource;
this.initialSampler = initialSampler;
client = new XraySamplerClient(endpoint);
executor =
Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory(WORKER_THREAD_NAME));
sampler = initialSampler;
pollFuture =
executor.scheduleAtFixedRate(
this::getAndUpdateSampler, 0, pollingIntervalNanos, TimeUnit.NANOSECONDS);
}
@Override
public SamplingResult shouldSample(
Context parentContext,
String traceId,
String name,
SpanKind spanKind,
Attributes attributes,
List<LinkData> parentLinks) {
return sampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks);
}
@Override
public String getDescription() {
return "AwsXrayRemoteSampler{" + sampler.getDescription() + "}";
}
private void getAndUpdateSampler() {
try {
// No pagination support yet, or possibly ever.
GetSamplingRulesResponse response =
client.getSamplingRules(GetSamplingRulesRequest.create(null));
if (!response.equals(previousRulesResponse)) {
sampler =
new XrayRulesSampler(CLIENT_ID, resource, initialSampler, response.getSamplingRules());
previousRulesResponse = response;
}
} catch (Throwable t) {
logger.log(Level.FINE, "Failed to update sampler", t);
}
}
@Override
public void close() {
pollFuture.cancel(true);
executor.shutdownNow();
// No flushing behavior so no need to wait for the shutdown.
}
private static String generateClientId() {
SecureRandom rand = new SecureRandom();
byte[] bytes = new byte[12];
rand.nextBytes(bytes);
char[] clientIdChars = new char[24];
OtelEncodingUtils.bytesToBase16(bytes, clientIdChars, 12);
return new String(clientIdChars);
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import static io.opentelemetry.api.internal.Utils.checkArgument;
import static java.util.Objects.requireNonNull;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
/** A builder for {@link AwsXrayRemoteSampler}. */
public final class AwsXrayRemoteSamplerBuilder {
private static final String DEFAULT_ENDPOINT = "http://localhost:2000";
private static final long DEFAULT_POLLING_INTERVAL_SECS = 300;
private final Resource resource;
private String endpoint = DEFAULT_ENDPOINT;
private Sampler initialSampler = Sampler.parentBased(Sampler.alwaysOn());
private long pollingIntervalNanos = TimeUnit.SECONDS.toNanos(DEFAULT_POLLING_INTERVAL_SECS);
AwsXrayRemoteSamplerBuilder(Resource resource) {
this.resource = resource;
}
/**
* Sets the endpoint for the TCP proxy to connect to. This is the address to the port on the
* OpenTelemetry Collector configured for proxying X-Ray sampling requests. If unset, defaults to
* {@value DEFAULT_ENDPOINT}.
*/
public AwsXrayRemoteSamplerBuilder setEndpoint(String endpoint) {
requireNonNull(endpoint, "endpoint");
this.endpoint = endpoint;
return this;
}
/**
* Sets the polling interval for configuration updates. If unset, defaults to {@value
* DEFAULT_POLLING_INTERVAL_SECS}s. Must be positive.
*/
public AwsXrayRemoteSamplerBuilder setPollingInterval(Duration delay) {
requireNonNull(delay, "delay");
return setPollingInterval(delay.toNanos(), TimeUnit.NANOSECONDS);
}
/**
* Sets the polling interval for configuration updates. If unset, defaults to {@value
* DEFAULT_POLLING_INTERVAL_SECS}s. Must be positive.
*/
public AwsXrayRemoteSamplerBuilder setPollingInterval(long delay, TimeUnit unit) {
requireNonNull(unit, "unit");
checkArgument(delay >= 0, "delay must be non-negative");
pollingIntervalNanos = unit.toNanos(delay);
return this;
}
/**
* Sets the initial sampler that is used before sampling configuration is obtained. If unset,
* defaults to a parent-based always-on sampler.
*/
public AwsXrayRemoteSamplerBuilder setInitialSampler(Sampler initialSampler) {
requireNonNull(initialSampler, "initialSampler");
this.initialSampler = initialSampler;
return this;
}
/** Returns a {@link AwsXrayRemoteSampler} with the configuration of this builder. */
public AwsXrayRemoteSampler build() {
return new AwsXrayRemoteSampler(resource, endpoint, initialSampler, pollingIntervalNanos);
}
}

View File

@ -1,24 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.auto.value.AutoValue;
import javax.annotation.Nullable;
@AutoValue
@JsonSerialize(as = GetSamplingRulesRequest.class)
abstract class GetSamplingRulesRequest {
static GetSamplingRulesRequest create(@Nullable String nextToken) {
return new AutoValue_GetSamplingRulesRequest(nextToken);
}
@JsonProperty("NextToken")
@Nullable
abstract String getNextToken();
}

View File

@ -1,110 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@AutoValue
abstract class GetSamplingRulesResponse {
@JsonCreator
static GetSamplingRulesResponse create(
@JsonProperty("NextToken") String nextToken,
@JsonProperty("SamplingRuleRecords") List<SamplingRuleRecord> samplingRules) {
return new AutoValue_GetSamplingRulesResponse(nextToken, samplingRules);
}
@Nullable
abstract String getNextToken();
abstract List<SamplingRuleRecord> getSamplingRules();
@AutoValue
abstract static class SamplingRuleRecord {
@JsonCreator
static SamplingRuleRecord create(
@JsonProperty("CreatedAt") String createdAt,
@JsonProperty("ModifiedAt") String modifiedAt,
@JsonProperty("SamplingRule") SamplingRule rule) {
return new AutoValue_GetSamplingRulesResponse_SamplingRuleRecord(createdAt, modifiedAt, rule);
}
abstract String getCreatedAt();
abstract String getModifiedAt();
abstract SamplingRule getRule();
}
@AutoValue
abstract static class SamplingRule {
@JsonCreator
static SamplingRule create(
@JsonProperty("Attributes") Map<String, String> attributes,
@JsonProperty("FixedRate") double fixedRate,
@JsonProperty("Host") String host,
@JsonProperty("HTTPMethod") String httpMethod,
@JsonProperty("Priority") int priority,
@JsonProperty("ReservoirSize") int reservoirSize,
@JsonProperty("ResourceARN") String resourceArn,
@JsonProperty("RuleARN") @Nullable String ruleArn,
@JsonProperty("RuleName") @Nullable String ruleName,
@JsonProperty("ServiceName") String serviceName,
@JsonProperty("ServiceType") String serviceType,
@JsonProperty("URLPath") String urlPath,
@JsonProperty("Version") int version) {
return new AutoValue_GetSamplingRulesResponse_SamplingRule(
attributes,
fixedRate,
host,
httpMethod,
priority,
reservoirSize,
resourceArn,
ruleArn,
ruleName,
serviceName,
serviceType,
urlPath,
version);
}
abstract Map<String, String> getAttributes();
abstract double getFixedRate();
abstract String getHost();
abstract String getHttpMethod();
abstract int getPriority();
abstract int getReservoirSize();
abstract String getResourceArn();
@Nullable
abstract String getRuleArn();
@Nullable
abstract String getRuleName();
abstract String getServiceName();
abstract String getServiceType();
abstract String getUrlPath();
abstract int getVersion();
}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.auto.value.AutoValue;
import java.util.Date;
import java.util.List;
@AutoValue
@JsonSerialize(as = GetSamplingTargetsRequest.class)
abstract class GetSamplingTargetsRequest {
static GetSamplingTargetsRequest create(List<SamplingStatisticsDocument> documents) {
return new AutoValue_GetSamplingTargetsRequest(documents);
}
// Limit of 25 items
@JsonProperty("SamplingStatisticsDocuments")
abstract List<SamplingStatisticsDocument> getDocuments();
@AutoValue
@JsonSerialize(as = SamplingStatisticsDocument.class)
abstract static class SamplingStatisticsDocument {
static SamplingStatisticsDocument.Builder newBuilder() {
return new AutoValue_GetSamplingTargetsRequest_SamplingStatisticsDocument.Builder();
}
@JsonProperty("BorrowCount")
abstract long getBorrowCount();
@JsonProperty("ClientID")
abstract String getClientId();
@JsonProperty("RequestCount")
abstract long getRequestCount();
@JsonProperty("RuleName")
abstract String getRuleName();
@JsonProperty("SampledCount")
abstract long getSampledCount();
@JsonProperty("Timestamp")
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ss",
timezone = "UTC")
abstract Date getTimestamp();
@AutoValue.Builder
abstract static class Builder {
abstract Builder setBorrowCount(long borrowCount);
abstract Builder setClientId(String clientId);
abstract Builder setRequestCount(long requestCount);
abstract Builder setRuleName(String ruleName);
abstract Builder setSampledCount(long sampledCount);
abstract Builder setTimestamp(Date timestamp);
abstract SamplingStatisticsDocument build();
}
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;
import java.util.Date;
import java.util.List;
import javax.annotation.Nullable;
@AutoValue
abstract class GetSamplingTargetsResponse {
@JsonCreator
static GetSamplingTargetsResponse create(
@JsonProperty("LastRuleModification") Date lastRuleModification,
@JsonProperty("SamplingTargetDocuments") List<SamplingTargetDocument> documents,
@JsonProperty("UnprocessedStatistics") List<UnprocessedStatistics> unprocessedStatistics) {
return new AutoValue_GetSamplingTargetsResponse(
lastRuleModification, documents, unprocessedStatistics);
}
abstract Date getLastRuleModification();
abstract List<SamplingTargetDocument> getDocuments();
abstract List<UnprocessedStatistics> getUnprocessedStatistics();
@AutoValue
abstract static class SamplingTargetDocument {
@JsonCreator
static SamplingTargetDocument create(
@JsonProperty("FixedRate") double fixedRate,
@JsonProperty("Interval") int intervalSecs,
@JsonProperty("ReservoirQuota") int reservoirQuota,
@JsonProperty("ReservoirQuotaTTL") @Nullable Date reservoirQuotaTtl,
@JsonProperty("RuleName") String ruleName) {
return new AutoValue_GetSamplingTargetsResponse_SamplingTargetDocument(
fixedRate, intervalSecs, reservoirQuota, reservoirQuotaTtl, ruleName);
}
abstract double getFixedRate();
abstract int getIntervalSecs();
abstract int getReservoirQuota();
@Nullable
abstract Date getReservoirQuotaTtl();
abstract String getRuleName();
}
@AutoValue
abstract static class UnprocessedStatistics {
@JsonCreator
static UnprocessedStatistics create(
@JsonProperty("ErrorCode") String errorCode,
@JsonProperty("Message") String message,
@JsonProperty("RuleName") String ruleName) {
return new AutoValue_GetSamplingTargetsResponse_UnprocessedStatistics(
errorCode, message, ruleName);
}
abstract String getErrorCode();
abstract String getMessage();
abstract String getRuleName();
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.internal.RateLimiter;
import io.opentelemetry.sdk.internal.SystemClock;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import java.util.List;
final class RateLimitingSampler implements Sampler {
private final RateLimiter limiter;
private final int numPerSecond;
RateLimitingSampler(int numPerSecond) {
this(numPerSecond, SystemClock.getInstance());
}
// Visible for testing
RateLimitingSampler(int numPerSecond, Clock clock) {
limiter = new RateLimiter(numPerSecond, numPerSecond, clock);
this.numPerSecond = numPerSecond;
}
@Override
public SamplingResult shouldSample(
Context parentContext,
String traceId,
String name,
SpanKind spanKind,
Attributes attributes,
List<LinkData> parentLinks) {
if (limiter.trySpend(1)) {
return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE);
}
return SamplingResult.create(SamplingDecision.DROP);
}
@Override
public String getDescription() {
return "RateLimitingSampler{" + numPerSecond + "}";
}
}

View File

@ -1,318 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.LongAdder;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
final class SamplingRuleApplier {
private static final Map<String, String> XRAY_CLOUD_PLATFORM;
static {
Map<String, String> xrayCloudPlatform = new HashMap<>();
xrayCloudPlatform.put(ResourceAttributes.CloudPlatformValues.AWS_EC2, "AWS::EC2::Instance");
xrayCloudPlatform.put(ResourceAttributes.CloudPlatformValues.AWS_ECS, "AWS::ECS::Container");
xrayCloudPlatform.put(ResourceAttributes.CloudPlatformValues.AWS_EKS, "AWS::EKS::Container");
xrayCloudPlatform.put(
ResourceAttributes.CloudPlatformValues.AWS_ELASTIC_BEANSTALK,
"AWS::ElasticBeanstalk::Environment");
xrayCloudPlatform.put(
ResourceAttributes.CloudPlatformValues.AWS_LAMBDA, "AWS::Lambda::Function");
XRAY_CLOUD_PLATFORM = Collections.unmodifiableMap(xrayCloudPlatform);
}
private final String clientId;
private final String ruleName;
private final Sampler reservoirSampler;
private final Sampler fixedRateSampler;
private final boolean borrowing;
private final Map<String, Matcher> attributeMatchers;
private final Matcher urlPathMatcher;
private final Matcher serviceNameMatcher;
private final Matcher httpMethodMatcher;
private final Matcher hostMatcher;
private final Matcher serviceTypeMatcher;
private final Matcher resourceArnMatcher;
private final Statistics statistics;
SamplingRuleApplier(String clientId, GetSamplingRulesResponse.SamplingRule rule) {
this.clientId = clientId;
ruleName = rule.getRuleName();
if (rule.getReservoirSize() > 0) {
// Until calling GetSamplingTargets, the default is to borrow 1/s if reservoir size is
// positive.
reservoirSampler = new RateLimitingSampler(1);
borrowing = true;
} else {
// No reservoir sampling, we will always use the fixed rate.
reservoirSampler = Sampler.alwaysOff();
borrowing = false;
}
fixedRateSampler = Sampler.parentBased(Sampler.traceIdRatioBased(rule.getFixedRate()));
if (rule.getAttributes().isEmpty()) {
attributeMatchers = Collections.emptyMap();
} else {
attributeMatchers =
rule.getAttributes().entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> toMatcher(e.getValue())));
}
urlPathMatcher = toMatcher(rule.getUrlPath());
serviceNameMatcher = toMatcher(rule.getServiceName());
httpMethodMatcher = toMatcher(rule.getHttpMethod());
hostMatcher = toMatcher(rule.getHost());
serviceTypeMatcher = toMatcher(rule.getServiceType());
resourceArnMatcher = toMatcher(rule.getResourceArn());
statistics = new Statistics();
}
boolean matches(String name, Attributes attributes, Resource resource) {
int matchedAttributes = 0;
String httpTarget = null;
String httpMethod = null;
String host = null;
for (Map.Entry<AttributeKey<?>, Object> entry : attributes.asMap().entrySet()) {
if (entry.getKey().equals(SemanticAttributes.HTTP_TARGET)) {
httpTarget = (String) entry.getValue();
} else if (entry.getKey().equals(SemanticAttributes.HTTP_METHOD)) {
httpMethod = (String) entry.getValue();
} else if (entry.getKey().equals(SemanticAttributes.HTTP_HOST)) {
host = (String) entry.getValue();
}
Matcher matcher = attributeMatchers.get(entry.getKey().getKey());
if (matcher == null) {
continue;
}
if (matcher.matches(entry.getValue().toString())) {
matchedAttributes++;
} else {
return false;
}
}
// All attributes in the matched attributes must have been present in the span to be a match.
if (matchedAttributes != attributeMatchers.size()) {
return false;
}
return urlPathMatcher.matches(httpTarget)
&& serviceNameMatcher.matches(name)
&& httpMethodMatcher.matches(httpMethod)
&& hostMatcher.matches(host)
&& serviceTypeMatcher.matches(getServiceType(resource))
&& resourceArnMatcher.matches(getArn(attributes, resource));
}
SamplingResult shouldSample(
Context parentContext,
String traceId,
String name,
SpanKind spanKind,
Attributes attributes,
List<LinkData> parentLinks) {
// Incrementing requests first ensures sample / borrow rate are positive.
statistics.requests.increment();
SamplingResult result =
reservoirSampler.shouldSample(
parentContext, traceId, name, spanKind, attributes, parentLinks);
if (result.getDecision() != SamplingDecision.DROP) {
// We use the result from the reservoir sampler if it worked.
if (borrowing) {
statistics.borrowed.increment();
}
statistics.sampled.increment();
return result;
}
result =
fixedRateSampler.shouldSample(
parentContext, traceId, name, spanKind, attributes, parentLinks);
if (result.getDecision() != SamplingDecision.DROP) {
statistics.sampled.increment();
}
return result;
}
GetSamplingTargetsRequest.SamplingStatisticsDocument snapshot(Date now) {
return GetSamplingTargetsRequest.SamplingStatisticsDocument.newBuilder()
.setClientId(clientId)
.setRuleName(ruleName)
.setTimestamp(now)
// Resetting requests first ensures that sample / borrow rate are positive after the reset.
// Snapshotting is not concurrent so this ensures they are always positive.
.setRequestCount(statistics.requests.sumThenReset())
.setSampledCount(statistics.sampled.sumThenReset())
.setBorrowCount(statistics.borrowed.sumThenReset())
.build();
}
@Nullable
private static String getArn(Attributes attributes, Resource resource) {
String arn = resource.getAttributes().get(ResourceAttributes.AWS_ECS_CONTAINER_ARN);
if (arn != null) {
return arn;
}
String cloudPlatform = resource.getAttributes().get(ResourceAttributes.CLOUD_PLATFORM);
if (ResourceAttributes.CloudPlatformValues.AWS_LAMBDA.equals(cloudPlatform)) {
return getLambdaArn(attributes, resource);
}
return null;
}
private static String getLambdaArn(Attributes attributes, Resource resource) {
String arn = resource.getAttributes().get(ResourceAttributes.FAAS_ID);
if (arn != null) {
return arn;
}
return attributes.get(ResourceAttributes.FAAS_ID);
}
@Nullable
private static String getServiceType(Resource resource) {
String cloudPlatform = resource.getAttributes().get(ResourceAttributes.CLOUD_PLATFORM);
if (cloudPlatform == null) {
return null;
}
return XRAY_CLOUD_PLATFORM.get(cloudPlatform);
}
private static Matcher toMatcher(String globPattern) {
if (globPattern.equals("*")) {
return TrueMatcher.INSTANCE;
}
for (int i = 0; i < globPattern.length(); i++) {
char c = globPattern.charAt(i);
if (c == '*' || c == '?') {
return new PatternMatcher(toRegexPattern(globPattern));
}
}
return new StringMatcher(globPattern);
}
private static Pattern toRegexPattern(String globPattern) {
int tokenStart = -1;
StringBuilder patternBuilder = new StringBuilder();
for (int i = 0; i < globPattern.length(); i++) {
char c = globPattern.charAt(i);
if (c == '*' || c == '?') {
if (tokenStart != -1) {
patternBuilder.append(Pattern.quote(globPattern.substring(tokenStart, i)));
tokenStart = -1;
}
if (c == '*') {
patternBuilder.append(".*");
} else {
// c == '?'
patternBuilder.append(".");
}
} else {
if (tokenStart == -1) {
tokenStart = i;
}
}
}
if (tokenStart != -1) {
patternBuilder.append(Pattern.quote(globPattern.substring(tokenStart)));
}
return Pattern.compile(patternBuilder.toString());
}
private interface Matcher {
boolean matches(@Nullable String s);
}
private enum TrueMatcher implements Matcher {
INSTANCE;
@Override
public boolean matches(@Nullable String s) {
return true;
}
@Override
public String toString() {
return "TrueMatcher";
}
}
private static class StringMatcher implements Matcher {
private final String target;
StringMatcher(String target) {
this.target = target;
}
@Override
public boolean matches(@Nullable String s) {
if (s == null) {
return false;
}
return target.equalsIgnoreCase(s);
}
@Override
public String toString() {
return target;
}
}
private static class PatternMatcher implements Matcher {
private final Pattern pattern;
PatternMatcher(Pattern pattern) {
this.pattern = pattern;
}
@Override
public boolean matches(@Nullable String s) {
if (s == null) {
return false;
}
return pattern.matcher(s).matches();
}
@Override
public String toString() {
return pattern.toString();
}
}
// We keep track of sampling requests and decisions to report to X-Ray to allow it to allocate
// quota from the central reservoir. We do not lock around updates because sampling is called on
// the hot, highly-contended path and locking would have significant overhead. The actual possible
// error should not be off to significantly affect quotas in practice.
private static class Statistics {
final LongAdder requests = new LongAdder();
final LongAdder sampled = new LongAdder();
final LongAdder borrowed = new LongAdder();
}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
final class XrayRulesSampler implements Sampler {
private static final Logger logger = Logger.getLogger(XrayRulesSampler.class.getName());
private final Resource resource;
private final Sampler fallbackSampler;
private final SamplingRuleApplier[] ruleAppliers;
XrayRulesSampler(
String clientId,
Resource resource,
Sampler fallbackSampler,
List<GetSamplingRulesResponse.SamplingRuleRecord> rules) {
this.resource = resource;
this.fallbackSampler = fallbackSampler;
ruleAppliers =
rules.stream()
.map(GetSamplingRulesResponse.SamplingRuleRecord::getRule)
// Lower priority value takes precedence so normal ascending sort.
.sorted(Comparator.comparingInt(GetSamplingRulesResponse.SamplingRule::getPriority))
.map(rule -> new SamplingRuleApplier(clientId, rule))
.toArray(SamplingRuleApplier[]::new);
}
@Override
public SamplingResult shouldSample(
Context parentContext,
String traceId,
String name,
SpanKind spanKind,
Attributes attributes,
List<LinkData> parentLinks) {
for (SamplingRuleApplier applier : ruleAppliers) {
if (applier.matches(name, attributes, resource)) {
return applier.shouldSample(
parentContext, traceId, name, spanKind, attributes, parentLinks);
}
}
// In practice, X-Ray always returns a Default rule that matches all requests so it is a bug in
// our code or X-Ray to reach here, fallback just in case.
logger.log(
Level.FINE,
"No sampling rule matched the request. "
+ "This is a bug in either the OpenTelemetry SDK or X-Ray.");
return fallbackSampler.shouldSample(
parentContext, traceId, name, spanKind, attributes, parentLinks);
}
@Override
public String getDescription() {
return "XrayRulesSampler{" + Arrays.toString(ruleAppliers) + "}";
}
}

View File

@ -1,124 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
// Includes work from:
/*
* Copyright 2010-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Portions copyright 2006-2009 James Murty. Please see LICENSE.txt
* for applicable license terms and NOTICE.txt for applicable notices.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.
*/
package io.opentelemetry.sdk.extension.aws.trace;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import io.opentelemetry.sdk.extension.aws.internal.JdkHttpClient;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
final class XraySamplerClient {
private static final ObjectMapper OBJECT_MAPPER =
new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
// AWS APIs return timestamps as floats.
.registerModule(
new SimpleModule().addDeserializer(Date.class, new FloatDateDeserializer()))
// In case API is extended with new fields.
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, /* state= */ false);
private static final Map<String, String> JSON_CONTENT_TYPE =
Collections.singletonMap("Content-Type", "application/json");
private final String getSamplingRulesEndpoint;
private final String getSamplingTargetsEndpoint;
private final JdkHttpClient httpClient;
XraySamplerClient(String host) {
this.getSamplingRulesEndpoint = host + "/GetSamplingRules";
// Lack of Get may look wrong but is correct.
this.getSamplingTargetsEndpoint = host + "/SamplingTargets";
httpClient = new JdkHttpClient();
}
GetSamplingRulesResponse getSamplingRules(GetSamplingRulesRequest request) {
return executeJsonRequest(getSamplingRulesEndpoint, request, GetSamplingRulesResponse.class);
}
GetSamplingTargetsResponse getSamplingTargets(GetSamplingTargetsRequest request) {
return executeJsonRequest(
getSamplingTargetsEndpoint, request, GetSamplingTargetsResponse.class);
}
private <T> T executeJsonRequest(String endpoint, Object request, Class<T> responseType) {
final byte[] requestBody;
try {
requestBody = OBJECT_MAPPER.writeValueAsBytes(request);
} catch (JsonProcessingException e) {
throw new UncheckedIOException("Failed to serialize request.", e);
}
String response =
httpClient.fetchString("POST", endpoint, JSON_CONTENT_TYPE, null, requestBody);
try {
return OBJECT_MAPPER.readValue(response, responseType);
} catch (JsonProcessingException e) {
throw new UncheckedIOException("Failed to deserialize response.", e);
}
}
@SuppressWarnings("JavaUtilDate")
private static class FloatDateDeserializer extends StdDeserializer<Date> {
private static final long serialVersionUID = 4446058377205025341L;
private static final int AWS_DATE_MILLI_SECOND_PRECISION = 3;
private FloatDateDeserializer() {
super(Date.class);
}
@Override
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return parseServiceSpecificDate(p.getText());
}
// Copied from AWS SDK
// https://github.com/aws/aws-sdk-java/blob/7b1e5b87b0bf03456df9e77716b14731adf9a7a7/aws-java-sdk-core/src/main/java/com/amazonaws/util/DateUtils.java#L239
/** Parses the given date string returned by the AWS service into a Date object. */
private static Date parseServiceSpecificDate(String dateString) {
try {
BigDecimal dateValue = new BigDecimal(dateString);
return new Date(dateValue.scaleByPowerOfTen(AWS_DATE_MILLI_SECOND_PRECISION).longValue());
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Unable to parse date : " + dateString, nfe);
}
}
}
}

View File

@ -1,190 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import com.google.common.io.ByteStreams;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.common.MediaType;
import com.linecorp.armeria.server.ServerBuilder;
import com.linecorp.armeria.testing.junit5.server.ServerExtension;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.TraceId;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
class AwsXrayRemoteSamplerTest {
private static final byte[] RESPONSE_1;
private static final byte[] RESPONSE_2;
static {
try {
RESPONSE_1 =
ByteStreams.toByteArray(
requireNonNull(
AwsXrayRemoteSamplerTest.class.getResourceAsStream(
"/test-sampling-rules-response-1.json")));
RESPONSE_2 =
ByteStreams.toByteArray(
requireNonNull(
AwsXrayRemoteSamplerTest.class.getResourceAsStream(
"/test-sampling-rules-response-2.json")));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static final AtomicReference<byte[]> response = new AtomicReference<>();
private static final String TRACE_ID = TraceId.fromLongs(1, 2);
@RegisterExtension
public static final ServerExtension server =
new ServerExtension() {
@Override
protected void configure(ServerBuilder sb) {
sb.service(
"/GetSamplingRules",
(ctx, req) -> {
byte[] response = AwsXrayRemoteSamplerTest.response.get();
if (response == null) {
// Error out until the test configures a response, the sampler will use the
// initial
// sampler in the meantime.
return HttpResponse.of(HttpStatus.INTERNAL_SERVER_ERROR);
}
return HttpResponse.of(HttpStatus.OK, MediaType.JSON_UTF_8, response);
});
}
};
private AwsXrayRemoteSampler sampler;
@BeforeEach
void setUp() {
sampler =
AwsXrayRemoteSampler.newBuilder(Resource.empty())
.setInitialSampler(Sampler.alwaysOn())
.setEndpoint(server.httpUri().toString())
.setPollingInterval(Duration.ofMillis(10))
.build();
}
@AfterEach
void tearDown() {
sampler.close();
response.set(null);
}
@Test
void getAndUpdate() {
// Initial Sampler allows all.
assertThat(
sampler
.shouldSample(
Context.root(),
TRACE_ID,
"cat-service",
SpanKind.SERVER,
Attributes.empty(),
Collections.emptyList())
.getDecision())
.isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
assertThat(
sampler
.shouldSample(
Context.root(),
TRACE_ID,
"dog-service",
SpanKind.SERVER,
Attributes.empty(),
Collections.emptyList())
.getDecision())
.isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
response.set(RESPONSE_1);
// cat-service allowed, others dropped
await()
.untilAsserted(
() -> {
assertThat(
sampler
.shouldSample(
Context.root(),
TRACE_ID,
"cat-service",
SpanKind.SERVER,
Attributes.empty(),
Collections.emptyList())
.getDecision())
.isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
assertThat(
sampler
.shouldSample(
Context.root(),
TRACE_ID,
"dog-service",
SpanKind.SERVER,
Attributes.empty(),
Collections.emptyList())
.getDecision())
.isEqualTo(SamplingDecision.DROP);
});
response.set(RESPONSE_2);
// cat-service dropped, others allowed
await()
.untilAsserted(
() -> {
assertThat(
sampler
.shouldSample(
Context.root(),
TRACE_ID,
"cat-service",
SpanKind.SERVER,
Attributes.empty(),
Collections.emptyList())
.getDecision())
.isEqualTo(SamplingDecision.DROP);
assertThat(
sampler
.shouldSample(
Context.root(),
TRACE_ID,
"dog-service",
SpanKind.SERVER,
Attributes.empty(),
Collections.emptyList())
.getDecision())
.isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
});
}
@Test
void initialSampler() {
assertThat(sampler.getDescription()).isEqualTo("AwsXrayRemoteSampler{AlwaysOnSampler}");
}
}

View File

@ -1,79 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.TraceId;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.internal.TestClock;
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import java.util.Collections;
import org.junit.jupiter.api.Test;
class RateLimitingSamplerTest {
// RateLimiter is well tested, just do some sanity check.
@Test
void limitsRate() {
TestClock clock = TestClock.create();
RateLimitingSampler sampler = new RateLimitingSampler(1, clock);
assertThat(sampler.getDescription()).isEqualTo("RateLimitingSampler{1}");
assertThat(
sampler
.shouldSample(
Context.root(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList())
.getDecision())
.isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
// Balanced used up
assertThat(
sampler
.shouldSample(
Context.root(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList())
.getDecision())
.isEqualTo(SamplingDecision.DROP);
clock.advanceMillis(100);
// Balance restored after a second, not yet
assertThat(
sampler
.shouldSample(
Context.root(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList())
.getDecision())
.isEqualTo(SamplingDecision.DROP);
clock.advanceMillis(900);
// Balance restored
assertThat(
sampler
.shouldSample(
Context.root(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList())
.getDecision())
.isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
}
}

View File

@ -1,604 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.TraceId;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@SuppressWarnings("JavaUtilDate")
class SamplingRuleApplierTest {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final String CLIENT_ID = "test-client-id";
@Nested
@SuppressWarnings("ClassCanBeStatic")
class ExactMatch {
private final SamplingRuleApplier applier =
new SamplingRuleApplier(CLIENT_ID, readSamplingRule("/sampling-rule-exactmatch.json"));
private final Resource resource =
Resource.builder()
.put(ResourceAttributes.CLOUD_PLATFORM, ResourceAttributes.CloudPlatformValues.AWS_EKS)
.put(
ResourceAttributes.AWS_ECS_CONTAINER_ARN,
"arn:aws:xray:us-east-1:595986152929:my-service")
.build();
private final Attributes attributes =
Attributes.builder()
.put(SemanticAttributes.HTTP_METHOD, "GET")
.put(SemanticAttributes.HTTP_HOST, "opentelemetry.io")
.put(SemanticAttributes.HTTP_TARGET, "/instrument-me")
.put(AttributeKey.stringKey("animal"), "cat")
.put(AttributeKey.longKey("speed"), 10)
.build();
// FixedRate set to 1.0 in rule and no reservoir
@Test
void fixedRateAlwaysSample() {
assertThat(
applier.shouldSample(
Context.current(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList()))
.isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE));
Date now = new Date();
GetSamplingTargetsRequest.SamplingStatisticsDocument statistics = applier.snapshot(now);
assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID);
assertThat(statistics.getRuleName()).isEqualTo("Test");
assertThat(statistics.getTimestamp()).isEqualTo(now);
assertThat(statistics.getRequestCount()).isEqualTo(1);
assertThat(statistics.getSampledCount()).isEqualTo(1);
assertThat(statistics.getBorrowCount()).isEqualTo(0);
// Reset
statistics = applier.snapshot(now);
assertThat(statistics.getRequestCount()).isEqualTo(0);
assertThat(statistics.getSampledCount()).isEqualTo(0);
assertThat(statistics.getBorrowCount()).isEqualTo(0);
applier.shouldSample(
Context.current(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList());
applier.shouldSample(
Context.current(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList());
now = new Date();
statistics = applier.snapshot(now);
assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID);
assertThat(statistics.getRuleName()).isEqualTo("Test");
assertThat(statistics.getTimestamp()).isEqualTo(now);
assertThat(statistics.getRequestCount()).isEqualTo(2);
assertThat(statistics.getSampledCount()).isEqualTo(2);
assertThat(statistics.getBorrowCount()).isEqualTo(0);
}
@Test
void matches() {
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
}
@Test
void nameNotMatch() {
assertThat(applier.matches("test-service-foo-baz", attributes, resource)).isFalse();
}
@Test
void nullNotMatch() {
assertThat(applier.matches(null, attributes, resource)).isFalse();
}
@Test
void methodNotMatch() {
Attributes attributes =
this.attributes.toBuilder().put(SemanticAttributes.HTTP_METHOD, "POST").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
@Test
void hostNotMatch() {
// Replacing dot with character makes sure we're not accidentally treating dot as regex
// wildcard.
Attributes attributes =
this.attributes.toBuilder().put(SemanticAttributes.HTTP_HOST, "opentelemetryfio").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
@Test
void pathNotMatch() {
Attributes attributes =
this.attributes.toBuilder()
.put(SemanticAttributes.HTTP_TARGET, "/instrument-you")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
@Test
void attributeNotMatch() {
Attributes attributes =
this.attributes.toBuilder().put(AttributeKey.stringKey("animal"), "dog").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
@Test
void attributeMissing() {
Attributes attributes = removeAttribute(this.attributes, AttributeKey.stringKey("animal"));
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
@Test
void serviceTypeNotMatch() {
Resource resource =
this.resource.toBuilder()
.put(
ResourceAttributes.CLOUD_PLATFORM, ResourceAttributes.CloudPlatformValues.AWS_EC2)
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
resource =
Resource.create(
removeAttribute(this.resource.getAttributes(), ResourceAttributes.CLOUD_PLATFORM));
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
@Test
void arnNotMatch() {
Resource resource =
this.resource.toBuilder()
.put(
ResourceAttributes.AWS_ECS_CONTAINER_ARN,
"arn:aws:xray:us-east-1:595986152929:my-service2")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
}
@Nested
@SuppressWarnings("ClassCanBeStatic")
class WildcardMatch {
private final SamplingRuleApplier applier =
new SamplingRuleApplier(CLIENT_ID, readSamplingRule("/sampling-rule-wildcards.json"));
private final Resource resource =
Resource.builder()
.put(ResourceAttributes.CLOUD_PLATFORM, ResourceAttributes.CloudPlatformValues.AWS_EKS)
.put(
ResourceAttributes.AWS_ECS_CONTAINER_ARN,
"arn:aws:xray:us-east-1:595986152929:my-service")
.build();
private final Attributes attributes =
Attributes.builder()
.put(SemanticAttributes.HTTP_METHOD, "GET")
.put(SemanticAttributes.HTTP_HOST, "opentelemetry.io")
.put(SemanticAttributes.HTTP_TARGET, "/instrument-me?foo=bar&cat=meow")
.put(AttributeKey.stringKey("animal"), "cat")
.put(AttributeKey.longKey("speed"), 10)
.build();
// FixedRate set to 0.0 in rule and no reservoir
@Test
void fixedRateNeverSample() {
assertThat(
applier.shouldSample(
Context.current(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList()))
.isEqualTo(SamplingResult.create(SamplingDecision.DROP));
Date now = new Date();
GetSamplingTargetsRequest.SamplingStatisticsDocument statistics = applier.snapshot(now);
assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID);
assertThat(statistics.getRuleName()).isEqualTo("Test");
assertThat(statistics.getTimestamp()).isEqualTo(now);
assertThat(statistics.getRequestCount()).isEqualTo(1);
assertThat(statistics.getSampledCount()).isEqualTo(0);
assertThat(statistics.getBorrowCount()).isEqualTo(0);
// Reset
statistics = applier.snapshot(now);
assertThat(statistics.getRequestCount()).isEqualTo(0);
assertThat(statistics.getSampledCount()).isEqualTo(0);
assertThat(statistics.getBorrowCount()).isEqualTo(0);
applier.shouldSample(
Context.current(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList());
applier.shouldSample(
Context.current(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList());
now = new Date();
statistics = applier.snapshot(now);
assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID);
assertThat(statistics.getRuleName()).isEqualTo("Test");
assertThat(statistics.getTimestamp()).isEqualTo(now);
assertThat(statistics.getRequestCount()).isEqualTo(2);
assertThat(statistics.getSampledCount()).isEqualTo(0);
assertThat(statistics.getBorrowCount()).isEqualTo(0);
}
@Test
void nameMatches() {
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
assertThat(applier.matches("test-service-foo-baz", attributes, resource)).isTrue();
assertThat(applier.matches("test-service-foo-", attributes, resource)).isTrue();
}
@Test
void nameNotMatch() {
assertThat(applier.matches("test-service-foo", attributes, resource)).isFalse();
assertThat(applier.matches("prod-service-foo-bar", attributes, resource)).isFalse();
assertThat(applier.matches(null, attributes, resource)).isFalse();
}
@Test
void methodMatches() {
Attributes attributes =
this.attributes.toBuilder().put(SemanticAttributes.HTTP_METHOD, "BADGETGOOD").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
attributes =
this.attributes.toBuilder().put(SemanticAttributes.HTTP_METHOD, "BADGET").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
attributes =
this.attributes.toBuilder().put(SemanticAttributes.HTTP_METHOD, "GETGET").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
}
@Test
void methodNotMatch() {
Attributes attributes =
this.attributes.toBuilder().put(SemanticAttributes.HTTP_METHOD, "POST").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
attributes = removeAttribute(this.attributes, SemanticAttributes.HTTP_METHOD);
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
@Test
void hostMatches() {
Attributes attributes =
this.attributes.toBuilder()
.put(SemanticAttributes.HTTP_HOST, "alpha.opentelemetry.io")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
attributes =
this.attributes.toBuilder()
.put(SemanticAttributes.HTTP_HOST, "opfdnqtelemetry.io")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
attributes =
this.attributes.toBuilder().put(SemanticAttributes.HTTP_HOST, "opentglemetry.io").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
attributes =
this.attributes.toBuilder().put(SemanticAttributes.HTTP_HOST, "opentglemry.io").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
attributes =
this.attributes.toBuilder().put(SemanticAttributes.HTTP_HOST, "opentglemrz.io").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
}
@Test
void hostNotMatch() {
Attributes attributes =
this.attributes.toBuilder().put(SemanticAttributes.HTTP_HOST, "opentelemetryfio").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
attributes =
this.attributes.toBuilder()
.put(SemanticAttributes.HTTP_HOST, "opentgalemetry.io")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
attributes =
this.attributes.toBuilder()
.put(SemanticAttributes.HTTP_HOST, "alpha.oentelemetry.io")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
attributes = removeAttribute(this.attributes, SemanticAttributes.HTTP_HOST);
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
@Test
void pathMatches() {
Attributes attributes =
this.attributes.toBuilder()
.put(SemanticAttributes.HTTP_TARGET, "/instrument-me?foo=bar&cat=")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
// Deceptive question mark, it's actually a wildcard :-)
attributes =
this.attributes.toBuilder()
.put(SemanticAttributes.HTTP_TARGET, "/instrument-meafoo=bar&cat=")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
}
@Test
void pathNotMatch() {
Attributes attributes =
this.attributes.toBuilder()
.put(SemanticAttributes.HTTP_TARGET, "/instrument-mea?foo=bar&cat=")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
attributes =
this.attributes.toBuilder()
.put(SemanticAttributes.HTTP_TARGET, "foo/instrument-meafoo=bar&cat=")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
attributes = removeAttribute(this.attributes, SemanticAttributes.HTTP_TARGET);
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
@Test
void attributeMatches() {
Attributes attributes =
this.attributes.toBuilder().put(AttributeKey.stringKey("animal"), "catman").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
attributes = this.attributes.toBuilder().put(AttributeKey.longKey("speed"), 20).build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
}
@Test
void attributeNotMatch() {
Attributes attributes =
this.attributes.toBuilder().put(AttributeKey.stringKey("animal"), "dog").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
attributes =
this.attributes.toBuilder().put(AttributeKey.stringKey("animal"), "mancat").build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
attributes = this.attributes.toBuilder().put(AttributeKey.longKey("speed"), 21).build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
@Test
void attributeMissing() {
Attributes attributes = removeAttribute(this.attributes, AttributeKey.stringKey("animal"));
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
@Test
void serviceTypeMatches() {
Resource resource =
this.resource.toBuilder()
.put(
ResourceAttributes.CLOUD_PLATFORM, ResourceAttributes.CloudPlatformValues.AWS_EC2)
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
resource =
Resource.create(
removeAttribute(this.resource.getAttributes(), ResourceAttributes.CLOUD_PLATFORM));
// null matches for pattern '*'
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
}
@Test
void arnMatches() {
Resource resource =
this.resource.toBuilder()
.put(
ResourceAttributes.AWS_ECS_CONTAINER_ARN,
"arn:aws:opentelemetry:us-east-3:52929:my-service")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
}
@Test
void arnNotMatch() {
Resource resource =
this.resource.toBuilder()
.put(
ResourceAttributes.AWS_ECS_CONTAINER_ARN,
"arn:aws:xray:us-east-1:595986152929:my-service2")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
resource =
this.resource.toBuilder()
.put(
ResourceAttributes.AWS_ECS_CONTAINER_ARN,
"frn:aws:xray:us-east-1:595986152929:my-service")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
resource =
Resource.create(
removeAttribute(
this.resource.getAttributes(), ResourceAttributes.AWS_ECS_CONTAINER_ARN));
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
}
@Nested
@SuppressWarnings("ClassCanBeStatic")
class AwsLambdaTest {
private final SamplingRuleApplier applier =
new SamplingRuleApplier(CLIENT_ID, readSamplingRule("/sampling-rule-awslambda.json"));
private final Resource resource =
Resource.builder()
.put(
ResourceAttributes.CLOUD_PLATFORM,
ResourceAttributes.CloudPlatformValues.AWS_LAMBDA)
.put(ResourceAttributes.FAAS_ID, "arn:aws:xray:us-east-1:595986152929:my-service")
.build();
private final Attributes attributes =
Attributes.builder()
.put(SemanticAttributes.HTTP_METHOD, "GET")
.put(SemanticAttributes.HTTP_HOST, "opentelemetry.io")
.put(SemanticAttributes.HTTP_TARGET, "/instrument-me")
.put(AttributeKey.stringKey("animal"), "cat")
.put(AttributeKey.longKey("speed"), 10)
.build();
@Test
void resourceFaasIdMatches() {
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
}
@Test
void spanFaasIdMatches() {
Resource resource =
Resource.create(
removeAttribute(this.resource.getAttributes(), ResourceAttributes.FAAS_ID));
Attributes attributes =
this.attributes.toBuilder()
.put(ResourceAttributes.FAAS_ID, "arn:aws:xray:us-east-1:595986152929:my-service")
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isTrue();
}
@Test
void notLambdaNotMatches() {
Resource resource =
this.resource.toBuilder()
.put(
ResourceAttributes.CLOUD_PLATFORM,
ResourceAttributes.CloudPlatformValues.GCP_CLOUD_FUNCTIONS)
.build();
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
resource =
Resource.create(
removeAttribute(this.resource.getAttributes(), ResourceAttributes.CLOUD_PLATFORM));
assertThat(applier.matches("test-service-foo-bar", attributes, resource)).isFalse();
}
}
@Test
void borrowing() {
SamplingRuleApplier applier =
new SamplingRuleApplier(CLIENT_ID, readSamplingRule("/sampling-rule-reservoir.json"));
// Borrow
assertThat(
applier.shouldSample(
Context.current(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList()))
.isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE));
// Can only borrow one per second. If a second passes between these two lines of code, the test
// will be flaky. Revisit if we ever see it, it's unlikely but can be fixed by injecting a
// a clock.
assertThat(
applier.shouldSample(
Context.current(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList()))
.isEqualTo(SamplingResult.create(SamplingDecision.DROP));
Date now = new Date();
GetSamplingTargetsRequest.SamplingStatisticsDocument statistics = applier.snapshot(now);
assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID);
assertThat(statistics.getRuleName()).isEqualTo("Test");
assertThat(statistics.getTimestamp()).isEqualTo(now);
assertThat(statistics.getRequestCount()).isEqualTo(2);
assertThat(statistics.getSampledCount()).isEqualTo(1);
assertThat(statistics.getBorrowCount()).isEqualTo(1);
// Reset
statistics = applier.snapshot(now);
assertThat(statistics.getRequestCount()).isEqualTo(0);
assertThat(statistics.getSampledCount()).isEqualTo(0);
assertThat(statistics.getBorrowCount()).isEqualTo(0);
AtomicInteger numRequests = new AtomicInteger();
// Wait for reservoir to fill.
await()
.untilAsserted(
() -> {
numRequests.incrementAndGet();
assertThat(
applier.shouldSample(
Context.current(),
TraceId.fromLongs(1, 2),
"span",
SpanKind.CLIENT,
Attributes.empty(),
Collections.emptyList()))
.isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE));
});
now = new Date();
statistics = applier.snapshot(now);
assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID);
assertThat(statistics.getRuleName()).isEqualTo("Test");
assertThat(statistics.getTimestamp()).isEqualTo(now);
assertThat(statistics.getRequestCount()).isEqualTo(numRequests.get());
assertThat(statistics.getSampledCount()).isEqualTo(1);
assertThat(statistics.getBorrowCount()).isEqualTo(1);
}
private static GetSamplingRulesResponse.SamplingRule readSamplingRule(String resourcePath) {
try {
return OBJECT_MAPPER.readValue(
SamplingRuleApplierTest.class.getResource(resourcePath),
GetSamplingRulesResponse.SamplingRule.class);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static Attributes removeAttribute(Attributes attributes, AttributeKey<?> removedKey) {
AttributesBuilder builder = Attributes.builder();
// TODO(anuraaga): Replace with AttributeBuilder.remove
attributes.forEach(
(key, value) -> {
if (!key.equals(removedKey)) {
builder.put((AttributeKey) key, value);
}
});
return builder.build();
}
}

View File

@ -1,189 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry;
import com.google.common.io.ByteStreams;
import com.google.common.io.Resources;
import com.linecorp.armeria.common.AggregatedHttpRequest;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.common.MediaType;
import com.linecorp.armeria.testing.junit5.server.mock.MockWebServerExtension;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.skyscreamer.jsonassert.JSONAssert;
class XraySamplerClientTest {
@RegisterExtension public static MockWebServerExtension server = new MockWebServerExtension();
private XraySamplerClient client;
@BeforeEach
void setUp() {
client = new XraySamplerClient(server.httpUri().toString());
}
@Test
void getSamplingRules() throws Exception {
enqueueResource("/get-sampling-rules-response.json");
GetSamplingRulesResponse response =
client.getSamplingRules(GetSamplingRulesRequest.create("token"));
AggregatedHttpRequest request = server.takeRequest().request();
assertThat(request.path()).isEqualTo("/GetSamplingRules");
assertThat(request.contentType()).isEqualTo(MediaType.JSON);
assertThat(request.contentUtf8()).isEqualTo("{\"NextToken\":\"token\"}");
assertThat(response.getNextToken()).isNull();
assertThat(response.getSamplingRules())
.satisfiesExactly(
rule -> {
assertThat(rule.getCreatedAt()).isEqualTo("2021-06-18T17:28:15+09:00");
assertThat(rule.getModifiedAt()).isEqualTo("2021-06-18T17:28:15+09:00");
assertThat(rule.getRule().getRuleName()).isEqualTo("Test");
assertThat(rule.getRule().getRuleArn())
.isEqualTo("arn:aws:xray:us-east-1:595986152929:sampling-rule/Test");
assertThat(rule.getRule().getResourceArn()).isEqualTo("*");
assertThat(rule.getRule().getPriority()).isEqualTo(1);
assertThat(rule.getRule().getFixedRate()).isEqualTo(0.9);
assertThat(rule.getRule().getReservoirSize()).isEqualTo(1000);
assertThat(rule.getRule().getServiceName()).isEqualTo("test-service-foo-bar");
assertThat(rule.getRule().getServiceType()).isEqualTo("*");
assertThat(rule.getRule().getHost()).isEqualTo("*");
assertThat(rule.getRule().getHttpMethod()).isEqualTo("*");
assertThat(rule.getRule().getUrlPath()).isEqualTo("*");
assertThat(rule.getRule().getVersion()).isEqualTo(1);
assertThat(rule.getRule().getAttributes())
.containsExactly(entry("animal", "cat"), entry("speed", "10"));
},
rule -> {
assertThat(rule.getCreatedAt()).isEqualTo("1970-01-01T09:00:00+09:00");
assertThat(rule.getModifiedAt()).isEqualTo("1970-01-01T09:00:00+09:00");
assertThat(rule.getRule().getRuleName()).isEqualTo("Default");
assertThat(rule.getRule().getRuleArn())
.isEqualTo("arn:aws:xray:us-east-1:595986152929:sampling-rule/Default");
assertThat(rule.getRule().getResourceArn()).isEqualTo("*");
assertThat(rule.getRule().getPriority()).isEqualTo(10000);
assertThat(rule.getRule().getFixedRate()).isEqualTo(0.05);
assertThat(rule.getRule().getReservoirSize()).isEqualTo(1);
assertThat(rule.getRule().getServiceName()).isEqualTo("*");
assertThat(rule.getRule().getServiceType()).isEqualTo("*");
assertThat(rule.getRule().getHost()).isEqualTo("*");
assertThat(rule.getRule().getHttpMethod()).isEqualTo("*");
assertThat(rule.getRule().getUrlPath()).isEqualTo("*");
assertThat(rule.getRule().getVersion()).isEqualTo(1);
assertThat(rule.getRule().getAttributes()).isEmpty();
});
}
@Test
void getSamplingRules_malformed() {
server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.JSON, "notjson"));
assertThatThrownBy(() -> client.getSamplingRules(GetSamplingRulesRequest.create("token")))
.isInstanceOf(UncheckedIOException.class)
.hasMessage("Failed to deserialize response.");
}
@Test
void getSamplingTargets() throws Exception {
// Request and response adapted from
// https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sampling.html
enqueueResource("/get-sampling-targets-response.json");
Date timestamp = Date.from(Instant.parse("2021-06-21T06:46:07Z"));
Date timestamp2 = Date.from(Instant.parse("2018-07-07T00:20:06Z"));
GetSamplingTargetsRequest samplingTargetsRequest =
GetSamplingTargetsRequest.create(
Arrays.asList(
GetSamplingTargetsRequest.SamplingStatisticsDocument.newBuilder()
.setRuleName("Test")
.setClientId("ABCDEF1234567890ABCDEF10")
.setTimestamp(timestamp)
.setRequestCount(110)
.setSampledCount(30)
.setBorrowCount(20)
.build(),
GetSamplingTargetsRequest.SamplingStatisticsDocument.newBuilder()
.setRuleName("polling-scorekeep")
.setClientId("ABCDEF1234567890ABCDEF11")
.setTimestamp(timestamp2)
.setRequestCount(10500)
.setSampledCount(31)
.setBorrowCount(0)
.build()));
GetSamplingTargetsResponse response = client.getSamplingTargets(samplingTargetsRequest);
AggregatedHttpRequest request = server.takeRequest().request();
assertThat(request.path()).isEqualTo("/SamplingTargets");
assertThat(request.contentType()).isEqualTo(MediaType.JSON);
JSONAssert.assertEquals(
Resources.toString(
XraySamplerClientTest.class.getResource("/get-sampling-targets-request.json"),
StandardCharsets.UTF_8),
request.contentUtf8(),
true);
assertThat(response.getLastRuleModification())
.isEqualTo(Date.from(Instant.parse("2018-07-06T23:41:45Z")));
assertThat(response.getDocuments())
.satisfiesExactly(
document -> {
assertThat(document.getRuleName()).isEqualTo("base-scorekeep");
assertThat(document.getFixedRate()).isEqualTo(0.1);
assertThat(document.getReservoirQuota()).isEqualTo(2);
assertThat(document.getReservoirQuotaTtl())
.isEqualTo(Date.from(Instant.parse("2018-07-07T00:25:07Z")));
assertThat(document.getIntervalSecs()).isEqualTo(10);
},
document -> {
assertThat(document.getRuleName()).isEqualTo("polling-scorekeep");
assertThat(document.getFixedRate()).isEqualTo(0.003);
assertThat(document.getReservoirQuota()).isZero();
assertThat(document.getReservoirQuotaTtl()).isNull();
assertThat(document.getIntervalSecs()).isZero();
});
assertThat(response.getUnprocessedStatistics())
.satisfiesExactly(
statistics -> {
assertThat(statistics.getRuleName()).isEqualTo("cats-rule");
assertThat(statistics.getErrorCode()).isEqualTo("400");
assertThat(statistics.getMessage()).isEqualTo("Unknown rule");
});
}
@Test
void getSamplingTargets_malformed() {
server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.JSON, "notjson"));
assertThatThrownBy(
() ->
client.getSamplingTargets(
GetSamplingTargetsRequest.create(Collections.emptyList())))
.isInstanceOf(UncheckedIOException.class)
.hasMessage("Failed to deserialize response.");
}
private static void enqueueResource(String resourcePath) throws Exception {
server.enqueue(
HttpResponse.of(
HttpStatus.OK,
MediaType.JSON_UTF_8,
ByteStreams.toByteArray(
requireNonNull(XraySamplerClientTest.class.getResourceAsStream(resourcePath)))));
}
}