Latency Histogram Generator in Java

1. Core Histogram Classes

// LatencyHistogram.java
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
public class LatencyHistogram {
private final String name;
private final Map<Long, AtomicLong> buckets;
private final AtomicLong totalCount;
private final AtomicLong totalSum;
private final AtomicLong minValue;
private final AtomicLong maxValue;
private final long[] predefinedBuckets;
private final boolean dynamicBuckets;
// Predefined bucket boundaries for common use cases
public static final long[] MICROSECOND_BUCKETS = {
1, 2, 5, 10, 25, 50, 75, 100, 250, 500, 750,
1000, 2500, 5000, 7500, 10000, Long.MAX_VALUE
};
public static final long[] MILLISECOND_BUCKETS = {
1, 2, 5, 10, 25, 50, 75, 100, 250, 500, 750,
1000, 2500, 5000, 7500, 10000, 30000, 60000, Long.MAX_VALUE
};
public static final long[] SECOND_BUCKETS = {
1, 2, 5, 10, 30, 60, 120, 300, 600, 1800, 3600, Long.MAX_VALUE
};
public LatencyHistogram(String name) {
this(name, MICROSECOND_BUCKETS, false);
}
public LatencyHistogram(String name, long[] bucketBoundaries) {
this(name, bucketBoundaries, false);
}
public LatencyHistogram(String name, long[] bucketBoundaries, boolean dynamicBuckets) {
this.name = name;
this.predefinedBuckets = bucketBoundaries != null ? bucketBoundaries.clone() : MICROSECOND_BUCKETS;
this.dynamicBuckets = dynamicBuckets;
if (dynamicBuckets) {
this.buckets = new ConcurrentSkipListMap<>();
} else {
this.buckets = new ConcurrentHashMap<>();
// Pre-initialize buckets for fixed histogram
for (long boundary : predefinedBuckets) {
buckets.put(boundary, new AtomicLong(0));
}
}
this.totalCount = new AtomicLong(0);
this.totalSum = new AtomicLong(0);
this.minValue = new AtomicLong(Long.MAX_VALUE);
this.maxValue = new AtomicLong(Long.MIN_VALUE);
}
public void record(long value) {
if (value < 0) {
throw new IllegalArgumentException("Latency value cannot be negative: " + value);
}
long bucketKey = findBucket(value);
buckets.computeIfAbsent(bucketKey, k -> new AtomicLong(0)).incrementAndGet();
totalCount.incrementAndGet();
totalSum.addAndGet(value);
// Update min value
long currentMin;
do {
currentMin = minValue.get();
} while (value < currentMin && !minValue.compareAndSet(currentMin, value));
// Update max value
long currentMax;
do {
currentMax = maxValue.get();
} while (value > currentMax && !maxValue.compareAndSet(currentMax, value));
}
public void record(long value, TimeUnit unit) {
record(unit.toNanos(value));
}
private long findBucket(long value) {
if (dynamicBuckets) {
// For dynamic buckets, use the value itself as bucket key
// In practice, you might want to round to nearest power of 2 or similar
return value;
} else {
// For fixed buckets, find the appropriate predefined bucket
for (long boundary : predefinedBuckets) {
if (value <= boundary) {
return boundary;
}
}
return predefinedBuckets[predefinedBuckets.length - 1];
}
}
public HistogramSnapshot getSnapshot() {
return new HistogramSnapshot(this);
}
public void reset() {
buckets.clear();
if (!dynamicBuckets) {
for (long boundary : predefinedBuckets) {
buckets.put(boundary, new AtomicLong(0));
}
}
totalCount.set(0);
totalSum.set(0);
minValue.set(Long.MAX_VALUE);
maxValue.set(Long.MIN_VALUE);
}
// Getters
public String getName() { return name; }
public long getTotalCount() { return totalCount.get(); }
public long getTotalSum() { return totalSum.get(); }
public long getMinValue() { return minValue.get() == Long.MAX_VALUE ? 0 : minValue.get(); }
public long getMaxValue() { return maxValue.get() == Long.MIN_VALUE ? 0 : maxValue.get(); }
public double getAverage() { 
long count = totalCount.get();
return count == 0 ? 0.0 : (double) totalSum.get() / count; 
}
public Map<Long, Long> getBucketCounts() {
return buckets.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().get()
));
}
public static class HistogramSnapshot {
private final String name;
private final long timestamp;
private final long totalCount;
private final long totalSum;
private final long minValue;
private final long maxValue;
private final double average;
private final Map<Long, Long> bucketCounts;
private final Map<Long, Double> percentiles;
public HistogramSnapshot(LatencyHistogram histogram) {
this.name = histogram.name;
this.timestamp = System.currentTimeMillis();
this.totalCount = histogram.getTotalCount();
this.totalSum = histogram.getTotalSum();
this.minValue = histogram.getMinValue();
this.maxValue = histogram.getMaxValue();
this.average = histogram.getAverage();
this.bucketCounts = new TreeMap<>(histogram.getBucketCounts());
this.percentiles = calculatePercentiles(histogram);
}
private Map<Long, Double> calculatePercentiles(LatencyHistogram histogram) {
Map<Long, Double> result = new HashMap<>();
if (totalCount == 0) return result;
// For simplicity, we'll calculate approximate percentiles from buckets
// In a production system, you might want to store individual values or use HDRHistogram
long[] percentiles = {50, 75, 90, 95, 99, 99.9, 99.99, 99.999};
for (long percentile : percentiles) {
result.put(percentile, calculatePercentile(histogram, percentile / 100.0));
}
return result;
}
private double calculatePercentile(LatencyHistogram histogram, double percentile) {
// Simplified percentile calculation from buckets
// This is approximate and works better with more granular buckets
long targetCount = (long) (totalCount * percentile);
long accumulatedCount = 0;
for (Map.Entry<Long, Long> entry : bucketCounts.entrySet()) {
accumulatedCount += entry.getValue();
if (accumulatedCount >= targetCount) {
return entry.getKey();
}
}
return maxValue;
}
// Getters
public String getName() { return name; }
public long getTimestamp() { return timestamp; }
public long getTotalCount() { return totalCount; }
public long getTotalSum() { return totalSum; }
public long getMinValue() { return minValue; }
public long getMaxValue() { return maxValue; }
public double getAverage() { return average; }
public Map<Long, Long> getBucketCounts() { return Collections.unmodifiableMap(bucketCounts); }
public Map<Long, Double> getPercentiles() { return Collections.unmodifiableMap(percentiles); }
public double getPercentile(double percentile) {
return percentiles.getOrDefault((long)(percentile * 100), 0.0);
}
@Override
public String toString() {
return String.format(
"HistogramSnapshot{name='%s', count=%,d, min=%,d, max=%,d, avg=%.2f, p95=%.2f, p99=%.2f}",
name, totalCount, minValue, maxValue, average, 
getPercentile(0.95), getPercentile(0.99)
);
}
}
}

2. High-Performance HDR Histogram Implementation

// HDRLatencyHistogram.java
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLongArray;
/**
* High Dynamic Range (HDR) Histogram for accurate latency measurement
* Based on the HDR Histogram concept by Gil Tene
*/
public class HDRLatencyHistogram {
private final String name;
private final int significantDigits;
private final long lowestDiscernibleValue;
private final long highestTrackableValue;
private final int bucketCount;
private final int subBucketCount;
private final int subBucketHalfCount;
private final int unitMagnitude;
private final int subBucketMagnitude;
private final AtomicLongArray counts;
private final long[] ranges;
private final AtomicLong totalCount;
private final AtomicLong totalSum;
private static final long MAX_SUB_BUCKET_COUNT = 1 << 17; // 131072
public HDRLatencyHistogram(String name, int significantDigits) {
this(name, 1, 3600_000_000_000L, significantDigits); // 1 nanosecond to 1 hour
}
public HDRLatencyHistogram(String name, long lowestDiscernibleValue, 
long highestTrackableValue, int significantDigits) {
this.name = name;
this.significantDigits = Math.max(1, Math.min(5, significantDigits));
this.lowestDiscernibleValue = Math.max(1, lowestDiscernibleValue);
this.highestTrackableValue = Math.max(2 * lowestDiscernibleValue, highestTrackableValue);
// Calculate unit magnitude
this.unitMagnitude = (int) Math.floor(Math.log(this.lowestDiscernibleValue) / Math.log(2));
// Calculate needed sub-bucket magnitude to ensure desired precision
long largestValueWithSingleUnitResolution = 2L * (long) Math.pow(10, significantDigits);
int subBucketMagnitude = (int) Math.ceil(Math.log(largestValueWithSingleUnitResolution) / Math.log(2));
this.subBucketMagnitude = Math.max(0, subBucketMagnitude - unitMagnitude);
this.subBucketCount = (int) Math.pow(2, this.subBucketMagnitude + 1);
this.subBucketHalfCount = subBucketCount / 2;
// Calculate bucket count needed
long trackableValue = this.highestTrackableValue;
this.bucketCount = 1;
while (trackableValue > (1L << (unitMagnitude + subBucketMagnitude + bucketCount - 1))) {
bucketCount++;
}
bucketCount++; // Extra bucket for overflow
// Initialize counts array
int arrayLength = bucketCount * subBucketCount;
this.counts = new AtomicLongArray(arrayLength);
this.ranges = new long[arrayLength];
initializeRanges();
this.totalCount = new AtomicLong(0);
this.totalSum = new AtomicLong(0);
}
private void initializeRanges() {
for (int bucketIndex = 0; bucketIndex < bucketCount; bucketIndex++) {
for (int subBucketIndex = 0; subBucketIndex < subBucketCount; subBucketIndex++) {
int index = getIndex(bucketIndex, subBucketIndex);
ranges[index] = valueFromIndex(bucketIndex, subBucketIndex);
}
}
}
public void recordValue(long value) {
if (value < 0) {
throw new IllegalArgumentException("Value cannot be negative: " + value);
}
int countsIndex = countsIndexFor(value);
if (countsIndex < 0 || countsIndex >= counts.length()) {
// Value is outside trackable range, record in overflow if available
return;
}
counts.incrementAndGet(countsIndex);
totalCount.incrementAndGet();
totalSum.addAndGet(value);
}
private int countsIndexFor(long value) {
if (value < 0) return -1;
int bucketIndex = getBucketIndex(value);
int subBucketIndex = getSubBucketIndex(value, bucketIndex);
return getIndex(bucketIndex, subBucketIndex);
}
private int getBucketIndex(long value) {
long leadingZeros = Long.numberOfLeadingZeros(value);
int shiftAmount = unitMagnitude + subBucketMagnitude + 1;
return (64 - shiftAmount) - leadingZeros;
}
private int getSubBucketIndex(long value, int bucketIndex) {
if (bucketIndex < 0) return 0;
long shiftedValue = value >>> (unitMagnitude + bucketIndex);
int subBucketIndex = (int) shiftedValue;
if (subBucketIndex >= subBucketCount) {
subBucketIndex = subBucketCount - 1;
}
return subBucketIndex;
}
private int getIndex(int bucketIndex, int subBucketIndex) {
return (bucketIndex * subBucketCount) + subBucketIndex;
}
private long valueFromIndex(int bucketIndex, int subBucketIndex) {
return ((long) subBucketIndex) << (unitMagnitude + bucketIndex);
}
public HDRSnapshot getSnapshot() {
return new HDRSnapshot(this);
}
public void reset() {
for (int i = 0; i < counts.length(); i++) {
counts.set(i, 0);
}
totalCount.set(0);
totalSum.set(0);
}
public static class HDRSnapshot {
private final String name;
private final long timestamp;
private final long totalCount;
private final long totalSum;
private final double mean;
private final Map<Double, Long> percentiles;
public HDRSnapshot(HDRLatencyHistogram histogram) {
this.name = histogram.name;
this.timestamp = System.currentTimeMillis();
this.totalCount = histogram.totalCount.get();
this.totalSum = histogram.totalSum.get();
this.mean = totalCount > 0 ? (double) totalSum / totalCount : 0.0;
this.percentiles = calculatePercentiles(histogram);
}
private Map<Double, Long> calculatePercentiles(HDRLatencyHistogram histogram) {
Map<Double, Long> result = new TreeMap<>();
if (totalCount == 0) return result;
double[] percentiles = {0.5, 0.75, 0.9, 0.95, 0.99, 0.999, 0.9999};
for (double percentile : percentiles) {
result.put(percentile, getValueAtPercentile(histogram, percentile));
}
return result;
}
private long getValueAtPercentile(HDRLatencyHistogram histogram, double percentile) {
long targetCount = (long) Math.ceil((percentile * totalCount) / 100.0);
long accumulatedCount = 0;
for (int i = 0; i < histogram.counts.length(); i++) {
accumulatedCount += histogram.counts.get(i);
if (accumulatedCount >= targetCount) {
return histogram.ranges[i];
}
}
return histogram.ranges[histogram.ranges.length - 1];
}
// Getters
public String getName() { return name; }
public long getTimestamp() { return timestamp; }
public long getTotalCount() { return totalCount; }
public double getMean() { return mean; }
public Map<Double, Long> getPercentiles() { return Collections.unmodifiableMap(percentiles); }
public long getPercentile(double percentile) {
return percentiles.getOrDefault(percentile, 0L);
}
@Override
public String toString() {
return String.format(
"HDRSnapshot{name='%s', count=%,d, mean=%.2f, p50=%,d, p95=%,d, p99=%,d}",
name, totalCount, mean, getPercentile(0.5), getPercentile(0.95), getPercentile(0.99)
);
}
}
}

