com.github.lpezet.antiope.metrics.aws.spi.PredefinedMetricTransformer.java Source code

Java tutorial

Introduction

Here is the source code for com.github.lpezet.antiope.metrics.aws.spi.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.github.lpezet.antiope.metrics.aws.spi;

import static com.github.lpezet.antiope.metrics.aws.spi.MetricData.newMetricDatum;
import static com.github.lpezet.antiope.metrics.aws.spi.IMetricTransformer.Utils.endTimestamp;

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

import org.apache.commons.logging.LogFactory;
import org.apache.http.annotation.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.metrics.RequestMetricCollector;
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.github.lpezet.antiope.be.APIWebServiceRequest;
import com.github.lpezet.antiope.dao.Request;
import com.github.lpezet.antiope.dao.Response;
import com.github.lpezet.antiope.metrics.APIRequestMetrics;
import com.github.lpezet.antiope.metrics.IMetrics;
import com.github.lpezet.antiope.metrics.MetricType;
import com.github.lpezet.antiope.metrics.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 implements IMetricTransformer {
    private static final Logger LOGGER = LoggerFactory.getLogger(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 pMetricType the request metric type
     */
    public List<MetricDatum> toMetricData(MetricType pMetricType, Request<?> pRequest, Response<?> pResponse) {
        if (pMetricType instanceof APIRequestMetrics) {
            // Predefined metrics across all aws http clients
            APIRequestMetrics oPredefined = (APIRequestMetrics) pMetricType;
            switch (oPredefined) {
            case HttpClientRetryCount:
            case HttpClientPoolAvailableCount:
            case HttpClientPoolLeasedCount:
            case HttpClientPoolPendingCount:
                return metricOfCount(oPredefined, pRequest, pResponse);
            case RequestCount: // intentionally fall thru to reuse the same routine as RetryCount
            case RetryCount:
                return metricOfRequestOrRetryCount(oPredefined, pRequest, pResponse);
            case ResponseProcessingTime: // drop thru
            case RequestSigningTime: // drop thru
                return latencyMetricOf(oPredefined, pRequest, pResponse, EXCLUDE_REQUEST_TYPE);
            case ClientExecuteTime:
                return latencyOfClientExecuteTime(pRequest, pResponse);
            case HttpClientSendRequestTime:
            case HttpClientReceiveResponseTime:
            case HttpRequestTime:
                return latencyMetricOf(oPredefined, pRequest, pResponse, INCLUDE_REQUEST_TYPE);
            case Exception:
            case ThrottleException:
                return counterMetricOf(oPredefined, pRequest, pResponse, INCLUDE_REQUEST_TYPE);
            default:
                break;
            }
        }
        // Predefined metrics for specific service clients
        /*
        for (MetricTransformerFactory aws: MetricTransformerFactory.values()) {
        if (metricType.name().startsWith(aws.name())) {
            List<MetricDatum> metricData = aws.getRequestMetricTransformer()
                    .toMetricData(metricType, request, response);
            if (metricData != null)
                return metricData;
            break;
        }
        }
        */
        if (LOGGER.isDebugEnabled()) {
            APIWebServiceRequest oOrigReq = pRequest == null ? null : pRequest.getOriginalRequest();
            String reqClassName = oOrigReq == null ? null : oOrigReq.getClass().getName();
            LOGGER.debug("No request metric transformer can be found for metric type " + pMetricType.name()
                    + " for " + reqClassName);
        }
        return Collections.emptyList();
    }

    @Override
    public boolean canHandle(MetricType pMetricType, Request<?> pRequest, Response<?> pResponse) {
        return (pMetricType instanceof APIRequestMetrics);
    }

    /**
     * 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 pMetricType
     *            must be either {@link Field#RequestCount} or
     *            {@link Field#RetryCount}; or else GIGO.
     */
    protected List<MetricDatum> metricOfRequestOrRetryCount(APIRequestMetrics pMetricType, Request<?> pReq,
            Object pResp) {
        IMetrics m = pReq.getMetrics();
        TimingInfo ti = m.getTimingInfo();
        // Always retrieve the request count even for retry which is equivalent
        // to the number of requests minus one.
        Number oCounter = ti.getCounter(Field.RequestCount.name());
        if (oCounter == null) {
            // this is possible if one of the request handlers screwed up
            return Collections.emptyList();
        }
        int oRequestCount = oCounter.intValue();
        if (oRequestCount < 1) {
            LogFactory.getLog(getClass()).warn("request count must be at least one");
            return Collections.emptyList();
        }
        final double oCount = pMetricType == APIRequestMetrics.RequestCount ? oRequestCount : oRequestCount - 1 // retryCount = requestCount - 1
        ;
        if (oCount < 1) {
            return Collections.emptyList();
        } else {
            return Collections.singletonList(new MetricDatum().withMetricName(pReq.getServiceName())
                    .withDimensions(
                            new Dimension().withName(Dimensions.MetricType.name()).withValue(pMetricType.name()))
                    .withUnit(StandardUnit.Count).withValue(Double.valueOf(oCount))
                    .withTimestamp(endTimestamp(ti)));
        }
    }

    protected List<MetricDatum> metricOfCount(APIRequestMetrics pMetricType, Request<?> pReq, Object pResp) {
        IMetrics m = pReq.getMetrics();
        TimingInfo ti = m.getTimingInfo();
        Number oCounter = ti.getCounter(pMetricType.name());
        if (oCounter == null) {
            return Collections.emptyList();
        }
        final double oCount = oCounter.doubleValue();
        if (oCount < 1) {
            return Collections.emptyList();
        } else {
            return Collections.singletonList(new MetricDatum().withMetricName(pReq.getServiceName())
                    .withDimensions(
                            new Dimension().withName(Dimensions.MetricType.name()).withValue(pMetricType.name()))
                    .withUnit(StandardUnit.Count).withValue(Double.valueOf(oCount))
                    .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 pIncludesRequestType
     *            true iff the "request" dimension is to be included;
     */
    protected List<MetricDatum> latencyMetricOf(MetricType pMetricType, Request<?> pReq, Object pResponse,
            boolean pIncludesRequestType) {
        IMetrics m = pReq.getMetrics();
        TimingInfo oRoot = m.getTimingInfo();
        final String oMetricName = pMetricType.name();
        List<TimingInfo> oSubMeasures = oRoot.getAllSubMeasurements(oMetricName);
        if (oSubMeasures != null) {
            List<MetricDatum> oResult = new ArrayList<MetricDatum>(oSubMeasures.size());
            for (TimingInfo oSub : oSubMeasures) {
                if (oSub.isEndTimeKnown()) { // being defensive
                    List<Dimension> oDims = new ArrayList<Dimension>();
                    oDims.add(new Dimension().withName(Dimensions.MetricType.name()).withValue(oMetricName));
                    // Either a non request type specific datum is created per
                    // sub-measurement, or a request type specific one is 
                    // created but not both
                    if (pIncludesRequestType) {
                        oDims.add(new Dimension().withName(Dimensions.RequestType.name())
                                .withValue(requestType(pReq)));
                    }
                    MetricDatum oDatum = new MetricDatum().withMetricName(pReq.getServiceName())
                            .withDimensions(oDims).withUnit(StandardUnit.Milliseconds)
                            .withValue(oSub.getTimeTakenMillisIfKnown());
                    oResult.add(oDatum);
                }
            }
            return oResult;
        }
        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<?> pReq, Object pResponse) {
        IMetrics m = pReq.getMetrics();
        TimingInfo oRoot = m.getTimingInfo();
        final String oMetricName = Field.ClientExecuteTime.name();
        if (oRoot.isEndTimeKnown()) { // being defensive
            List<Dimension> oDims = new ArrayList<Dimension>();
            oDims.add(new Dimension().withName(Dimensions.MetricType.name()).withValue(oMetricName));
            // request type specific
            oDims.add(new Dimension().withName(Dimensions.RequestType.name()).withValue(requestType(pReq)));
            MetricDatum oDatum = new MetricDatum().withMetricName(pReq.getServiceName()).withDimensions(oDims)
                    .withUnit(StandardUnit.Milliseconds).withValue(oRoot.getTimeTakenMillisIfKnown());
            return Collections.singletonList(oDatum);
        }
        return Collections.emptyList();
    }

    /**
     * Returns the name of the type of request.
     */
    private String requestType(Request<?> pReq) {
        return pReq.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 pIncludesRequestType
     *            true iff an additional metric datum is to be created that
     *            includes the "request" dimension
     */
    protected List<MetricDatum> counterMetricOf(MetricType pType, Request<?> pReq, Object pResp,
            boolean pIncludesRequestType) {
        IMetrics m = pReq.getMetrics();
        TimingInfo ti = m.getTimingInfo();
        final String oMetricName = pType.name();
        Number oCounter = ti.getCounter(oMetricName);
        if (oCounter == null) {
            return Collections.emptyList();
        }
        int oCount = oCounter.intValue();
        if (oCount < 1) {
            LogFactory.getLog(getClass()).warn("Count must be at least one");
            return Collections.emptyList();
        }
        final List<MetricDatum> oResult = new ArrayList<MetricDatum>();
        final Dimension oMetricDimension = new Dimension().withName(Dimensions.MetricType.name())
                .withValue(oMetricName);
        // non-request type specific metric datum
        final MetricDatum oFirst = new MetricDatum().withMetricName(pReq.getServiceName())
                .withDimensions(oMetricDimension).withUnit(StandardUnit.Count).withValue(Double.valueOf(oCount))
                .withTimestamp(endTimestamp(ti));
        oResult.add(oFirst);
        if (pIncludesRequestType) {
            // additional request type specific metric datum
            Dimension oRequestDimension = new Dimension().withName(Dimensions.RequestType.name())
                    .withValue(requestType(pReq));
            final MetricDatum oSecond = newMetricDatum(oFirst, oMetricDimension, oRequestDimension);
            oResult.add(oSecond);
        }
        return oResult;
    }
}