All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavefront.agent.sampler.SpanSampler Maven / Gradle / Ivy

package com.wavefront.agent.sampler;

import static com.wavefront.internal.SpanDerivedMetricsUtils.DEBUG_SPAN_TAG_VAL;
import static com.wavefront.sdk.common.Constants.DEBUG_TAG_KEY;

import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.wavefront.api.agent.SpanSamplingPolicy;
import com.wavefront.predicates.ExpressionSyntaxException;
import com.wavefront.predicates.Predicates;
import com.wavefront.sdk.entities.tracing.sampling.Sampler;
import com.yammer.metrics.core.Counter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.checkerframework.checker.nullness.qual.NonNull;
import wavefront.report.Annotation;
import wavefront.report.Span;

/**
 * Sampler that takes a {@link Span} as input and delegates to a {@link Sampler} when evaluating the
 * sampling decision.
 *
 * @author Han Zhang ([email protected])
 */
public class SpanSampler {
  public static final String SPAN_SAMPLING_POLICY_TAG = "_sampledByPolicy";
  private static final int EXPIRE_AFTER_ACCESS_SECONDS = 3600;
  private static final int POLICY_BASED_SAMPLING_MOD_FACTOR = 100;
  private static final Logger logger = Logger.getLogger(SpanSampler.class.getCanonicalName());
  private final Sampler delegate;
  private final LoadingCache> spanPredicateCache =
      Caffeine.newBuilder()
          .expireAfterAccess(EXPIRE_AFTER_ACCESS_SECONDS, TimeUnit.SECONDS)
          .build(
              new CacheLoader>() {
                @Override
                @Nullable
                public Predicate load(@NonNull String key) {
                  try {
                    return Predicates.fromPredicateEvalExpression(key);
                  } catch (ExpressionSyntaxException ex) {
                    logger.severe("Policy expression " + key + " is invalid: " + ex.getMessage());
                    return null;
                  }
                }
              });
  private final Supplier> activeSpanSamplingPoliciesSupplier;

  /**
   * Creates a new instance from a {@Sampler} delegate.
   *
   * @param delegate The delegate {@Sampler}.
   * @param activeSpanSamplingPoliciesSupplier Active span sampling policies to be applied.
   */
  public SpanSampler(
      Sampler delegate,
      @Nonnull Supplier> activeSpanSamplingPoliciesSupplier) {
    this.delegate = delegate;
    this.activeSpanSamplingPoliciesSupplier = activeSpanSamplingPoliciesSupplier;
  }

  /**
   * Evaluates whether a span should be allowed or discarded.
   *
   * @param span The span to sample.
   * @return true if the span should be allowed, false otherwise.
   */
  public boolean sample(Span span) {
    return sample(span, null);
  }

  /**
   * Evaluates whether a span should be allowed or discarded, and increment a counter if it should
   * be discarded.
   *
   * @param span The span to sample.
   * @param discarded The counter to increment if the decision is to discard the span.
   * @return true if the span should be allowed, false otherwise.
   */
  public boolean sample(Span span, @Nullable Counter discarded) {
    if (isForceSampled(span)) {
      return true;
    }
    // Policy based span sampling
    List activeSpanSamplingPolicies = activeSpanSamplingPoliciesSupplier.get();
    if (activeSpanSamplingPolicies != null) {
      int samplingPercent = 0;
      String policyId = null;
      for (SpanSamplingPolicy policy : activeSpanSamplingPolicies) {
        Predicate spanPredicate = spanPredicateCache.get(policy.getExpression());
        if (spanPredicate != null
            && spanPredicate.test(span)
            && policy.getSamplingPercent() > samplingPercent) {
          samplingPercent = policy.getSamplingPercent();
          policyId = policy.getPolicyId();
        }
      }
      if (samplingPercent > 0
          && Math.abs(UUID.fromString(span.getTraceId()).getLeastSignificantBits())
                  % POLICY_BASED_SAMPLING_MOD_FACTOR
              <= samplingPercent) {
        if (span.getAnnotations() == null) {
          span.setAnnotations(new ArrayList<>());
        }
        span.getAnnotations().add(new Annotation(SPAN_SAMPLING_POLICY_TAG, policyId));
        return true;
      }
    }
    if (delegate.sample(
        span.getName(),
        UUID.fromString(span.getTraceId()).getLeastSignificantBits(),
        span.getDuration())) {
      return true;
    }
    if (discarded != null) {
      discarded.inc();
    }
    return false;
  }

  /**
   * Util method to determine if a span is force sampled. Currently force samples if any of the
   * below conditions are met. 1. The span annotation debug=true is present 2.
   * alwaysSampleErrors=true and the span annotation error=true is present.
   *
   * @param span The span to sample
   * @return true if the span should be force sampled.
   */
  private boolean isForceSampled(Span span) {
    List annotations = span.getAnnotations();
    for (Annotation annotation : annotations) {
      if (DEBUG_TAG_KEY.equals(annotation.getKey())) {
        if (annotation.getValue().equals(DEBUG_SPAN_TAG_VAL)) {
          return true;
        }
      }
    }
    return false;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy