• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.core.internal.util;
17 
18 import static software.amazon.awssdk.core.http.HttpResponseHandler.X_AMZN_REQUEST_ID_HEADERS;
19 import static software.amazon.awssdk.core.http.HttpResponseHandler.X_AMZ_ID_2_HEADER;
20 
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.time.Duration;
24 import java.util.OptionalLong;
25 import java.util.concurrent.Callable;
26 import java.util.concurrent.CompletableFuture;
27 import java.util.concurrent.atomic.AtomicLong;
28 import java.util.function.Supplier;
29 import software.amazon.awssdk.annotations.SdkInternalApi;
30 import software.amazon.awssdk.core.exception.SdkClientException;
31 import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
32 import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
33 import software.amazon.awssdk.core.metrics.CoreMetric;
34 import software.amazon.awssdk.http.HttpMetric;
35 import software.amazon.awssdk.http.SdkHttpFullRequest;
36 import software.amazon.awssdk.http.SdkHttpFullResponse;
37 import software.amazon.awssdk.metrics.MetricCollector;
38 import software.amazon.awssdk.metrics.NoOpMetricCollector;
39 import software.amazon.awssdk.metrics.SdkMetric;
40 import software.amazon.awssdk.utils.Pair;
41 
42 /**
43  * Utility methods for working with metrics.
44  */
45 @SdkInternalApi
46 public final class MetricUtils {
47     private static final long ONE_SEC_IN_NS = 1_000_000_000L;
48 
MetricUtils()49     private MetricUtils() {
50     }
51 
52     /**
53      * Measure the duration of the given callable.
54      *
55      * @param c The callable to measure.
56      * @return A {@code Pair} containing the result of {@code c} and the duration.
57      */
measureDuration(Supplier<T> c)58     public static <T> Pair<T, Duration> measureDuration(Supplier<T> c) {
59         long start = System.nanoTime();
60         T result = c.get();
61         Duration d = Duration.ofNanos(System.nanoTime() - start);
62         return Pair.of(result, d);
63     }
64 
65     /**
66      * Report a duration metric of the given {@link CompletableFuture} supplier.
67      *
68      * @param c The callable to measure.
69      * @param metricCollector The MetricCollector where the metric is to be reported.
70      * @param metric The metric to be reported.
71      * @return A {@code Pair} containing the result of {@code c} and the duration.
72      */
reportDuration(Supplier<CompletableFuture<T>> c, MetricCollector metricCollector, SdkMetric<Duration> metric)73     public static <T> CompletableFuture<T> reportDuration(Supplier<CompletableFuture<T>> c,
74                                                           MetricCollector metricCollector,
75                                                           SdkMetric<Duration> metric) {
76         long start = System.nanoTime();
77         CompletableFuture<T> result = c.get();
78         result.whenComplete((r, t) -> {
79             Duration d = Duration.ofNanos(System.nanoTime() - start);
80             metricCollector.reportMetric(metric, d);
81         });
82         return result;
83     }
84 
85     /**
86      * Measure the duration of the given callable.
87      *
88      * @param c The callable to measure.
89      * @return A {@code Pair} containing the result of {@code c} and the duration.
90      */
measureDurationUnsafe(Callable<T> c)91     public static <T> Pair<T, Duration> measureDurationUnsafe(Callable<T> c) throws Exception {
92         return measureDurationUnsafe(c, System.nanoTime());
93     }
94 
95     /**
96      * Measure the duration of the given callable, using the provided time as the basis.
97      */
measureDurationUnsafe(Callable<T> c, long startTime)98     public static <T> Pair<T, Duration> measureDurationUnsafe(Callable<T> c, long startTime) throws Exception {
99         T result = c.call();
100         Duration d = Duration.ofNanos(System.nanoTime() - startTime);
101         return Pair.of(result, d);
102     }
103 
104     /**
105      * Collect the SERVICE_ENDPOINT metric for this request.
106      */
collectServiceEndpointMetrics(MetricCollector metricCollector, SdkHttpFullRequest httpRequest)107     public static void collectServiceEndpointMetrics(MetricCollector metricCollector, SdkHttpFullRequest httpRequest) {
108         if (metricCollector != null && !(metricCollector instanceof NoOpMetricCollector) && httpRequest != null) {
109             // Only interested in the service endpoint so don't include any path, query, or fragment component
110             URI requestUri = httpRequest.getUri();
111             try {
112                 URI serviceEndpoint = new URI(requestUri.getScheme(), requestUri.getAuthority(), null, null, null);
113                 metricCollector.reportMetric(CoreMetric.SERVICE_ENDPOINT, serviceEndpoint);
114             } catch (URISyntaxException e) {
115                 // This should not happen since getUri() should return a valid URI
116                 throw SdkClientException.create("Unable to collect SERVICE_ENDPOINT metric", e);
117             }
118         }
119     }
120 
collectHttpMetrics(MetricCollector metricCollector, SdkHttpFullResponse httpResponse)121     public static void collectHttpMetrics(MetricCollector metricCollector, SdkHttpFullResponse httpResponse) {
122         if (metricCollector != null && !(metricCollector instanceof NoOpMetricCollector) && httpResponse != null) {
123             metricCollector.reportMetric(HttpMetric.HTTP_STATUS_CODE, httpResponse.statusCode());
124             X_AMZN_REQUEST_ID_HEADERS.forEach(h -> {
125                 httpResponse.firstMatchingHeader(h).ifPresent(v -> metricCollector.reportMetric(CoreMetric.AWS_REQUEST_ID, v));
126             });
127             httpResponse.firstMatchingHeader(X_AMZ_ID_2_HEADER)
128                         .ifPresent(v -> metricCollector.reportMetric(CoreMetric.AWS_EXTENDED_REQUEST_ID, v));
129         }
130     }
131 
createAttemptMetricsCollector(RequestExecutionContext context)132     public static MetricCollector createAttemptMetricsCollector(RequestExecutionContext context) {
133         MetricCollector parentCollector = context.executionContext().metricCollector();
134         if (parentCollector != null) {
135             return parentCollector.createChild("ApiCallAttempt");
136         }
137         return NoOpMetricCollector.create();
138     }
139 
createHttpMetricsCollector(RequestExecutionContext context)140     public static MetricCollector createHttpMetricsCollector(RequestExecutionContext context) {
141         MetricCollector parentCollector = context.attemptMetricCollector();
142         if (parentCollector != null) {
143             return parentCollector.createChild("HttpClient");
144         }
145         return NoOpMetricCollector.create();
146     }
147 
apiCallAttemptStartNanoTime(RequestExecutionContext context)148     public static OptionalLong apiCallAttemptStartNanoTime(RequestExecutionContext context) {
149         Long t = context.executionAttributes().getAttribute(SdkInternalExecutionAttribute.API_CALL_ATTEMPT_START_NANO_TIME);
150         if (t == null) {
151             return OptionalLong.empty();
152         }
153         return OptionalLong.of(t);
154     }
155 
resetApiCallAttemptStartNanoTime(RequestExecutionContext context)156     public static long resetApiCallAttemptStartNanoTime(RequestExecutionContext context) {
157         long now = System.nanoTime();
158         context.executionAttributes().putAttribute(SdkInternalExecutionAttribute.API_CALL_ATTEMPT_START_NANO_TIME, now);
159         return now;
160     }
161 
apiCallAttemptResponseBytesRead(RequestExecutionContext context)162     public static OptionalLong apiCallAttemptResponseBytesRead(RequestExecutionContext context) {
163         AtomicLong read = context.executionAttributes().getAttribute(SdkInternalExecutionAttribute.RESPONSE_BYTES_READ);
164         if (read == null) {
165             return OptionalLong.empty();
166         }
167         return OptionalLong.of(read.get());
168     }
169 
responseHeadersReadEndNanoTime(RequestExecutionContext context)170     public static OptionalLong responseHeadersReadEndNanoTime(RequestExecutionContext context) {
171         Long startTime = context.executionAttributes().getAttribute(SdkInternalExecutionAttribute.HEADERS_READ_END_NANO_TIME);
172         if (startTime == null) {
173             return OptionalLong.empty();
174         }
175         return OptionalLong.of(startTime);
176     }
177 
bytesPerSec(long totalBytes, long nanoStart, long nanoEnd)178     public static double bytesPerSec(long totalBytes, long nanoStart, long nanoEnd) {
179         long duration = nanoEnd - nanoStart;
180         double bytesPerNs = (double) totalBytes / duration;
181         return bytesPerNs * ONE_SEC_IN_NS;
182     }
183 }
184