com.amazonaws.metrics.internal.cloudwatch.PredefinedMetricTransformer.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.metrics.internal.cloudwatch.PredefinedMetricTransformer.java

Source

/*
 * Copyright 2010-2014 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 static com.amazonaws.metrics.internal.cloudwatch.spi.MetricData.newMetricDatum;
import static com.amazonaws.metrics.internal.cloudwatch.spi.RequestMetricTransformer.Utils.endTimestamp;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.annotation.ThreadSafe;

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;

/**
 * Used to transform the predefined metrics of the AWS SDK into instances of
 * {@link MetricDatum}.
 * 
 * See <a href=
 * "http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/publishingMetrics.html"
 * >http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/
 * publishingMetrics.html</a>
 * 
 * @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<MetricDatum> 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 thru to reuse the same routine as RetryCount
            case RetryCount:
                return metricOfRequestOrRetryCount(predefined, request, response);
            case ResponseProcessingTime: // drop thru
            case RequestSigningTime: // drop thru
                return latencyMetricOf(predefined, request, response, EXCLUDE_REQUEST_TYPE);
            case ClientExecuteTime:
                return latencyOfClientExecuteTime(request, response);
            case HttpClientSendRequestTime:
            case HttpClientReceiveResponseTime:
            case HttpRequestTime:
                return latencyMetricOf(predefined, request, response, INCLUDE_REQUEST_TYPE);
            case Exception:
                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<MetricDatum> 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<MetricDatum> 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) {
            LogFactory.getLog(getClass()).warn("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<MetricDatum> 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<MetricDatum> latencyMetricOf(MetricType metricType, Request<?> req, Object response,
            boolean includesRequestType) {
        AWSRequestMetrics m = req.getAWSRequestMetrics();
        TimingInfo root = m.getTimingInfo();
        final String metricName = metricType.name();
        List<TimingInfo> subMeasures = root.getAllSubMeasurements(metricName);
        if (subMeasures != null) {
            List<MetricDatum> result = new ArrayList<MetricDatum>(subMeasures.size());
            for (TimingInfo sub : subMeasures) {
                if (sub.isEndTimeKnown()) { // being defensive
                    List<Dimension> dims = new ArrayList<Dimension>();
                    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<MetricDatum> 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<Dimension> dims = new ArrayList<Dimension>();
            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<MetricDatum> 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) {
            LogFactory.getLog(getClass()).warn("Count must be at least one");
            return Collections.emptyList();
        }
        final List<MetricDatum> result = new ArrayList<MetricDatum>();
        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;
    }
}