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:
parent
b5d202a08c
commit
d8999911fc
|
|
@ -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.
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 + "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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) + "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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)))));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue