0
# Recorder Classes
1
2
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.
3
4
## Recorder
5
6
Core recorder implementation for integer values using writer-reader phasing to coordinate continuous recording with interval reporting.
7
8
```java { .api }
9
public class Recorder implements ValueRecorder, IntervalHistogramProvider<Histogram> {
10
11
// Constructors
12
public Recorder(int numberOfSignificantValueDigits);
13
public Recorder(int numberOfSignificantValueDigits, boolean packed);
14
public Recorder(long highestTrackableValue, int numberOfSignificantValueDigits);
15
public Recorder(long lowestDiscernibleValue,
16
long highestTrackableValue,
17
int numberOfSignificantValueDigits);
18
19
// Recording methods
20
void recordValue(long value);
21
void recordValueWithCount(long value, long count);
22
void recordValueWithExpectedInterval(long value, long expectedInterval);
23
void reset();
24
25
// Interval histogram methods
26
Histogram getIntervalHistogram();
27
Histogram getIntervalHistogram(Histogram histogramToRecycle);
28
void getIntervalHistogramInto(Histogram targetHistogram);
29
}
30
```
31
32
### Core Recording Operations
33
34
**Thread-Safe Recording**: All recording methods are wait-free and thread-safe for concurrent access from multiple threads.
35
36
```java
37
// Create recorder for latency measurements
38
Recorder latencyRecorder = new Recorder(3);
39
40
// Multiple threads can record concurrently
41
// Thread 1 - API requests
42
new Thread(() -> {
43
while (running) {
44
long startTime = System.nanoTime();
45
processApiRequest();
46
long endTime = System.nanoTime();
47
latencyRecorder.recordValue((endTime - startTime) / 1000); // Convert to microseconds
48
}
49
}).start();
50
51
// Thread 2 - Database operations
52
new Thread(() -> {
53
while (running) {
54
long startTime = System.nanoTime();
55
executeDatabaseQuery();
56
long endTime = System.nanoTime();
57
latencyRecorder.recordValue((endTime - startTime) / 1000);
58
}
59
}).start();
60
```
61
62
### Interval Histogram Extraction
63
64
The key feature of recorders is non-blocking interval histogram extraction:
65
66
```java
67
// Recording continues uninterrupted while extracting intervals
68
ScheduledExecutorService reporter = Executors.newSingleThreadScheduledExecutor();
69
70
reporter.scheduleAtFixedRate(() -> {
71
// Get interval histogram (automatically resets internal state)
72
Histogram intervalHist = latencyRecorder.getIntervalHistogram();
73
74
if (intervalHist.getTotalCount() > 0) {
75
// Analyze this interval's measurements
76
double mean = intervalHist.getMean();
77
long p95 = intervalHist.getValueAtPercentile(95.0);
78
long p99 = intervalHist.getValueAtPercentile(99.0);
79
80
System.out.printf("Interval: count=%d, mean=%.1fμs, P95=%dμs, P99=%dμs%n",
81
intervalHist.getTotalCount(), mean, p95, p99);
82
83
// Send to monitoring system
84
sendToMonitoring("latency.count", intervalHist.getTotalCount());
85
sendToMonitoring("latency.mean", mean);
86
sendToMonitoring("latency.p95", p95);
87
sendToMonitoring("latency.p99", p99);
88
}
89
}, 0, 10, TimeUnit.SECONDS);
90
```
91
92
### Memory-Efficient Interval Extraction
93
94
Recorders support histogram recycling to reduce garbage collection:
95
96
```java
97
Recorder recorder = new Recorder(3);
98
99
// Reuse histogram objects to minimize GC pressure
100
Histogram recycledHistogram = null;
101
102
while (true) {
103
Thread.sleep(5000); // 5-second intervals
104
105
// Reuse previous histogram object
106
Histogram intervalHist = recorder.getIntervalHistogram(recycledHistogram);
107
108
if (intervalHist.getTotalCount() > 0) {
109
processIntervalData(intervalHist);
110
}
111
112
// Keep reference for recycling
113
recycledHistogram = intervalHist;
114
}
115
```
116
117
### Constructor Options
118
119
```java
120
// Basic recorder with auto-resizing
121
Recorder basic = new Recorder(3);
122
123
// Memory-optimized with packed storage
124
Recorder packed = new Recorder(3, true);
125
126
// Fixed range recorder
127
Recorder fixedRange = new Recorder(1_000_000, 3); // Max 1 million
128
129
// Full specification
130
Recorder fullSpec = new Recorder(1, 10_000_000, 4); // Min 1, max 10M, high precision
131
```
132
133
## SingleWriterRecorder
134
135
Optimized recorder for single-writer scenarios providing better performance when only one thread records values.
136
137
```java { .api }
138
public class SingleWriterRecorder extends Recorder {
139
140
// Constructors
141
public SingleWriterRecorder(int numberOfSignificantValueDigits);
142
public SingleWriterRecorder(int numberOfSignificantValueDigits, boolean packed);
143
public SingleWriterRecorder(long highestTrackableValue, int numberOfSignificantValueDigits);
144
public SingleWriterRecorder(long lowestDiscernibleValue,
145
long highestTrackableValue,
146
int numberOfSignificantValueDigits);
147
}
148
```
149
150
### Usage Patterns
151
152
**Single-Threaded Recording**: Optimized for scenarios where only one thread performs measurements.
153
154
```java
155
// Single-writer recorder for main application thread measurements
156
SingleWriterRecorder mainThreadLatency = new SingleWriterRecorder(3);
157
158
// Main application loop (single thread)
159
public void processRequests() {
160
while (running) {
161
Request request = requestQueue.take();
162
163
long startTime = System.nanoTime();
164
processRequest(request);
165
long endTime = System.nanoTime();
166
167
// Only this thread records (optimized path)
168
mainThreadLatency.recordValue((endTime - startTime) / 1000);
169
}
170
}
171
172
// Separate monitoring thread extracts intervals
173
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
174
monitor.scheduleAtFixedRate(() -> {
175
Histogram interval = mainThreadLatency.getIntervalHistogram();
176
analyzeMainThreadPerformance(interval);
177
}, 0, 30, TimeUnit.SECONDS);
178
```
179
180
### Performance Benefits
181
182
SingleWriterRecorder provides:
183
- **Lower overhead** for recording operations (no inter-thread coordination)
184
- **Better cache locality** (single writer, single reader pattern)
185
- **Reduced contention** (no atomic operations between recording threads)
186
187
## DoubleRecorder
188
189
Recorder implementation for double (floating-point) values.
190
191
```java { .api }
192
public class DoubleRecorder implements DoubleValueRecorder, IntervalHistogramProvider<DoubleHistogram> {
193
194
// Constructors
195
public DoubleRecorder(int numberOfSignificantValueDigits);
196
public DoubleRecorder(long highestToLowestValueRatio, int numberOfSignificantValueDigits);
197
198
// Recording methods
199
void recordValue(double value);
200
void recordValueWithCount(double value, long count);
201
void recordValueWithExpectedInterval(double value, double expectedInterval);
202
void reset();
203
204
// Interval histogram methods
205
DoubleHistogram getIntervalHistogram();
206
DoubleHistogram getIntervalHistogram(DoubleHistogram histogramToRecycle);
207
void getIntervalHistogramInto(DoubleHistogram targetHistogram);
208
}
209
```
210
211
### Usage Examples
212
213
```java
214
// Create double recorder for response time measurements (in seconds)
215
DoubleRecorder responseTimeRecorder = new DoubleRecorder(3);
216
217
// Record response times from multiple services
218
public void recordServiceCall(String service, Supplier<String> serviceCall) {
219
double startTime = System.nanoTime() / 1e9;
220
221
try {
222
String result = serviceCall.get();
223
} finally {
224
double endTime = System.nanoTime() / 1e9;
225
double responseTime = endTime - startTime;
226
227
responseTimeRecorder.recordValue(responseTime);
228
}
229
}
230
231
// Extract interval measurements
232
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
233
scheduler.scheduleAtFixedRate(() -> {
234
DoubleHistogram interval = responseTimeRecorder.getIntervalHistogram();
235
236
if (interval.getTotalCount() > 0) {
237
double meanSeconds = interval.getMean();
238
double p95Seconds = interval.getValueAtPercentile(95.0);
239
double p99Seconds = interval.getValueAtPercentile(99.0);
240
241
System.out.printf("Response times - Mean: %.3fs, P95: %.3fs, P99: %.3fs%n",
242
meanSeconds, p95Seconds, p99Seconds);
243
244
// Check SLA compliance
245
if (p95Seconds > 2.0) { // P95 should be under 2 seconds
246
alertingService.sendAlert("P95 response time exceeded SLA: " + p95Seconds + "s");
247
}
248
}
249
}, 0, 60, TimeUnit.SECONDS);
250
```
251
252
### Coordinated Omission Correction
253
254
DoubleRecorder supports coordinated omission correction for timing measurements:
255
256
```java
257
DoubleRecorder timerRecorder = new DoubleRecorder(3);
258
259
// Expected measurement interval
260
double expectedInterval = 0.100; // 100ms expected intervals
261
262
public void performPeriodicMeasurement() {
263
while (running) {
264
double startTime = System.nanoTime() / 1e9;
265
266
// Measurement that might be delayed by system pauses
267
double measurement = performActualMeasurement();
268
269
// Record with coordinated omission correction
270
timerRecorder.recordValueWithExpectedInterval(measurement, expectedInterval);
271
272
try {
273
Thread.sleep(100); // Try to maintain 100ms intervals
274
} catch (InterruptedException e) {
275
break;
276
}
277
}
278
}
279
```
280
281
## SingleWriterDoubleRecorder
282
283
Single-writer optimized version of DoubleRecorder.
284
285
```java { .api }
286
public class SingleWriterDoubleRecorder extends DoubleRecorder {
287
288
// Constructors
289
public SingleWriterDoubleRecorder(int numberOfSignificantValueDigits);
290
public SingleWriterDoubleRecorder(long highestToLowestValueRatio, int numberOfSignificantValueDigits);
291
}
292
```
293
294
### Usage Examples
295
296
```java
297
// Single-writer double recorder for precise timing measurements
298
SingleWriterDoubleRecorder precisionTimer = new SingleWriterDoubleRecorder(4); // High precision
299
300
// Measurement loop (single thread only)
301
public void performPrecisionMeasurements() {
302
while (running) {
303
double startTime = System.nanoTime() / 1e9;
304
305
// Critical operation requiring precise timing
306
performCriticalOperation();
307
308
double endTime = System.nanoTime() / 1e9;
309
double duration = endTime - startTime;
310
311
// Single-threaded optimized recording
312
precisionTimer.recordValue(duration);
313
314
// Maintain measurement cadence
315
maintainCadence();
316
}
317
}
318
```
319
320
## IntervalHistogramProvider Interface
321
322
Common interface for classes providing interval histogram snapshots.
323
324
```java { .api }
325
public interface IntervalHistogramProvider<T extends EncodableHistogram> {
326
T getIntervalHistogram();
327
T getIntervalHistogram(T histogramToRecycle);
328
void getIntervalHistogramInto(T targetHistogram);
329
}
330
```
331
332
## Advanced Recording Patterns
333
334
### Multi-Metric Recording System
335
336
```java
337
public class MetricsRecordingSystem {
338
private final Recorder requestLatency = new Recorder(3);
339
private final Recorder requestSize = new Recorder(2); // Lower precision for size
340
private final DoubleRecorder errorRate = new DoubleRecorder(2);
341
342
public void recordRequest(long latencyMicros, long sizeBytes, boolean success) {
343
requestLatency.recordValue(latencyMicros);
344
requestSize.recordValue(sizeBytes);
345
errorRate.recordValue(success ? 0.0 : 1.0); // 0 for success, 1 for error
346
}
347
348
public void generateIntervalReport() {
349
Histogram latencyHist = requestLatency.getIntervalHistogram();
350
Histogram sizeHist = requestSize.getIntervalHistogram();
351
DoubleHistogram errorHist = errorRate.getIntervalHistogram();
352
353
if (latencyHist.getTotalCount() > 0) {
354
long totalRequests = latencyHist.getTotalCount();
355
double errorRate = errorHist.getMean(); // Mean of 0s and 1s = error rate
356
357
System.out.printf("Interval Report:%n");
358
System.out.printf(" Total Requests: %d%n", totalRequests);
359
System.out.printf(" Error Rate: %.2f%%%n", errorRate * 100);
360
System.out.printf(" Latency P95: %d μs%n", latencyHist.getValueAtPercentile(95.0));
361
System.out.printf(" Size P95: %d bytes%n", sizeHist.getValueAtPercentile(95.0));
362
}
363
}
364
}
365
```
366
367
### Hierarchical Metrics with Tags
368
369
```java
370
public class ServiceMetrics {
371
private final Map<String, Recorder> serviceRecorders = new ConcurrentHashMap<>();
372
373
public void recordServiceCall(String serviceName, long latencyMicros) {
374
Recorder recorder = serviceRecorders.computeIfAbsent(serviceName,
375
name -> {
376
Recorder r = new Recorder(3);
377
r.setTag("service:" + name);
378
return r;
379
});
380
recorder.recordValue(latencyMicros);
381
}
382
383
public void generateServiceReports() {
384
serviceRecorders.forEach((serviceName, recorder) -> {
385
Histogram interval = recorder.getIntervalHistogram();
386
387
if (interval.getTotalCount() > 0) {
388
System.out.printf("Service %s:%n", serviceName);
389
System.out.printf(" Calls: %d%n", interval.getTotalCount());
390
System.out.printf(" Mean: %.1f μs%n", interval.getMean());
391
System.out.printf(" P99: %d μs%n", interval.getValueAtPercentile(99.0));
392
}
393
});
394
}
395
}
396
```
397
398
### Coordinated Recording with Reset Synchronization
399
400
```java
401
public class CoordinatedMetricsSystem {
402
private final List<Recorder> allRecorders = new ArrayList<>();
403
private final Object resetLock = new Object();
404
405
public Recorder createRecorder(String name, int precision) {
406
Recorder recorder = new Recorder(precision);
407
recorder.setTag(name);
408
409
synchronized (resetLock) {
410
allRecorders.add(recorder);
411
}
412
413
return recorder;
414
}
415
416
public Map<String, Histogram> getCoordinatedIntervals() {
417
Map<String, Histogram> intervals = new HashMap<>();
418
419
synchronized (resetLock) {
420
// Get all intervals atomically
421
for (Recorder recorder : allRecorders) {
422
String tag = recorder.getTag();
423
Histogram interval = recorder.getIntervalHistogram();
424
intervals.put(tag, interval);
425
}
426
}
427
428
return intervals;
429
}
430
}
431
```
432
433
## Performance Considerations
434
435
### Recording Performance
436
- **Recorder**: Excellent multi-threaded recording performance
437
- **SingleWriterRecorder**: Best single-threaded performance
438
- **DoubleRecorder**: Slight overhead for double-to-internal conversion
439
- **SingleWriterDoubleRecorder**: Optimized single-threaded double recording
440
441
### Memory Efficiency
442
- Use histogram recycling with `getIntervalHistogram(histogramToRecycle)`
443
- Consider packed storage for sparse distributions
444
- Monitor GC impact of frequent interval extraction
445
446
### Thread Safety
447
All recorder classes provide:
448
- **Wait-free recording** from multiple threads
449
- **Non-blocking interval extraction**
450
- **Proper memory visibility** guarantees
451
- **Coordinated reset** behavior
452
453
## Common Use Cases
454
455
### Application Performance Monitoring (APM)
456
457
```java
458
// APM system using recorders
459
public class APMSystem {
460
private final Recorder httpRequestLatency = new Recorder(3);
461
private final Recorder databaseQueryLatency = new Recorder(3);
462
private final DoubleRecorder cpuUtilization = new DoubleRecorder(2);
463
464
// Called by HTTP request interceptors
465
public void recordHttpRequest(long latencyMicros) {
466
httpRequestLatency.recordValue(latencyMicros);
467
}
468
469
// Called by database interceptors
470
public void recordDatabaseQuery(long latencyMicros) {
471
databaseQueryLatency.recordValue(latencyMicros);
472
}
473
474
// Called by system monitor
475
public void recordCpuUtilization(double percentage) {
476
cpuUtilization.recordValue(percentage / 100.0); // Store as 0-1 ratio
477
}
478
479
@Scheduled(fixedRate = 30000) // Every 30 seconds
480
public void publishMetrics() {
481
publishHistogram("http.latency", httpRequestLatency.getIntervalHistogram());
482
publishHistogram("db.latency", databaseQueryLatency.getIntervalHistogram());
483
publishHistogram("cpu.utilization", cpuUtilization.getIntervalHistogram());
484
}
485
}
486
```
487
488
### Load Testing Framework
489
490
```java
491
// Load testing with detailed latency recording
492
public class LoadTester {
493
private final SingleWriterRecorder latencyRecorder = new SingleWriterRecorder(3);
494
private volatile boolean testRunning = true;
495
496
public void runLoadTest(int durationSeconds, int requestsPerSecond) {
497
// Start metrics collection
498
ScheduledExecutorService metricsCollector = Executors.newSingleThreadScheduledExecutor();
499
metricsCollector.scheduleAtFixedRate(this::collectMetrics, 0, 1, TimeUnit.SECONDS);
500
501
// Run load test
502
long testEndTime = System.currentTimeMillis() + durationSeconds * 1000L;
503
504
while (System.currentTimeMillis() < testEndTime) {
505
long startTime = System.nanoTime();
506
507
try {
508
makeTestRequest();
509
} catch (Exception e) {
510
// Record failed requests with high latency value
511
latencyRecorder.recordValue(Long.MAX_VALUE / 1000); // Error marker
512
continue;
513
}
514
515
long endTime = System.nanoTime();
516
latencyRecorder.recordValue((endTime - startTime) / 1000); // Microseconds
517
518
// Rate limiting
519
Thread.sleep(1000 / requestsPerSecond);
520
}
521
522
testRunning = false;
523
metricsCollector.shutdown();
524
525
// Final report
526
generateFinalReport();
527
}
528
529
private void collectMetrics() {
530
if (!testRunning) return;
531
532
Histogram interval = latencyRecorder.getIntervalHistogram();
533
if (interval.getTotalCount() > 0) {
534
System.out.printf("RPS: %d, P50: %d μs, P95: %d μs, P99: %d μs%n",
535
interval.getTotalCount(),
536
interval.getValueAtPercentile(50.0),
537
interval.getValueAtPercentile(95.0),
538
interval.getValueAtPercentile(99.0));
539
}
540
}
541
}
542
```