diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java index a44ecc6aab1..cbe9e54ee38 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java @@ -36,6 +36,26 @@ public final class TraceMapperV0_4 implements TraceMapper { ? new GenerationalUtf8Cache(Config.get().getTagValueUtf8CacheSize()) : null; + // Controls how often the UTF8 caches are recalibrated. The caches adapt to shifts in tag-value + // cardinality over a timescale much longer than a single span, so recalibrating periodically + // rather than per-span preserves their effectiveness without paying recalibrate()'s O(cacheSize) + // cost on every span. Must be a power of two (see the mask in shouldRecalibrate). + static final long RECALIBRATE_SPAN_INTERVAL = 512; + + // True when at least one cache exists. static-final so the JIT can fold the whole recalibrate + // block away when both caches are off. + private static final boolean RECALIBRATE = (TAG_CACHE != null || VALUE_CACHE != null); + + // Advanced by the single serializer thread. The value is a recalibration cadence, not an exact + // count, so a plain counter suffices -- no atomic/volatile needed; a benign race would at most + // nudge when recalibrate fires. + private static long spanCounter; + + /** True once every {@link #RECALIBRATE_SPAN_INTERVAL} spans; advances the span counter. */ + static boolean shouldRecalibrate() { + return RECALIBRATE && (++spanCounter & (RECALIBRATE_SPAN_INTERVAL - 1)) == 0; + } + private final int size; private boolean firstSpanWritten; @@ -68,8 +88,10 @@ MetaWriter forSpan(boolean firstInTrace, boolean lastInTrace, boolean firstInPay @Override public void accept(Metadata metadata) { - if (TAG_CACHE != null) TAG_CACHE.recalibrate(); - if (VALUE_CACHE != null) VALUE_CACHE.recalibrate(); + if (shouldRecalibrate()) { + if (TAG_CACHE != null) TAG_CACHE.recalibrate(); + if (VALUE_CACHE != null) VALUE_CACHE.recalibrate(); + } TagMap tags = metadata.getTags(); diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4RecalibrateTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4RecalibrateTest.java new file mode 100644 index 00000000000..770b7422210 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4RecalibrateTest.java @@ -0,0 +1,31 @@ +package datadog.trace.common.writer.ddagent; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class TraceMapperV0_4RecalibrateTest { + + @Test + void recalibratesOncePerInterval() { + final long interval = TraceMapperV0_4.RECALIBRATE_SPAN_INTERVAL; + + // shouldRecalibrate() advances a shared counter, so assert a property that holds regardless of + // the starting value: any window of 2*interval consecutive spans crosses exactly two interval + // boundaries. + int recalibrations = 0; + for (long i = 0; i < 2 * interval; i++) { + if (TraceMapperV0_4.shouldRecalibrate()) { + recalibrations++; + } + } + assertEquals(2, recalibrations); + } + + @Test + void intervalIsAPowerOfTwo() { + // shouldRecalibrate uses a bit mask, which is only correct for a power-of-two interval. + final long interval = TraceMapperV0_4.RECALIBRATE_SPAN_INTERVAL; + assertEquals(0, interval & (interval - 1), "RECALIBRATE_SPAN_INTERVAL must be a power of two"); + } +}