3. Latency Registry and Management

// LatencyRegistry.java
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class LatencyRegistry {
private static final LatencyRegistry INSTANCE = new LatencyRegistry();
private final Map<String, LatencyHistogram> histograms;
private final Map<String, HDRLatencyHistogram> hdrHistograms;
private final ScheduledExecutorService scheduler;
private final List<LatencyReporter> reporters;
private final AtomicReference<RegistryConfig> config;
private LatencyRegistry() {
this.histograms = new ConcurrentHashMap<>();
this.hdrHistograms = new ConcurrentHashMap<>();
this.scheduler = Executors.newScheduledThreadPool(2);
this.reporters = new ArrayList<>();
this.config = new AtomicReference<>(new RegistryConfig());
// Start periodic reporting
startPeriodicReporting();
}
public static LatencyRegistry getInstance() {
return INSTANCE;
}
public LatencyHistogram getHistogram(String name) {
return histograms.computeIfAbsent(name, 
k -> new LatencyHistogram(name, LatencyHistogram.MICROSECOND_BUCKETS));
}
public LatencyHistogram getHistogram(String name, long[] buckets) {
return histograms.computeIfAbsent(name, 
k -> new LatencyHistogram(name, buckets));
}
public HDRLatencyHistogram getHDRHistogram(String name) {
return hdrHistograms.computeIfAbsent(name,
k -> new HDRLatencyHistogram(name, 3)); // 3 significant digits
}
public HDRLatencyHistogram getHDRHistogram(String name, int significantDigits) {
return hdrHistograms.computeIfAbsent(name,
k -> new HDRLatencyHistogram(name, significantDigits));
}
public void recordLatency(String histogramName, long value, TimeUnit unit) {
LatencyHistogram histogram = getHistogram(histogramName);
histogram.record(value, unit);
// Also record in HDR histogram for more accurate percentiles
HDRLatencyHistogram hdrHistogram = getHDRHistogram(histogramName + ".hdr");
hdrHistogram.recordValue(unit.toNanos(value));
}
public void recordLatency(String histogramName, long value) {
recordLatency(histogramName, value, TimeUnit.NANOSECONDS);
}
public void addReporter(LatencyReporter reporter) {
reporters.add(reporter);
}
public void removeReporter(LatencyReporter reporter) {
reporters.remove(reporter);
}
private void startPeriodicReporting() {
scheduler.scheduleAtFixedRate(() -> {
try {
generateReports();
} catch (Exception e) {
System.err.println("Error generating latency reports: " + e.getMessage());
}
}, config.get().reportIntervalSeconds, config.get().reportIntervalSeconds, TimeUnit.SECONDS);
// Schedule histogram rotation for sliding window analysis
scheduler.scheduleAtFixedRate(() -> {
try {
rotateHistograms();
} catch (Exception e) {
System.err.println("Error rotating histograms: " + e.getMessage());
}
}, config.get().rotationIntervalMinutes, config.get().rotationIntervalMinutes, TimeUnit.MINUTES);
}
private void generateReports() {
Map<String, LatencyHistogram.HistogramSnapshot> snapshots = new HashMap<>();
Map<String, HDRLatencyHistogram.HDRSnapshot> hdrSnapshots = new HashMap<>();
// Collect snapshots
for (Map.Entry<String, LatencyHistogram> entry : histograms.entrySet()) {
snapshots.put(entry.getKey(), entry.getValue().getSnapshot());
}
for (Map.Entry<String, HDRLatencyHistogram> entry : hdrHistograms.entrySet()) {
hdrSnapshots.put(entry.getKey(), entry.getValue().getSnapshot());
}
// Notify reporters
for (LatencyReporter reporter : reporters) {
try {
reporter.report(snapshots, hdrSnapshots);
} catch (Exception e) {
System.err.println("Error in reporter " + reporter.getClass().getSimpleName() + ": " + e.getMessage());
}
}
}
private void rotateHistograms() {
// For sliding window analysis, you might want to keep multiple time windows
// This is a simplified version that just resets histograms periodically
if (config.get().autoReset) {
for (LatencyHistogram histogram : histograms.values()) {
histogram.reset();
}
for (HDRLatencyHistogram histogram : hdrHistograms.values()) {
histogram.reset();
}
}
}
public Map<String, LatencyHistogram.HistogramSnapshot> getAllSnapshots() {
Map<String, LatencyHistogram.HistogramSnapshot> snapshots = new HashMap<>();
for (Map.Entry<String, LatencyHistogram> entry : histograms.entrySet()) {
snapshots.put(entry.getKey(), entry.getValue().getSnapshot());
}
return snapshots;
}
public void shutdown() {
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
public static class RegistryConfig {
public long reportIntervalSeconds = 60;
public long rotationIntervalMinutes = 5;
public boolean autoReset = false;
public int significantDigits = 3;
}
}

4. Reporters and Output Formats

// LatencyReporter.java
import java.util.Map;
public interface LatencyReporter {
void report(Map<String, LatencyHistogram.HistogramSnapshot> snapshots,
Map<String, HDRLatencyHistogram.HDRSnapshot> hdrSnapshots);
}
// ConsoleReporter.java
import java.util.Map;
public class ConsoleReporter implements LatencyReporter {
private final boolean showDetails;
public ConsoleReporter() {
this(false);
}
public ConsoleReporter(boolean showDetails) {
this.showDetails = showDetails;
}
@Override
public void report(Map<String, LatencyHistogram.HistogramSnapshot> snapshots,
Map<String, HDRLatencyHistogram.HDRSnapshot> hdrSnapshots) {
System.out.println("\n=== Latency Report ===");
System.out.printf("Time: %s%n", new java.util.Date());
for (Map.Entry<String, LatencyHistogram.HistogramSnapshot> entry : snapshots.entrySet()) {
LatencyHistogram.HistogramSnapshot snapshot = entry.getValue();
System.out.printf("%nHistogram: %s%n", snapshot.getName());
System.out.printf("  Count: %,d, Min: %,d, Max: %,d, Avg: %.2f%n",
snapshot.getTotalCount(), snapshot.getMinValue(), 
snapshot.getMaxValue(), snapshot.getAverage());
System.out.printf("  Percentiles: p50=%.2f, p95=%.2f, p99=%.2f%n",
snapshot.getPercentile(0.5), snapshot.getPercentile(0.95), snapshot.getPercentile(0.99));
if (showDetails) {
System.out.println("  Bucket Distribution:");
for (Map.Entry<Long, Long> bucket : snapshot.getBucketCounts().entrySet()) {
if (bucket.getValue() > 0) {
double percentage = (double) bucket.getValue() / snapshot.getTotalCount() * 100;
System.out.printf("    ≤ %,d: %,d (%.1f%%)%n", 
bucket.getKey(), bucket.getValue(), percentage);
}
}
}
}
// Show HDR histogram summaries
for (Map.Entry<String, HDRLatencyHistogram.HDRSnapshot> entry : hdrSnapshots.entrySet()) {
HDRLatencyHistogram.HDRSnapshot snapshot = entry.getValue();
System.out.printf("%nHDR Histogram: %s%n", snapshot.getName());
System.out.printf("  Count: %,d, Mean: %.2f%n", snapshot.getTotalCount(), snapshot.getMean());
System.out.printf("  Percentiles: p50=%,d, p95=%,d, p99=%,d, p99.9=%,d%n",
snapshot.getPercentile(0.5), snapshot.getPercentile(0.95),
snapshot.getPercentile(0.99), snapshot.getPercentile(0.999));
}
}
}
// JSONReporter.java
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ArrayList;
public class JSONReporter implements LatencyReporter {
@Override
public void report(Map<String, LatencyHistogram.HistogramSnapshot> snapshots,
Map<String, HDRLatencyHistogram.HDRSnapshot> hdrSnapshots) {
Map<String, Object> report = new LinkedHashMap<>();
report.put("timestamp", System.currentTimeMillis());
report.put("format", "latency-histogram-report");
List<Map<String, Object>> histogramReports = new ArrayList<>();
for (Map.Entry<String, LatencyHistogram.HistogramSnapshot> entry : snapshots.entrySet()) {
histogramReports.add(createHistogramReport(entry.getValue()));
}
report.put("histograms", histogramReports);
List<Map<String, Object>> hdrReports = new ArrayList<>();
for (Map.Entry<String, HDRLatencyHistogram.HDRSnapshot> entry : hdrSnapshots.entrySet()) {
hdrReports.add(createHDRReport(entry.getValue()));
}
report.put("hdr_histograms", hdrReports);
// In a real implementation, you'd use Jackson or GSON here
System.out.println("JSON Report: " + formatAsJson(report));
}
private Map<String, Object> createHistogramReport(LatencyHistogram.HistogramSnapshot snapshot) {
Map<String, Object> report = new LinkedHashMap<>();
report.put("name", snapshot.getName());
report.put("timestamp", snapshot.getTimestamp());
report.put("total_count", snapshot.getTotalCount());
report.put("total_sum", snapshot.getTotalSum());
report.put("min", snapshot.getMinValue());
report.put("max", snapshot.getMaxValue());
report.put("average", snapshot.getAverage());
report.put("percentiles", snapshot.getPercentiles());
report.put("buckets", snapshot.getBucketCounts());
return report;
}
private Map<String, Object> createHDRReport(HDRLatencyHistogram.HDRSnapshot snapshot) {
Map<String, Object> report = new LinkedHashMap<>();
report.put("name", snapshot.getName());
report.put("timestamp", snapshot.getTimestamp());
report.put("total_count", snapshot.getTotalCount());
report.put("mean", snapshot.getMean());
report.put("percentiles", snapshot.getPercentiles());
return report;
}
private String formatAsJson(Map<String, Object> data) {
// Simplified JSON formatting - use a proper JSON library in production
StringBuilder sb = new StringBuilder();
sb.append("{");
boolean first = true;
for (Map.Entry<String, Object> entry : data.entrySet()) {
if (!first) sb.append(", ");
sb.append("\"").append(entry.getKey()).append("\": ");
appendValue(sb, entry.getValue());
first = false;
}
sb.append("}");
return sb.toString();
}
private void appendValue(StringBuilder sb, Object value) {
if (value instanceof String) {
sb.append("\"").append(escapeJson((String) value)).append("\"");
} else if (value instanceof Number) {
sb.append(value);
} else if (value instanceof Map) {
sb.append(formatAsJson((Map<String, Object>) value));
} else if (value instanceof List) {
sb.append("[");
boolean first = true;
for (Object item : (List<?>) value) {
if (!first) sb.append(", ");
appendValue(sb, item);
first = false;
}
sb.append("]");
} else {
sb.append("\"").append(String.valueOf(value)).append("\"");
}
}
private String escapeJson(String str) {
return str.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\b", "\\b")
.replace("\f", "\\f")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
}

5. Usage Examples and Demo

// LatencyHistogramDemo.java
import java.util.Random;
import java.util.concurrent.*;
public class LatencyHistogramDemo {
public static void main(String[] args) throws InterruptedException {
// Initialize registry
LatencyRegistry registry = LatencyRegistry.getInstance();
registry.addReporter(new ConsoleReporter(true));
registry.addReporter(new JSONReporter());
// Simulate different types of latency patterns
simulateDatabaseCalls(registry);
simulateNetworkCalls(registry);
simulateFileIOCalls(registry);
// Let the system run for a while to collect data
Thread.sleep(30000);
// Generate final report
registry.getAllSnapshots().forEach((name, snapshot) -> {
System.out.println(snapshot);
});
// Shutdown
registry.shutdown();
}
private static void simulateDatabaseCalls(LatencyRegistry registry) {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
Random random = new Random();
// Simulate database query latency (typically 1-100ms)
long latency = 1 + random.nextInt(100);
registry.recordLatency("database.query", latency, TimeUnit.MILLISECONDS);
// Simulate occasional slow queries
if (random.nextInt(100) < 5) { // 5% slow queries
long slowLatency = 100 + random.nextInt(900); // 100-1000ms
registry.recordLatency("database.query", slowLatency, TimeUnit.MILLISECONDS);
}
// Simulate transaction latency
long transactionLatency = 5 + random.nextInt(50);
registry.recordLatency("database.transaction", transactionLatency, TimeUnit.MILLISECONDS);
}, 0, 100, TimeUnit.MILLISECONDS); // 10 queries per second
}
private static void simulateNetworkCalls(LatencyRegistry registry) {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
Random random = new Random();
// Simulate API call latency (typically 10-500ms)
long latency = 10 + random.nextInt(490);
registry.recordLatency("api.rest", latency, TimeUnit.MILLISECONDS);
// Simulate occasional timeouts
if (random.nextInt(1000) < 2) { // 0.2% timeouts
registry.recordLatency("api.rest", 5000, TimeUnit.MILLISECONDS); // 5 second timeout
}
// Simulate cache hits (very fast)
long cacheLatency = random.nextInt(5);
registry.recordLatency("cache.get", cacheLatency, TimeUnit.MILLISECONDS);
}, 0, 50, TimeUnit.MILLISECONDS); // 20 calls per second
}
private static void simulateFileIOCalls(LatencyRegistry registry) {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
Random random = new Random();
// Simulate file read latency (typically 0.1-10ms)
long latency = 100 + random.nextInt(9900); // microseconds
registry.recordLatency("file.read", latency, TimeUnit.MICROSECONDS);
// Simulate file write latency
long writeLatency = 500 + random.nextInt(9500);
registry.recordLatency("file.write", writeLatency, TimeUnit.MICROSECONDS);
}, 0, 200, TimeUnit.MILLISECONDS); // 5 operations per second
}
}
// LatencyMeasurementUtility.java
public class LatencyMeasurementUtility {
public static <T> T measureLatency(LatencyRegistry registry, String operationName, 
TimeUnit unit, Callable<T> operation) throws Exception {
long startTime = System.nanoTime();
try {
return operation.call();
} finally {
long duration = System.nanoTime() - startTime;
long durationInUnit = unit.convert(duration, TimeUnit.NANOSECONDS);
registry.recordLatency(operationName, durationInUnit, unit);
}
}
public static void measureLatency(LatencyRegistry registry, String operationName,
TimeUnit unit, Runnable operation) {
long startTime = System.nanoTime();
try {
operation.run();
} finally {
long duration = System.nanoTime() - startTime;
long durationInUnit = unit.convert(duration, TimeUnit.NANOSECONDS);
registry.recordLatency(operationName, durationInUnit, unit);
}
}
}

Key Features:

  1. Multiple Histogram Types: Both fixed-bucket and HDR histograms
  2. High Performance: Thread-safe, low-overhead recording
  3. Accurate Percentiles: HDR histograms for precise percentile calculations
  4. Flexible Reporting: Multiple output formats (console, JSON)
  5. Automatic Management: Periodic reporting and histogram rotation
  6. Easy Integration: Simple API for measuring any operation
  7. Configurable: Custom bucket boundaries and precision levels

This latency histogram generator provides enterprise-grade latency tracking suitable for production systems, with the accuracy needed for performance analysis and SLO monitoring.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper