All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.awssdk.metrics.internal.cloudwatch.PredefinedMetricTransformer Maven / Gradle / Ivy

/*
 * Copyright 2010-2017 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 software.amazon.awssdk.metrics.internal.cloudwatch;

import static software.amazon.awssdk.metrics.internal.cloudwatch.spi.MetricData.newMetricDatum;
import static software.amazon.awssdk.metrics.internal.cloudwatch.spi.RequestMetricTransformer.Utils.endTimestamp;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.AmazonWebServiceRequest;
import software.amazon.awssdk.Request;
import software.amazon.awssdk.annotation.ThreadSafe;
import software.amazon.awssdk.metrics.RequestMetricCollector;
import software.amazon.awssdk.metrics.internal.cloudwatch.spi.AwsMetricTransformerFactory;
import software.amazon.awssdk.metrics.internal.cloudwatch.spi.Dimensions;
import software.amazon.awssdk.metrics.spi.AwsRequestMetrics;
import software.amazon.awssdk.metrics.spi.AwsRequestMetrics.Field;
import software.amazon.awssdk.metrics.spi.MetricType;
import software.amazon.awssdk.metrics.spi.TimingInfo;
import software.amazon.awssdk.services.cloudwatch.model.Dimension;
import software.amazon.awssdk.services.cloudwatch.model.MetricDatum;
import software.amazon.awssdk.services.cloudwatch.model.StandardUnit;

/**
 * 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 {
    static final boolean INCLUDE_REQUEST_TYPE = true;
    static final boolean EXCLUDE_REQUEST_TYPE = !INCLUDE_REQUEST_TYPE;
    private static final Logger log = LoggerFactory.getLogger(PredefinedMetricTransformer.class);

    /**
     * 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, Object 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);
                case RequestCount:  // intentionally fall through to reuse the same routine as RetryCount
                case RetryCount:
                    return metricOfRequestOrRetryCount(predefined, request);
                case ThrottledRetryCount: // drop through
                case RetryCapacityConsumed:
                    return counterMetricOf(predefined, request, EXCLUDE_REQUEST_TYPE);
                case ResponseProcessingTime: // drop through
                case RequestSigningTime: // drop through
                    return latencyMetricOf(predefined, request, EXCLUDE_REQUEST_TYPE);
                case ClientExecuteTime:
                    return latencyOfClientExecuteTime(request);
                case HttpClientSendRequestTime:
                case HttpClientReceiveResponseTime:
                case HttpRequestTime:
                case HttpSocketReadTime:
                    return latencyMetricOf(predefined, request, INCLUDE_REQUEST_TYPE);
                case Exception:
                case ThrottleException:
                    return counterMetricOf(predefined, request, 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) {
        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) {
            LoggerFactory.getLogger(getClass()).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(MetricDatum.builder()
                    .metricName(req.getServiceName())
                    .dimensions(Dimension.builder()
                            .name(Dimensions.MetricType.name())
                            .value(metricType.name())
                            .build())
                    .unit(StandardUnit.Count)
                    .value(count)
                    .timestamp(endTimestamp(ti)).build());
        }
    }

    protected List metricOfCount(Field metricType, Request req) {
        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(MetricDatum.builder()
                    .metricName(req.getServiceName())
                    .dimensions(Dimension.builder()
                            .name(Dimensions.MetricType.name())
                            .value(metricType.name())
                            .build())
                    .unit(StandardUnit.Count)
                    .value(count)
                    .timestamp(endTimestamp(ti))
                    .build());
        }
    }

    /**
     * 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, 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(Dimension.builder()
                            .name(Dimensions.MetricType.name())
                            .value(metricName)
                            .build());
                    // 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(Dimension.builder()
                                .name(Dimensions.RequestType.name())
                                .value(requestType(req))
                                .build());
                    }
                    MetricDatum datum = MetricDatum.builder()
                            .metricName(req.getServiceName())
                            .dimensions(dims)
                            .unit(StandardUnit.Milliseconds)
                            .value(sub.getTimeTakenMillisIfKnown())
                            .build();
                    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) {
        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(Dimension.builder()
                             .name(Dimensions.MetricType.name())
                             .value(metricName)
                    .build());
            // request type specific
            dims.add(Dimension.builder()
                             .name(Dimensions.RequestType.name())
                             .value(requestType(req))
                    .build());
            MetricDatum datum = MetricDatum.builder()
                    .metricName(req.getServiceName())
                    .dimensions(dims)
                    .unit(StandardUnit.Milliseconds)
                    .value(root.getTimeTakenMillisIfKnown())
                    .build();
            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, 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) {
            LoggerFactory.getLogger(getClass()).debug("Count must be at least one");
            return Collections.emptyList();
        }
        final List result = new ArrayList();
        final Dimension metricDimension = Dimension.builder()
                .name(Dimensions.MetricType.name())
                .value(metricName)
                .build();
        // non-request type specific metric datum
        final MetricDatum first = MetricDatum.builder()
                .metricName(req.getServiceName())
                .dimensions(metricDimension)
                .unit(StandardUnit.Count)
                .value((double) count)
                .timestamp(endTimestamp(ti))
                .build();
        result.add(first);
        if (includesRequestType) {
            // additional request type specific metric datum
            Dimension requestDimension = Dimension.builder()
                    .name(Dimensions.RequestType.name())
                    .value(requestType(req))
                    .build();
            final MetricDatum second =
                    newMetricDatum(first, metricDimension, requestDimension);
            result.add(second);
        }
        return result;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy