0
# Metrics and Monitoring
1
2
Built-in metrics collection capabilities for HTTP client implementations. Provides standardized metrics for HTTP/1.1 and HTTP/2 operations including connection pooling, performance monitoring, and protocol-specific statistics.
3
4
## Capabilities
5
6
### HttpMetric
7
8
Defines standard metrics that HTTP client implementations should collect for HTTP/1.1 and HTTP/2 operations. These metrics provide insights into client performance and resource utilization.
9
10
```java { .api }
11
/**
12
* Metrics collected by HTTP clients for HTTP/1 and HTTP/2 operations.
13
* Implementations should collect these metrics to provide visibility
14
* into client performance and resource utilization.
15
*/
16
public final class HttpMetric {
17
/**
18
* Name of the HTTP client implementation
19
*/
20
public static final SdkMetric<String> HTTP_CLIENT_NAME;
21
22
/**
23
* Maximum number of concurrent requests supported by the client
24
*/
25
public static final SdkMetric<Integer> MAX_CONCURRENCY;
26
27
/**
28
* Current number of available concurrent request slots
29
*/
30
public static final SdkMetric<Integer> AVAILABLE_CONCURRENCY;
31
32
/**
33
* Current number of requests actively being executed
34
*/
35
public static final SdkMetric<Integer> LEASED_CONCURRENCY;
36
37
/**
38
* Number of requests currently waiting to acquire concurrency
39
*/
40
public static final SdkMetric<Integer> PENDING_CONCURRENCY_ACQUIRES;
41
42
/**
43
* HTTP response status code for completed requests
44
*/
45
public static final SdkMetric<Integer> HTTP_STATUS_CODE;
46
47
/**
48
* Time taken to acquire a connection or channel from the pool
49
*/
50
public static final SdkMetric<Duration> CONCURRENCY_ACQUIRE_DURATION;
51
}
52
```
53
54
**Usage Example:**
55
56
```java
57
// HTTP client implementation collecting metrics
58
public class MyHttpClient implements SdkHttpClient {
59
private final MetricCollector metricCollector;
60
private final ConnectionPool connectionPool;
61
62
@Override
63
public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) {
64
return new MetricsAwareExecutableRequest(request, metricCollector, connectionPool);
65
}
66
67
// Collect periodic metrics
68
private void collectPoolMetrics() {
69
metricCollector.reportMetric(HttpMetric.HTTP_CLIENT_NAME, clientName());
70
metricCollector.reportMetric(HttpMetric.MAX_CONCURRENCY, connectionPool.maxConnections());
71
metricCollector.reportMetric(HttpMetric.AVAILABLE_CONCURRENCY, connectionPool.availableConnections());
72
metricCollector.reportMetric(HttpMetric.LEASED_CONCURRENCY, connectionPool.activeConnections());
73
metricCollector.reportMetric(HttpMetric.PENDING_CONCURRENCY_ACQUIRES, connectionPool.pendingAcquires());
74
}
75
}
76
77
// Metrics-aware request execution
78
public class MetricsAwareExecutableRequest implements ExecutableHttpRequest {
79
private final HttpExecuteRequest request;
80
private final MetricCollector metricCollector;
81
private final ConnectionPool connectionPool;
82
83
@Override
84
public HttpExecuteResponse call() throws IOException {
85
long acquireStart = System.nanoTime();
86
87
try (Connection connection = connectionPool.acquire()) {
88
long acquireDuration = System.nanoTime() - acquireStart;
89
metricCollector.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION,
90
Duration.ofNanos(acquireDuration));
91
92
HttpExecuteResponse response = connection.execute(request);
93
94
// Report response status
95
metricCollector.reportMetric(HttpMetric.HTTP_STATUS_CODE,
96
response.httpResponse().statusCode());
97
98
return response;
99
}
100
}
101
}
102
```
103
104
### Http2Metric
105
106
Defines HTTP/2 specific metrics that provide insights into HTTP/2 protocol behavior, stream management, and flow control.
107
108
```java { .api }
109
/**
110
* Metrics specific to HTTP/2 operations.
111
* These metrics provide insights into HTTP/2 stream management,
112
* flow control, and protocol-specific performance characteristics.
113
*/
114
public final class Http2Metric {
115
/**
116
* Local HTTP/2 stream window size in bytes.
117
* Indicates how much data the local endpoint can receive.
118
*/
119
public static final SdkMetric<Integer> LOCAL_STREAM_WINDOW_SIZE_IN_BYTES;
120
121
/**
122
* Remote HTTP/2 stream window size in bytes.
123
* Indicates how much data can be sent to the remote endpoint.
124
*/
125
public static final SdkMetric<Integer> REMOTE_STREAM_WINDOW_SIZE_IN_BYTES;
126
}
127
```
128
129
**Usage Example:**
130
131
```java
132
// HTTP/2 client implementation with flow control metrics
133
public class Http2Client implements SdkAsyncHttpClient {
134
private final MetricCollector metricCollector;
135
136
public void reportStreamMetrics(Http2Stream stream) {
137
// Report flow control window sizes
138
metricCollector.reportMetric(Http2Metric.LOCAL_STREAM_WINDOW_SIZE_IN_BYTES,
139
stream.getLocalWindowSize());
140
141
metricCollector.reportMetric(Http2Metric.REMOTE_STREAM_WINDOW_SIZE_IN_BYTES,
142
stream.getRemoteWindowSize());
143
}
144
145
@Override
146
public CompletableFuture<Void> execute(AsyncExecuteRequest request) {
147
return http2Connection.createStream()
148
.thenCompose(stream -> {
149
// Report initial window sizes
150
reportStreamMetrics(stream);
151
152
// Execute request and monitor flow control
153
return stream.sendRequest(request)
154
.thenRun(() -> {
155
// Report final window sizes
156
reportStreamMetrics(stream);
157
});
158
});
159
}
160
}
161
```
162
163
## Metric Collection Integration
164
165
### SdkMetric Type System
166
167
The metric system uses type-safe metric definitions:
168
169
```java { .api }
170
/**
171
* Type-safe metric definition
172
*/
173
public interface SdkMetric<T> {
174
/**
175
* @return Metric name for identification
176
*/
177
String name();
178
179
/**
180
* @return Expected value type for this metric
181
*/
182
Class<T> valueType();
183
}
184
```
185
186
### MetricCollector Integration
187
188
HTTP clients should integrate with the AWS SDK metric collection system:
189
190
```java
191
// Metric collection during request execution
192
public class MetricsIntegratedClient implements SdkHttpClient {
193
@Override
194
public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) {
195
Optional<MetricCollector> collector = request.metricCollector();
196
197
return new ExecutableHttpRequest() {
198
@Override
199
public HttpExecuteResponse call() throws IOException {
200
long startTime = System.nanoTime();
201
202
try {
203
// Report client name
204
collector.ifPresent(c ->
205
c.reportMetric(HttpMetric.HTTP_CLIENT_NAME, clientName()));
206
207
// Execute request
208
HttpExecuteResponse response = executeHttpRequest(request);
209
210
// Report success metrics
211
collector.ifPresent(c -> {
212
c.reportMetric(HttpMetric.HTTP_STATUS_CODE,
213
response.httpResponse().statusCode());
214
215
long duration = System.nanoTime() - startTime;
216
c.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION,
217
Duration.ofNanos(duration));
218
});
219
220
return response;
221
} catch (IOException e) {
222
// Report error metrics
223
collector.ifPresent(c -> {
224
// Could report error-specific metrics here
225
});
226
throw e;
227
}
228
}
229
};
230
}
231
}
232
```
233
234
## Connection Pool Metrics
235
236
### Real-time Pool Monitoring
237
238
HTTP clients should provide real-time visibility into connection pool state:
239
240
```java
241
public class MonitoredConnectionPool implements ConnectionPool {
242
private final AtomicInteger maxConnections;
243
private final AtomicInteger activeConnections;
244
private final AtomicInteger availableConnections;
245
private final AtomicInteger pendingAcquires;
246
private final MetricCollector metricCollector;
247
248
public MonitoredConnectionPool(int maxConnections, MetricCollector metricCollector) {
249
this.maxConnections = new AtomicInteger(maxConnections);
250
this.activeConnections = new AtomicInteger(0);
251
this.availableConnections = new AtomicInteger(maxConnections);
252
this.pendingAcquires = new AtomicInteger(0);
253
this.metricCollector = metricCollector;
254
255
// Start periodic metric reporting
256
startMetricReporting();
257
}
258
259
@Override
260
public Connection acquire() throws IOException {
261
pendingAcquires.incrementAndGet();
262
long acquireStart = System.nanoTime();
263
264
try {
265
Connection connection = doAcquire();
266
267
// Update counters
268
activeConnections.incrementAndGet();
269
availableConnections.decrementAndGet();
270
pendingAcquires.decrementAndGet();
271
272
// Report acquire duration
273
long acquireDuration = System.nanoTime() - acquireStart;
274
metricCollector.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION,
275
Duration.ofNanos(acquireDuration));
276
277
return new MetricsAwareConnection(connection, this);
278
} catch (IOException e) {
279
pendingAcquires.decrementAndGet();
280
throw e;
281
}
282
}
283
284
public void releaseConnection(Connection connection) {
285
activeConnections.decrementAndGet();
286
availableConnections.incrementAndGet();
287
doRelease(connection);
288
}
289
290
private void startMetricReporting() {
291
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
292
scheduler.scheduleAtFixedRate(() -> {
293
metricCollector.reportMetric(HttpMetric.MAX_CONCURRENCY, maxConnections.get());
294
metricCollector.reportMetric(HttpMetric.AVAILABLE_CONCURRENCY, availableConnections.get());
295
metricCollector.reportMetric(HttpMetric.LEASED_CONCURRENCY, activeConnections.get());
296
metricCollector.reportMetric(HttpMetric.PENDING_CONCURRENCY_ACQUIRES, pendingAcquires.get());
297
}, 0, 10, TimeUnit.SECONDS);
298
}
299
}
300
301
// Connection wrapper that reports metrics on release
302
public class MetricsAwareConnection implements Connection {
303
private final Connection delegate;
304
private final MonitoredConnectionPool pool;
305
306
@Override
307
public void close() {
308
delegate.close();
309
pool.releaseConnection(delegate);
310
}
311
312
// Delegate all other methods to the wrapped connection
313
}
314
```
315
316
### HTTP/2 Stream Metrics
317
318
For HTTP/2 implementations, track stream-specific metrics:
319
320
```java
321
public class Http2StreamManager {
322
private final MetricCollector metricCollector;
323
private final Map<Integer, Http2Stream> activeStreams = new ConcurrentHashMap<>();
324
325
public Http2Stream createStream() {
326
Http2Stream stream = new Http2Stream();
327
activeStreams.put(stream.getId(), stream);
328
329
// Report initial flow control state
330
reportStreamFlowControl(stream);
331
332
return stream;
333
}
334
335
public void reportStreamFlowControl(Http2Stream stream) {
336
metricCollector.reportMetric(Http2Metric.LOCAL_STREAM_WINDOW_SIZE_IN_BYTES,
337
stream.getLocalFlowControlWindow());
338
339
metricCollector.reportMetric(Http2Metric.REMOTE_STREAM_WINDOW_SIZE_IN_BYTES,
340
stream.getRemoteFlowControlWindow());
341
}
342
343
public void onWindowUpdate(int streamId, int windowSizeIncrement) {
344
Http2Stream stream = activeStreams.get(streamId);
345
if (stream != null) {
346
stream.updateRemoteWindow(windowSizeIncrement);
347
reportStreamFlowControl(stream);
348
}
349
}
350
351
public void onStreamData(int streamId, int dataSize) {
352
Http2Stream stream = activeStreams.get(streamId);
353
if (stream != null) {
354
stream.consumeLocalWindow(dataSize);
355
reportStreamFlowControl(stream);
356
}
357
}
358
}
359
```
360
361
## Metrics Best Practices
362
363
### Efficient Metric Collection
364
365
1. **Avoid High-Frequency Metrics**:
366
```java
367
// Don't report metrics for every byte transferred
368
// Instead, report periodically or on significant events
369
370
public class EfficientMetricReporting {
371
private volatile long lastMetricReport = 0;
372
private static final long METRIC_REPORT_INTERVAL_MS = 10_000; // 10 seconds
373
374
public void maybeReportMetrics() {
375
long now = System.currentTimeMillis();
376
if (now - lastMetricReport > METRIC_REPORT_INTERVAL_MS) {
377
reportCurrentMetrics();
378
lastMetricReport = now;
379
}
380
}
381
}
382
```
383
384
2. **Batch Metric Updates**:
385
```java
386
// Collect multiple metrics in a single call
387
public void reportBatchMetrics(MetricCollector collector) {
388
collector.reportMetrics(Map.of(
389
HttpMetric.AVAILABLE_CONCURRENCY, connectionPool.available(),
390
HttpMetric.LEASED_CONCURRENCY, connectionPool.active(),
391
HttpMetric.PENDING_CONCURRENCY_ACQUIRES, connectionPool.pending()
392
));
393
}
394
```
395
396
3. **Conditional Metric Collection**:
397
```java
398
public HttpExecuteResponse call() throws IOException {
399
Optional<MetricCollector> collector = request.metricCollector();
400
401
// Only collect metrics if a collector is provided
402
if (collector.isPresent()) {
403
return executeWithMetrics(collector.get());
404
} else {
405
return executeWithoutMetrics();
406
}
407
}
408
```
409
410
### Error and Performance Metrics
411
412
Track both success and failure scenarios:
413
414
```java
415
public class ComprehensiveMetricsClient implements SdkHttpClient {
416
@Override
417
public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) {
418
return new ExecutableHttpRequest() {
419
@Override
420
public HttpExecuteResponse call() throws IOException {
421
Optional<MetricCollector> collector = request.metricCollector();
422
long startTime = System.nanoTime();
423
424
try {
425
HttpExecuteResponse response = executeRequest(request);
426
427
// Report success metrics
428
collector.ifPresent(c -> {
429
c.reportMetric(HttpMetric.HTTP_STATUS_CODE,
430
response.httpResponse().statusCode());
431
432
// Report timing
433
long duration = System.nanoTime() - startTime;
434
c.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION,
435
Duration.ofNanos(duration));
436
});
437
438
return response;
439
} catch (IOException e) {
440
// Report error metrics
441
collector.ifPresent(c -> {
442
// Report error count or type
443
c.reportMetric(createErrorMetric(e.getClass().getSimpleName()), 1);
444
445
// Still report timing for failed requests
446
long duration = System.nanoTime() - startTime;
447
c.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION,
448
Duration.ofNanos(duration));
449
});
450
451
throw e;
452
}
453
}
454
};
455
}
456
457
private SdkMetric<Integer> createErrorMetric(String errorType) {
458
// Create custom metrics for different error types
459
return new SdkMetric<Integer>() {
460
@Override
461
public String name() {
462
return "http.client.error." + errorType.toLowerCase();
463
}
464
465
@Override
466
public Class<Integer> valueType() {
467
return Integer.class;
468
}
469
};
470
}
471
}
472
```
473
474
## Monitoring and Observability
475
476
### Integration with Monitoring Systems
477
478
HTTP client metrics can be integrated with various monitoring systems:
479
480
```java
481
// Example integration with Micrometer/Prometheus
482
public class MicrometerIntegratedClient implements SdkHttpClient {
483
private final MeterRegistry meterRegistry;
484
private final Counter requestCounter;
485
private final Timer requestTimer;
486
private final Gauge connectionGauge;
487
488
public MicrometerIntegratedClient(MeterRegistry meterRegistry) {
489
this.meterRegistry = meterRegistry;
490
this.requestCounter = Counter.builder("http.client.requests")
491
.description("Total HTTP requests")
492
.register(meterRegistry);
493
this.requestTimer = Timer.builder("http.client.request.duration")
494
.description("HTTP request duration")
495
.register(meterRegistry);
496
this.connectionGauge = Gauge.builder("http.client.connections.active")
497
.description("Active HTTP connections")
498
.register(meterRegistry, this, client -> getActiveConnectionCount());
499
}
500
501
// Implementation integrates AWS SDK metrics with Micrometer
502
}
503
```
504
505
The metrics system provides comprehensive visibility into HTTP client behavior, enabling effective monitoring, troubleshooting, and performance optimization of AWS SDK HTTP operations.