Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,15 @@ final class AggregateEntry extends Hashtable.Entry {

// Mutable aggregate state -- single-thread (consumer/aggregator) writer.
private final Histogram okLatencies = Histogram.newHistogram();
private final Histogram errorLatencies = Histogram.newHistogram();

/**
* Lazily allocated on the first recorded error. Most entries never see an error and keep this
* null for life; {@link SerializingMetricWriter} writes a cached empty-histogram form when null
* to keep the wire payload identical. Once allocated, it survives {@link #clear()} (cleared, not
* nulled) since an entry that errored once tends to error again.
*/
@Nullable private Histogram errorLatencies;

private int errorCount;
private int hitCount;
private int topLevelCount;
Expand Down Expand Up @@ -165,7 +173,7 @@ void recordOneDuration(long tagAndDuration) {
}
if ((tagAndDuration & ERROR_TAG) == ERROR_TAG) {
tagAndDuration ^= ERROR_TAG;
errorLatencies.accept(tagAndDuration);
errorLatenciesForWrite().accept(tagAndDuration);
++errorCount;
} else {
okLatencies.accept(tagAndDuration);
Expand Down Expand Up @@ -193,10 +201,26 @@ Histogram getOkLatencies() {
return okLatencies;
}

/**
* Returns the entry's error-latency histogram, or {@code null} if no error has been recorded.
* Callers serializing this should treat {@code null} as "emit a cached empty histogram"; see
* {@link SerializingMetricWriter}.
*/
@Nullable
Histogram getErrorLatencies() {
return errorLatencies;
}

/** Lazy-allocates {@link #errorLatencies} on the first error. */
private Histogram errorLatenciesForWrite() {
Histogram h = errorLatencies;
if (h == null) {
h = Histogram.newHistogram();
errorLatencies = h;
}
return h;
}

/**
* Resets the per-cycle counters and histograms. Label fields ({@code resource}, {@code service},
* ..., {@code peerTagNames}, {@code peerTagValues}) are deliberately left intact -- they're the
Expand All @@ -210,7 +234,10 @@ void clear() {
this.topLevelCount = 0;
this.duration = 0;
this.okLatencies.clear();
this.errorLatencies.clear();
// errorLatencies stays null on entries that never errored. Only clear if it was allocated.
if (this.errorLatencies != null) {
this.errorLatencies.clear();
}
}

boolean matches(SpanSnapshot s) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,32 @@ public void add(AggregateEntry entry) {
writer.writeBinary(entry.getOkLatencies().serialize());

writer.writeUTF8(ERROR_SUMMARY);
writer.writeBinary(entry.getErrorLatencies().serialize());
final datadog.metrics.api.Histogram errorLatencies = entry.getErrorLatencies();
if (errorLatencies != null) {
writer.writeBinary(errorLatencies.serialize());
} else {
// Entry never saw an error; emit a cached empty-histogram payload so the wire format is
// unchanged without allocating a histogram per entry.
writer.writeBinary(emptyErrorHistogramBytes());
}
}

private byte[] emptyHistogramBytesCache;

/**
* Returns the cached serialized form of an empty histogram. Computed lazily on first call so the
* {@link datadog.metrics.api.Histograms} factory has been registered (by the producer-side tracer
* startup or test setup) before we sample its output.
*/
private byte[] emptyErrorHistogramBytes() {
byte[] cached = emptyHistogramBytesCache;
if (cached == null) {
java.nio.ByteBuffer buf = datadog.metrics.api.Histogram.newHistogram().serialize();
cached = new byte[buf.remaining()];
buf.get(cached);
emptyHistogramBytesCache = cached;
}
return cached;
}

@Override
Expand Down