CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-hdrhistogram--hdr-histogram

High Dynamic Range (HDR) Histogram for recording and analyzing value distributions with configurable precision across wide dynamic ranges.

Pending
Overview
Eval results
Files

recorders.mddocs/

Recorder Classes

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.

Recorder

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);
}

Core Recording Operations

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();

Interval Histogram Extraction

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);

Memory-Efficient Interval Extraction

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;
}

Constructor Options

// 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 precision

SingleWriterRecorder

Optimized 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);
}

Usage Patterns

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);

Performance Benefits

SingleWriterRecorder provides:

  • Lower overhead for recording operations (no inter-thread coordination)
  • Better cache locality (single writer, single reader pattern)
  • Reduced contention (no atomic operations between recording threads)

DoubleRecorder

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);
}

Usage Examples

// 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);

Coordinated Omission Correction

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;
        }
    }
}

SingleWriterDoubleRecorder

Single-writer optimized version of DoubleRecorder.

public class SingleWriterDoubleRecorder extends DoubleRecorder {
    
    // Constructors
    public SingleWriterDoubleRecorder(int numberOfSignificantValueDigits);
    public SingleWriterDoubleRecorder(long highestToLowestValueRatio, int numberOfSignificantValueDigits);
}

Usage Examples

// 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();
    }
}

IntervalHistogramProvider Interface

Common interface for classes providing interval histogram snapshots.

public interface IntervalHistogramProvider<T extends EncodableHistogram> {
    T getIntervalHistogram();
    T getIntervalHistogram(T histogramToRecycle);
    void getIntervalHistogramInto(T targetHistogram);
}

Advanced Recording Patterns

Multi-Metric Recording System

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));
        }
    }
}

Hierarchical Metrics with Tags

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));
            }
        });
    }
}

Coordinated Recording with Reset Synchronization

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;
    }
}

Performance Considerations

Recording Performance

  • Recorder: Excellent multi-threaded recording performance
  • SingleWriterRecorder: Best single-threaded performance
  • DoubleRecorder: Slight overhead for double-to-internal conversion
  • SingleWriterDoubleRecorder: Optimized single-threaded double recording

Memory Efficiency

  • Use histogram recycling with getIntervalHistogram(histogramToRecycle)
  • Consider packed storage for sparse distributions
  • Monitor GC impact of frequent interval extraction

Thread Safety

All recorder classes provide:

  • Wait-free recording from multiple threads
  • Non-blocking interval extraction
  • Proper memory visibility guarantees
  • Coordinated reset behavior

Common Use Cases

Application Performance Monitoring (APM)

// 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 Framework

// 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

docs

concurrent-histograms.md

core-operations.md

double-histograms.md

index.md

iterators.md

recorders.md

serialization.md

specialized-variants.md

utilities.md

tile.json