001package io.prometheus.client.exemplars;
002
003import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier;
004
005/**
006 * Default Exemplar sampler.
007 * <p>
008 * Keeps each Exemplar for a minimum of ~7 seconds, then samples a new one.
009 */
010public class DefaultExemplarSampler implements ExemplarSampler {
011
012  private static final String SPAN_ID = "span_id";
013  private static final String TRACE_ID = "trace_id";
014
015  private final SpanContextSupplier spanContextSupplier;
016  // Choosing a prime number for the retention interval makes behavior more predictable,
017  // because it is unlikely that retention happens at the exact same time as a Prometheus scrape.
018  private final long minRetentionIntervalMs = 7109;
019  private final Clock clock;
020
021  public DefaultExemplarSampler(SpanContextSupplier spanContextSupplier) {
022    this.spanContextSupplier = spanContextSupplier;
023    this.clock = new SystemClock();
024  }
025
026  // for unit tests only
027  DefaultExemplarSampler(SpanContextSupplier spanContextSupplier, Clock clock) {
028    this.spanContextSupplier = spanContextSupplier;
029    this.clock = clock;
030  }
031
032  @Override
033  public Exemplar sample(double increment, Exemplar previous) {
034    return doSample(increment, previous);
035  }
036
037  @Override
038  public Exemplar sample(double value, double bucketFrom, double bucketTo, Exemplar previous) {
039    return doSample(value, previous);
040  }
041
042  private Exemplar doSample(double value, Exemplar previous) {
043    long timestampMs = clock.currentTimeMillis();
044    if ((previous == null || previous.getTimestampMs() == null
045        || timestampMs - previous.getTimestampMs() > minRetentionIntervalMs)
046        && spanContextSupplier.isSampled()) {
047      String spanId = spanContextSupplier.getSpanId();
048      String traceId = spanContextSupplier.getTraceId();
049      if (traceId != null && spanId != null) {
050        return new Exemplar(value, timestampMs, SPAN_ID, spanId, TRACE_ID, traceId);
051      }
052    }
053    return null;
054  }
055
056  interface Clock {
057    long currentTimeMillis();
058  }
059
060  static class SystemClock implements Clock {
061    @Override
062    public long currentTimeMillis() {
063      return System.currentTimeMillis();
064    }
065  }
066}