com.amazonaws.metrics.internal.cloudwatch.PredefinedMetricTransformer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aws-java-sdk-cloudwatchmetrics Show documentation
Show all versions of aws-java-sdk-cloudwatchmetrics Show documentation
This package holds the classes for uploading the clientside metrics collected from AWS Java SDK to Amazon CloudWatch
/*
* Copyright 2010-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.metrics.internal.cloudwatch;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.Request;
import com.amazonaws.Response;
import com.amazonaws.metrics.MetricType;
import com.amazonaws.metrics.RequestMetricCollector;
import com.amazonaws.metrics.internal.cloudwatch.spi.AWSMetricTransformerFactory;
import com.amazonaws.metrics.internal.cloudwatch.spi.Dimensions;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.MetricDatum;
import com.amazonaws.services.cloudwatch.model.StandardUnit;
import com.amazonaws.util.AWSRequestMetrics;
import com.amazonaws.util.AWSRequestMetrics.Field;
import com.amazonaws.util.TimingInfo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.amazonaws.annotation.ThreadSafe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.amazonaws.metrics.internal.cloudwatch.spi.MetricData.newMetricDatum;
import static com.amazonaws.metrics.internal.cloudwatch.spi.RequestMetricTransformer.Utils.endTimestamp;
/**
* Used to transform the predefined metrics of the AWS SDK into instances of
* {@link MetricDatum}.
*
* See http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/
* publishingMetrics.html
*
* @see AWSRequestMetrics
* @see RequestMetricCollector
*/
@ThreadSafe
public class PredefinedMetricTransformer {
private static final Log log = LogFactory.getLog(PredefinedMetricTransformer.class);
static final boolean INCLUDE_REQUEST_TYPE = true;
static final boolean EXCLUDE_REQUEST_TYPE = !INCLUDE_REQUEST_TYPE;
/**
* Returns a non-null list of metric datum for the metrics collected for the
* given request/response.
*
* @param metricType the request metric type
*/
public List toMetricData(MetricType metricType, Request> request, Response> response) {
if (metricType instanceof Field) {
// Predefined metrics across all AWS http clients
Field predefined = (Field) metricType;
switch(predefined) {
case HttpClientRetryCount:
case HttpClientPoolAvailableCount:
case HttpClientPoolLeasedCount:
case HttpClientPoolPendingCount:
return metricOfCount(predefined, request, response);
case RequestCount: // intentionally fall through to reuse the same routine as RetryCount
case RetryCount:
return metricOfRequestOrRetryCount(predefined, request, response);
case ThrottledRetryCount: // drop through
case RetryCapacityConsumed:
return counterMetricOf(predefined, request, response, EXCLUDE_REQUEST_TYPE);
case ResponseProcessingTime: // drop through
case RequestSigningTime: // drop through
return latencyMetricOf(predefined, request, response, EXCLUDE_REQUEST_TYPE);
case ClientExecuteTime:
return latencyOfClientExecuteTime(request, response);
case HttpClientSendRequestTime:
case HttpClientReceiveResponseTime:
case HttpRequestTime:
case HttpSocketReadTime:
return latencyMetricOf(predefined, request, response, INCLUDE_REQUEST_TYPE);
case Exception:
case ThrottleException:
return counterMetricOf(predefined, request, response, INCLUDE_REQUEST_TYPE);
default:
break;
}
}
// Predefined metrics for specific service clients
for (AWSMetricTransformerFactory aws: AWSMetricTransformerFactory.values()) {
if (metricType.name().startsWith(aws.name())) {
List metricData = aws.getRequestMetricTransformer()
.toMetricData(metricType, request, response);
if (metricData != null)
return metricData;
break;
}
}
if (log.isDebugEnabled()) {
AmazonWebServiceRequest origReq = request == null ? null : request
.getOriginalRequest();
String reqClassName = origReq == null ? null : origReq.getClass().getName();
log.debug("No request metric transformer can be found for metric type "
+ metricType.name() + " for " + reqClassName);
}
return Collections.emptyList();
}
/**
* Returns a list with a single metric datum for the specified retry or
* request count predefined metric; or an empty list if there is none.
*
* @param metricType
* must be either {@link Field#RequestCount} or
* {@link Field#RetryCount}; or else GIGO.
*/
protected List metricOfRequestOrRetryCount(
Field metricType, Request> req, Object resp) {
AWSRequestMetrics m = req.getAWSRequestMetrics();
TimingInfo ti = m.getTimingInfo();
// Always retrieve the request count even for retry which is equivalent
// to the number of requests minus one.
Number counter = ti.getCounter(Field.RequestCount.name());
if (counter == null) {
// this is possible if one of the request handlers screwed up
return Collections.emptyList();
}
int requestCount = counter.intValue();
if (requestCount < 1) {
if (log.isDebugEnabled()) {
log.debug("request count must be at least one");
}
return Collections.emptyList();
}
final double count = metricType == Field.RequestCount
? requestCount
: requestCount-1 // retryCount = requestCount - 1
;
if (count < 1) {
return Collections.emptyList();
} else {
return Collections.singletonList(new MetricDatum()
.withMetricName(req.getServiceName())
.withDimensions(new Dimension()
.withName(Dimensions.MetricType.name())
.withValue(metricType.name()))
.withUnit(StandardUnit.Count)
.withValue(Double.valueOf(count))
.withTimestamp(endTimestamp(ti)))
;
}
}
protected List metricOfCount(
Field metricType, Request> req, Object resp) {
AWSRequestMetrics m = req.getAWSRequestMetrics();
TimingInfo ti = m.getTimingInfo();
Number counter = ti.getCounter(metricType.name());
if (counter == null) {
return Collections.emptyList();
}
final double count = counter.doubleValue();
if (count < 1) {
return Collections.emptyList();
} else {
return Collections.singletonList(new MetricDatum()
.withMetricName(req.getServiceName())
.withDimensions(new Dimension()
.withName(Dimensions.MetricType.name())
.withValue(metricType.name()))
.withUnit(StandardUnit.Count)
.withValue(Double.valueOf(count))
.withTimestamp(endTimestamp(ti)))
;
}
}
/**
* Returns all the latency metric data recorded for the specified metric
* event type; or an empty list if there is none. The number of metric datum
* in the returned list should be exactly one when there is no retries, or
* more than one when there are retries.
*
* @param includesRequestType
* true iff the "request" dimension is to be included;
*/
protected List latencyMetricOf(MetricType metricType,
Request> req, Object response, boolean includesRequestType) {
AWSRequestMetrics m = req.getAWSRequestMetrics();
TimingInfo root = m.getTimingInfo();
final String metricName = metricType.name();
List subMeasures =
root.getAllSubMeasurements(metricName);
if (subMeasures != null) {
List result =
new ArrayList(subMeasures.size());
for (TimingInfo sub : subMeasures) {
if (sub.isEndTimeKnown()) { // being defensive
List dims = new ArrayList();
dims.add(new Dimension()
.withName(Dimensions.MetricType.name())
.withValue(metricName));
// Either a non request type specific datum is created per
// sub-measurement, or a request type specific one is
// created but not both
if (includesRequestType) {
dims.add(new Dimension()
.withName(Dimensions.RequestType.name())
.withValue(requestType(req)));
}
MetricDatum datum = new MetricDatum()
.withMetricName(req.getServiceName())
.withDimensions(dims)
.withUnit(StandardUnit.Milliseconds)
.withValue(sub.getTimeTakenMillisIfKnown());
result.add(datum);
}
}
return result;
}
return Collections.emptyList();
}
/**
* Returns a request type specific metrics for
* {@link Field#ClientExecuteTime} which is special in the sense that it
* makes a more accurate measurement by taking the {@link TimingInfo} at the
* root into account.
*/
protected List latencyOfClientExecuteTime(Request> req, Object response) {
AWSRequestMetrics m = req.getAWSRequestMetrics();
TimingInfo root = m.getTimingInfo();
final String metricName = Field.ClientExecuteTime.name();
if (root.isEndTimeKnown()) { // being defensive
List dims = new ArrayList();
dims.add(new Dimension()
.withName(Dimensions.MetricType.name())
.withValue(metricName));
// request type specific
dims.add(new Dimension()
.withName(Dimensions.RequestType.name())
.withValue(requestType(req)));
MetricDatum datum = new MetricDatum()
.withMetricName(req.getServiceName())
.withDimensions(dims)
.withUnit(StandardUnit.Milliseconds)
.withValue(root.getTimeTakenMillisIfKnown());
return Collections.singletonList(datum);
}
return Collections.emptyList();
}
/**
* Returns the name of the type of request.
*/
private String requestType(Request> req) {
return req.getOriginalRequest().getClass().getSimpleName();
}
/**
* Returns a list of metric datum recorded for the specified counter metric
* type; or an empty list if there is none.
*
* @param includesRequestType
* true iff an additional metric datum is to be created that
* includes the "request" dimension
*/
protected List counterMetricOf(MetricType type,
Request> req, Object resp, boolean includesRequestType) {
AWSRequestMetrics m = req.getAWSRequestMetrics();
TimingInfo ti = m.getTimingInfo();
final String metricName = type.name();
Number counter = ti.getCounter(metricName);
if (counter == null) {
return Collections.emptyList();
}
int count = counter.intValue();
if (count < 1) {
if (log.isDebugEnabled()) {
log.debug("Count must be at least one");
}
return Collections.emptyList();
}
final List result = new ArrayList();
final Dimension metricDimension = new Dimension()
.withName(Dimensions.MetricType.name())
.withValue(metricName);
// non-request type specific metric datum
final MetricDatum first = new MetricDatum()
.withMetricName(req.getServiceName())
.withDimensions(metricDimension)
.withUnit(StandardUnit.Count)
.withValue(Double.valueOf(count))
.withTimestamp(endTimestamp(ti));
result.add(first);
if (includesRequestType) {
// additional request type specific metric datum
Dimension requestDimension = new Dimension()
.withName(Dimensions.RequestType.name())
.withValue(requestType(req));
final MetricDatum second =
newMetricDatum(first, metricDimension, requestDimension);
result.add(second);
}
return result;
}
}