High Dynamic Range (HDR) Histogram for recording and analyzing value distributions with configurable precision across wide dynamic ranges.
—
Recorder classes provide continuous value recording while enabling stable interval histogram snapshots without interrupting active recording operations. They implement a producer-consumer pattern optimal for monitoring and metrics collection scenarios.
Core recorder implementation for integer values using writer-reader phasing to coordinate continuous recording with interval reporting.
public class Recorder implements ValueRecorder, IntervalHistogramProvider<Histogram> {
// Constructors
public Recorder(int numberOfSignificantValueDigits);
public Recorder(int numberOfSignificantValueDigits, boolean packed);
public Recorder(long highestTrackableValue, int numberOfSignificantValueDigits);
public Recorder(long lowestDiscernibleValue,
long highestTrackableValue,
int numberOfSignificantValueDigits);
// Recording methods
void recordValue(long value);
void recordValueWithCount(long value, long count);
void recordValueWithExpectedInterval(long value, long expectedInterval);
void reset();
// Interval histogram methods
Histogram getIntervalHistogram();
Histogram getIntervalHistogram(Histogram histogramToRecycle);
void getIntervalHistogramInto(Histogram targetHistogram);
}Thread-Safe Recording: All recording methods are wait-free and thread-safe for concurrent access from multiple threads.
// Create recorder for latency measurements
Recorder latencyRecorder = new Recorder(3);
// Multiple threads can record concurrently
// Thread 1 - API requests
new Thread(() -> {
while (running) {
long startTime = System.nanoTime();
processApiRequest();
long endTime = System.nanoTime();
latencyRecorder.recordValue((endTime - startTime) / 1000); // Convert to microseconds
}
}).start();
// Thread 2 - Database operations
new Thread(() -> {
while (running) {
long startTime = System.nanoTime();
executeDatabaseQuery();
long endTime = System.nanoTime();
latencyRecorder.recordValue((endTime - startTime) / 1000);
}
}).start();The key feature of recorders is non-blocking interval histogram extraction:
// Recording continues uninterrupted while extracting intervals
ScheduledExecutorService reporter = Executors.newSingleThreadScheduledExecutor();
reporter.scheduleAtFixedRate(() -> {
// Get interval histogram (automatically resets internal state)
Histogram intervalHist = latencyRecorder.getIntervalHistogram();
if (intervalHist.getTotalCount() > 0) {
// Analyze this interval's measurements
double mean = intervalHist.getMean();
long p95 = intervalHist.getValueAtPercentile(95.0);
long p99 = intervalHist.getValueAtPercentile(99.0);
System.out.printf("Interval: count=%d, mean=%.1fμs, P95=%dμs, P99=%dμs%n",
intervalHist.getTotalCount(), mean, p95, p99);
// Send to monitoring system
sendToMonitoring("latency.count", intervalHist.getTotalCount());
sendToMonitoring("latency.mean", mean);
sendToMonitoring("latency.p95", p95);
sendToMonitoring("latency.p99", p99);
}
}, 0, 10, TimeUnit.SECONDS);Recorders support histogram recycling to reduce garbage collection:
Recorder recorder = new Recorder(3);
// Reuse histogram objects to minimize GC pressure
Histogram recycledHistogram = null;
while (true) {
Thread.sleep(5000); // 5-second intervals
// Reuse previous histogram object
Histogram intervalHist = recorder.getIntervalHistogram(recycledHistogram);
if (intervalHist.getTotalCount() > 0) {
processIntervalData(intervalHist);
}
// Keep reference for recycling
recycledHistogram = intervalHist;
}// Basic recorder with auto-resizing
Recorder basic = new Recorder(3);
// Memory-optimized with packed storage
Recorder packed = new Recorder(3, true);
// Fixed range recorder
Recorder fixedRange = new Recorder(1_000_000, 3); // Max 1 million
// Full specification
Recorder fullSpec = new Recorder(1, 10_000_000, 4); // Min 1, max 10M, high precisionOptimized recorder for single-writer scenarios providing better performance when only one thread records values.
public class SingleWriterRecorder extends Recorder {
// Constructors
public SingleWriterRecorder(int numberOfSignificantValueDigits);
public SingleWriterRecorder(int numberOfSignificantValueDigits, boolean packed);
public SingleWriterRecorder(long highestTrackableValue, int numberOfSignificantValueDigits);
public SingleWriterRecorder(long lowestDiscernibleValue,
long highestTrackableValue,
int numberOfSignificantValueDigits);
}Single-Threaded Recording: Optimized for scenarios where only one thread performs measurements.
// Single-writer recorder for main application thread measurements
SingleWriterRecorder mainThreadLatency = new SingleWriterRecorder(3);
// Main application loop (single thread)
public void processRequests() {
while (running) {
Request request = requestQueue.take();
long startTime = System.nanoTime();
processRequest(request);
long endTime = System.nanoTime();
// Only this thread records (optimized path)
mainThreadLatency.recordValue((endTime - startTime) / 1000);
}
}
// Separate monitoring thread extracts intervals
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
monitor.scheduleAtFixedRate(() -> {
Histogram interval = mainThreadLatency.getIntervalHistogram();
analyzeMainThreadPerformance(interval);
}, 0, 30, TimeUnit.SECONDS);SingleWriterRecorder provides:
Recorder implementation for double (floating-point) values.
public class DoubleRecorder implements DoubleValueRecorder, IntervalHistogramProvider<DoubleHistogram> {
// Constructors
public DoubleRecorder(int numberOfSignificantValueDigits);
public DoubleRecorder(long highestToLowestValueRatio, int numberOfSignificantValueDigits);
// Recording methods
void recordValue(double value);
void recordValueWithCount(double value, long count);
void recordValueWithExpectedInterval(double value, double expectedInterval);
void reset();
// Interval histogram methods
DoubleHistogram getIntervalHistogram();
DoubleHistogram getIntervalHistogram(DoubleHistogram histogramToRecycle);
void getIntervalHistogramInto(DoubleHistogram targetHistogram);
}// Create double recorder for response time measurements (in seconds)
DoubleRecorder responseTimeRecorder = new DoubleRecorder(3);
// Record response times from multiple services
public void recordServiceCall(String service, Supplier<String> serviceCall) {
double startTime = System.nanoTime() / 1e9;
try {
String result = serviceCall.get();
} finally {
double endTime = System.nanoTime() / 1e9;
double responseTime = endTime - startTime;
responseTimeRecorder.recordValue(responseTime);
}
}
// Extract interval measurements
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
DoubleHistogram interval = responseTimeRecorder.getIntervalHistogram();
if (interval.getTotalCount() > 0) {
double meanSeconds = interval.getMean();
double p95Seconds = interval.getValueAtPercentile(95.0);
double p99Seconds = interval.getValueAtPercentile(99.0);
System.out.printf("Response times - Mean: %.3fs, P95: %.3fs, P99: %.3fs%n",
meanSeconds, p95Seconds, p99Seconds);
// Check SLA compliance
if (p95Seconds > 2.0) { // P95 should be under 2 seconds
alertingService.sendAlert("P95 response time exceeded SLA: " + p95Seconds + "s");
}
}
}, 0, 60, TimeUnit.SECONDS);DoubleRecorder supports coordinated omission correction for timing measurements:
DoubleRecorder timerRecorder = new DoubleRecorder(3);
// Expected measurement interval
double expectedInterval = 0.100; // 100ms expected intervals
public void performPeriodicMeasurement() {
while (running) {
double startTime = System.nanoTime() / 1e9;
// Measurement that might be delayed by system pauses
double measurement = performActualMeasurement();
// Record with coordinated omission correction
timerRecorder.recordValueWithExpectedInterval(measurement, expectedInterval);
try {
Thread.sleep(100); // Try to maintain 100ms intervals
} catch (InterruptedException e) {
break;
}
}
}Single-writer optimized version of DoubleRecorder.
public class SingleWriterDoubleRecorder extends DoubleRecorder {
// Constructors
public SingleWriterDoubleRecorder(int numberOfSignificantValueDigits);
public SingleWriterDoubleRecorder(long highestToLowestValueRatio, int numberOfSignificantValueDigits);
}// Single-writer double recorder for precise timing measurements
SingleWriterDoubleRecorder precisionTimer = new SingleWriterDoubleRecorder(4); // High precision
// Measurement loop (single thread only)
public void performPrecisionMeasurements() {
while (running) {
double startTime = System.nanoTime() / 1e9;
// Critical operation requiring precise timing
performCriticalOperation();
double endTime = System.nanoTime() / 1e9;
double duration = endTime - startTime;
// Single-threaded optimized recording
precisionTimer.recordValue(duration);
// Maintain measurement cadence
maintainCadence();
}
}Common interface for classes providing interval histogram snapshots.
public interface IntervalHistogramProvider<T extends EncodableHistogram> {
T getIntervalHistogram();
T getIntervalHistogram(T histogramToRecycle);
void getIntervalHistogramInto(T targetHistogram);
}public class MetricsRecordingSystem {
private final Recorder requestLatency = new Recorder(3);
private final Recorder requestSize = new Recorder(2); // Lower precision for size
private final DoubleRecorder errorRate = new DoubleRecorder(2);
public void recordRequest(long latencyMicros, long sizeBytes, boolean success) {
requestLatency.recordValue(latencyMicros);
requestSize.recordValue(sizeBytes);
errorRate.recordValue(success ? 0.0 : 1.0); // 0 for success, 1 for error
}
public void generateIntervalReport() {
Histogram latencyHist = requestLatency.getIntervalHistogram();
Histogram sizeHist = requestSize.getIntervalHistogram();
DoubleHistogram errorHist = errorRate.getIntervalHistogram();
if (latencyHist.getTotalCount() > 0) {
long totalRequests = latencyHist.getTotalCount();
double errorRate = errorHist.getMean(); // Mean of 0s and 1s = error rate
System.out.printf("Interval Report:%n");
System.out.printf(" Total Requests: %d%n", totalRequests);
System.out.printf(" Error Rate: %.2f%%%n", errorRate * 100);
System.out.printf(" Latency P95: %d μs%n", latencyHist.getValueAtPercentile(95.0));
System.out.printf(" Size P95: %d bytes%n", sizeHist.getValueAtPercentile(95.0));
}
}
}public class ServiceMetrics {
private final Map<String, Recorder> serviceRecorders = new ConcurrentHashMap<>();
public void recordServiceCall(String serviceName, long latencyMicros) {
Recorder recorder = serviceRecorders.computeIfAbsent(serviceName,
name -> {
Recorder r = new Recorder(3);
r.setTag("service:" + name);
return r;
});
recorder.recordValue(latencyMicros);
}
public void generateServiceReports() {
serviceRecorders.forEach((serviceName, recorder) -> {
Histogram interval = recorder.getIntervalHistogram();
if (interval.getTotalCount() > 0) {
System.out.printf("Service %s:%n", serviceName);
System.out.printf(" Calls: %d%n", interval.getTotalCount());
System.out.printf(" Mean: %.1f μs%n", interval.getMean());
System.out.printf(" P99: %d μs%n", interval.getValueAtPercentile(99.0));
}
});
}
}public class CoordinatedMetricsSystem {
private final List<Recorder> allRecorders = new ArrayList<>();
private final Object resetLock = new Object();
public Recorder createRecorder(String name, int precision) {
Recorder recorder = new Recorder(precision);
recorder.setTag(name);
synchronized (resetLock) {
allRecorders.add(recorder);
}
return recorder;
}
public Map<String, Histogram> getCoordinatedIntervals() {
Map<String, Histogram> intervals = new HashMap<>();
synchronized (resetLock) {
// Get all intervals atomically
for (Recorder recorder : allRecorders) {
String tag = recorder.getTag();
Histogram interval = recorder.getIntervalHistogram();
intervals.put(tag, interval);
}
}
return intervals;
}
}getIntervalHistogram(histogramToRecycle)All recorder classes provide:
// APM system using recorders
public class APMSystem {
private final Recorder httpRequestLatency = new Recorder(3);
private final Recorder databaseQueryLatency = new Recorder(3);
private final DoubleRecorder cpuUtilization = new DoubleRecorder(2);
// Called by HTTP request interceptors
public void recordHttpRequest(long latencyMicros) {
httpRequestLatency.recordValue(latencyMicros);
}
// Called by database interceptors
public void recordDatabaseQuery(long latencyMicros) {
databaseQueryLatency.recordValue(latencyMicros);
}
// Called by system monitor
public void recordCpuUtilization(double percentage) {
cpuUtilization.recordValue(percentage / 100.0); // Store as 0-1 ratio
}
@Scheduled(fixedRate = 30000) // Every 30 seconds
public void publishMetrics() {
publishHistogram("http.latency", httpRequestLatency.getIntervalHistogram());
publishHistogram("db.latency", databaseQueryLatency.getIntervalHistogram());
publishHistogram("cpu.utilization", cpuUtilization.getIntervalHistogram());
}
}// Load testing with detailed latency recording
public class LoadTester {
private final SingleWriterRecorder latencyRecorder = new SingleWriterRecorder(3);
private volatile boolean testRunning = true;
public void runLoadTest(int durationSeconds, int requestsPerSecond) {
// Start metrics collection
ScheduledExecutorService metricsCollector = Executors.newSingleThreadScheduledExecutor();
metricsCollector.scheduleAtFixedRate(this::collectMetrics, 0, 1, TimeUnit.SECONDS);
// Run load test
long testEndTime = System.currentTimeMillis() + durationSeconds * 1000L;
while (System.currentTimeMillis() < testEndTime) {
long startTime = System.nanoTime();
try {
makeTestRequest();
} catch (Exception e) {
// Record failed requests with high latency value
latencyRecorder.recordValue(Long.MAX_VALUE / 1000); // Error marker
continue;
}
long endTime = System.nanoTime();
latencyRecorder.recordValue((endTime - startTime) / 1000); // Microseconds
// Rate limiting
Thread.sleep(1000 / requestsPerSecond);
}
testRunning = false;
metricsCollector.shutdown();
// Final report
generateFinalReport();
}
private void collectMetrics() {
if (!testRunning) return;
Histogram interval = latencyRecorder.getIntervalHistogram();
if (interval.getTotalCount() > 0) {
System.out.printf("RPS: %d, P50: %d μs, P95: %d μs, P99: %d μs%n",
interval.getTotalCount(),
interval.getValueAtPercentile(50.0),
interval.getValueAtPercentile(95.0),
interval.getValueAtPercentile(99.0));
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-hdrhistogram--hdr-histogram