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