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      String spanId = spanContextSupplier.getSpanId();
047      String traceId = spanContextSupplier.getTraceId();
048      if (traceId != null && spanId != null) {
049        return new Exemplar(value, timestampMs, SPAN_ID, spanId, TRACE_ID, traceId);
050      }
051    }
052    return null;
053  }
054
055  interface Clock {
056    long currentTimeMillis();
057  }
058
059  static class SystemClock implements Clock {
060    @Override
061    public long currentTimeMillis() {
062      return System.currentTimeMillis();
063    }
064  }
065}