Skip to content

Commit b266e2d

Browse files
bm1549devflow.devflow-routing-intake
andauthored
Fix _dd.p.ksr formatting to use 6 decimal places instead of 6 significant figures (#11068)
Fix _dd.p.ksr formatting to use 6 decimal places instead of 6 significant figures The system-tests (Test_Knuth_Sample_Rate) expect the KSR tag to be formatted with up to 6 decimal digits of precision, not 6 significant figures. The previous implementation used %.6g semantics which produced scientific notation for values < 1e-4 (e.g. "1e-06" for 0.000001). The corrected format rounds to 6 decimal places: - 0.000001 -> "0.000001" (previously "1e-06") - 0.0000001 -> "0" (previously "1e-07"; below precision) - 0.00000051 -> "0.000001" (previously scientific; rounds up) This also simplifies the implementation: the variable-scale multiplier and formatScientific6g helper are replaced by a single fixed multiplier of 1e6 (always 6 decimal places). Unit tests updated to reflect the corrected expected values, and new tests added that directly mirror the three previously-failing system-test parametric cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent 68a2e63 commit b266e2d

File tree

3 files changed

+35
-123
lines changed

3 files changed

+35
-123
lines changed

dd-trace-core/src/jmh/java/datadog/trace/core/propagation/ptags/KnuthSamplingRateFormatBenchmark.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
/**
2323
* Benchmarks for formatting the Knuth sampling rate (_dd.p.ksr tag value).
2424
*
25-
* <p>The format requirement is %.6g semantics: 6 significant figures, no trailing zeros, using
26-
* fixed notation for values in [1e-4, 1] and scientific notation for smaller values.
25+
* <p>The format requirement is 6 decimal digits of precision, no trailing zeros (fixed notation
26+
* only; values below 0.0000005 round to "0").
2727
*
2828
* <p>Run with:
2929
*
@@ -94,10 +94,12 @@ public void updateRateFreshTrace(Blackhole bh) {
9494
bh.consume(ptags.getKnuthSamplingRateTagValue());
9595
}
9696

97-
// ---- old implementation for comparison ----
97+
// ---- old implementation for comparison (%.6f with trailing zero removal) ----
9898

9999
static String stringFormatImpl(double rate) {
100-
String formatted = String.format(Locale.ROOT, "%.6g", rate);
100+
if (rate <= 0.0) return "0";
101+
if (rate >= 1.0) return "1";
102+
String formatted = String.format(Locale.ROOT, "%.6f", rate);
101103
int dotIndex = formatted.indexOf('.');
102104
if (dotIndex >= 0) {
103105
int end = formatted.length();
@@ -109,6 +111,6 @@ static String stringFormatImpl(double rate) {
109111
}
110112
formatted = formatted.substring(0, end);
111113
}
112-
return formatted;
114+
return "0".equals(formatted) ? "0" : formatted;
113115
}
114116
}

dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java

Lines changed: 10 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -298,57 +298,28 @@ public void updateKnuthSamplingRate(double rate) {
298298
}
299299

300300
/**
301-
* Formats a sampling rate with up to 6 significant digits and no trailing zeros, matching
302-
* {@code %.6g} semantics (fixed notation for values in [1e-4, 1], scientific for smaller).
301+
* Formats a sampling rate with up to 6 decimal digits of precision and no trailing zeros.
302+
*
303+
* <p>Values below 0.0000005 (which round to zero at 6 decimal places) return {@code "0"}.
304+
* Values at or above 0.9999995 return {@code "1"}.
303305
*
304306
* <p>Uses char-array arithmetic to avoid {@link java.util.Formatter} allocations entirely.
305307
*/
306308
static String formatKnuthSamplingRate(double rate) {
307309
if (rate <= 0.0) return "0";
308310
if (rate >= 1.0) return "1";
309311

310-
if (rate < 1e-4) {
311-
return formatScientific6g(rate);
312-
}
313-
314-
return formatFixed6g(rate);
315-
}
316-
317-
/** Fixed notation for rates in [1e-4, 1): "0.DDDDDDDDD" with trailing zeros trimmed. */
318-
private static String formatFixed6g(double rate) {
319-
// Choose a multiplier so Math.round(rate * multiplier) is a 6-significant-figure integer.
320-
// For rate in [10^-k, 10^-(k-1)) the first sig fig is at decimal position k, so we need
321-
// k+5 total fractional digits:
322-
// [0.1, 1.0) -> scale=6, multiplier=1e6
323-
// [0.01, 0.1) -> scale=7, multiplier=1e7
324-
// [0.001, 0.01) -> scale=8, multiplier=1e8
325-
// [1e-4, 0.001) -> scale=9, multiplier=1e9
326-
final int scale;
327-
final long multiplier;
328-
if (rate >= 0.1) {
329-
scale = 6;
330-
multiplier = 1_000_000L;
331-
} else if (rate >= 0.01) {
332-
scale = 7;
333-
multiplier = 10_000_000L;
334-
} else if (rate >= 0.001) {
335-
scale = 8;
336-
multiplier = 100_000_000L;
337-
} else {
338-
scale = 9;
339-
multiplier = 1_000_000_000L;
340-
}
341-
342-
long rounded = Math.round(rate * multiplier);
312+
// Round to 6 decimal places.
313+
long rounded = Math.round(rate * 1_000_000L);
343314
if (rounded == 0) return "0";
344-
if (rounded >= multiplier) return "1"; // rounding pushed value to 1.0
315+
if (rounded >= 1_000_000L) return "1";
345316

346-
// Build "0." + <scale digits> and trim trailing zeros in a single right-to-left pass.
347-
char[] buf = new char[2 + scale];
317+
// Build "0.DDDDDD" and trim trailing zeros in a single right-to-left pass.
318+
char[] buf = new char[8]; // "0." + 6 digits
348319
buf[0] = '0';
349320
buf[1] = '.';
350321
int end = 2; // exclusive end; updated on first non-zero digit found from the right
351-
for (int i = 2 + scale - 1; i >= 2; i--) {
322+
for (int i = 7; i >= 2; i--) {
352323
int d = (int) (rounded % 10);
353324
rounded /= 10;
354325
buf[i] = (char) ('0' + d);
@@ -360,72 +331,6 @@ private static String formatFixed6g(double rate) {
360331
return new String(buf, 0, end);
361332
}
362333

363-
/** Scientific notation for rates below 1e-4: "X.XXXXXe-YY" with mantissa zeros trimmed. */
364-
private static String formatScientific6g(double rate) {
365-
// Normalize to [1, 10) by repeated multiply — at most ~15 iterations for realistic rates.
366-
int exp = 0;
367-
double normalized = rate;
368-
while (normalized < 1.0) {
369-
normalized *= 10;
370-
exp--;
371-
}
372-
373-
// Round mantissa to 6 significant figures (integer in [100000, 999999]).
374-
long sig = Math.round(normalized * 100000.0);
375-
if (sig >= 1000000) {
376-
sig /= 10;
377-
exp++;
378-
if (exp >= -4) {
379-
// Rounding pushed the value into fixed-notation range (always exactly 0.0001).
380-
return "0.0001";
381-
}
382-
}
383-
384-
// Build "X.XXXXXe-YY" trimming mantissa trailing zeros.
385-
// Max: "X.XXXXXe-XXX" = 13 chars.
386-
char[] buf = new char[13];
387-
int pos = 0;
388-
389-
// Integer part (always a single digit 1-9).
390-
buf[pos++] = (char) ('0' + (int) (sig / 100000));
391-
sig %= 100000;
392-
393-
// Fractional part (5 digits, trim trailing zeros).
394-
if (sig > 0) {
395-
buf[pos++] = '.';
396-
char[] frac = new char[5];
397-
int fracEnd = 0;
398-
for (int i = 4; i >= 0; i--) {
399-
frac[i] = (char) ('0' + (int) (sig % 10));
400-
sig /= 10;
401-
if (frac[i] != '0' && fracEnd == 0) {
402-
fracEnd = i + 1;
403-
}
404-
}
405-
for (int i = 0; i < fracEnd; i++) {
406-
buf[pos++] = frac[i];
407-
}
408-
}
409-
410-
// Exponent: "e-YY" (always negative here, at least 2 digits).
411-
buf[pos++] = 'e';
412-
buf[pos++] = '-';
413-
int absExp = -exp;
414-
if (absExp < 10) {
415-
buf[pos++] = '0';
416-
buf[pos++] = (char) ('0' + absExp);
417-
} else if (absExp < 100) {
418-
buf[pos++] = (char) ('0' + absExp / 10);
419-
buf[pos++] = (char) ('0' + absExp % 10);
420-
} else {
421-
buf[pos++] = (char) ('0' + absExp / 100);
422-
buf[pos++] = (char) ('0' + (absExp / 10) % 10);
423-
buf[pos++] = (char) ('0' + absExp % 10);
424-
}
425-
426-
return new String(buf, 0, pos);
427-
}
428-
429334
TagValue getKnuthSamplingRateTagValue() {
430335
return knuthSamplingRateTagValue;
431336
}

dd-trace-core/src/test/groovy/datadog/trace/core/KnuthSamplingRateTest.groovy

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,30 @@ class KnuthSamplingRateTest extends DDCoreSpecification {
3939
0.123456d | "0.123456"
4040
0.100000d | "0.1"
4141
0.250d | "0.25"
42-
// [0.01, 0.1) magnitude bucket (scale=7)
4342
0.05d | "0.05"
44-
0.0123456789d | "0.0123457"
45-
// [0.001, 0.01) magnitude bucket (scale=8)
43+
// 6 decimal places: round(0.0123456789 * 1e6) = round(12345.6789) = 12346
44+
0.0123456789d | "0.012346"
4645
0.001d | "0.001"
4746
0.00500d | "0.005"
48-
0.00123456789d | "0.00123457"
49-
// [0.0001, 0.001) magnitude bucket (scale=9)
47+
// 6 decimal places: round(0.00123456789 * 1e6) = round(1234.56789) = 1235
48+
0.00123456789d | "0.001235"
5049
0.0001d | "0.0001"
5150
0.000500d | "0.0005"
52-
0.000123456789d | "0.000123457"
53-
// rounding boundary: 0.9999995 rounds up to 1.0
51+
// 6 decimal places: round(0.000123456789 * 1e6) = round(123.456789) = 123
52+
0.000123456789d | "0.000123"
53+
// rounding boundary: round(0.9999995 * 1e6) = round(999999.5) = 1000000 >= 1e6 -> "1"
5454
0.9999995d | "1"
55-
// scientific notation (rate < 1e-4)
56-
0.00001d | "1e-05"
57-
0.000050d | "5e-05"
58-
1.23456789e-5d | "1.23457e-05"
59-
1e-7d | "1e-07"
60-
5.5e-10d | "5.5e-10"
55+
// values in (0, 1e-4): fixed 6 decimal places, no scientific notation
56+
0.00001d | "0.00001"
57+
0.000050d | "0.00005"
58+
// round(1.23456789e-5 * 1e6) = round(12.3456789) = 12
59+
1.23456789e-5d | "0.000012"
60+
// below 6-decimal-place precision: round to 0
61+
1e-7d | "0"
62+
5.5e-10d | "0"
63+
// system-tests Test_Knuth_Sample_Rate boundary cases
64+
0.000001d | "0.000001" // six_decimal_precision_boundary
65+
0.00000051d | "0.000001" // rounds_up_to_one_millionth
6166
}
6267

6368
def "agent rate sampler sets ksr propagated tag"() {

0 commit comments

Comments
 (0)