Package com.blacklocus.metrics

Source Code of com.blacklocus.metrics.CloudWatchReporter

/**
* Copyright 2013 BlackLocus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.blacklocus.metrics;

import com.amazonaws.services.cloudwatch.AmazonCloudWatchAsync;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.MetricDatum;
import com.amazonaws.services.cloudwatch.model.PutMetricDataRequest;
import com.amazonaws.services.cloudwatch.model.StandardUnit;
import com.amazonaws.services.cloudwatch.model.StatisticSet;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Counting;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Sampling;
import com.codahale.metrics.ScheduledReporter;
import com.codahale.metrics.Snapshot;
import com.codahale.metrics.Timer;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
* @author Jason Dunkelberger (dirkraft)
*/
public class CloudWatchReporter extends ScheduledReporter {

    private static final Logger LOG = LoggerFactory.getLogger(CloudWatchReporter.class);

    public static final String NAME_DELIMITER = " ";
    public static final String NAME_SEPARATOR = "=";

    public static final String METRIC_TYPE = "type";


    private final AmazonCloudWatchAsync cloudWatch;
    private final String metricNamespace;

    private final Map<Counting, Long> lastPolledCounts = new HashMap<Counting, Long>();

    /**
     * Creates a new {@link ScheduledReporter} instance.
     *
     * @param registry        the {@link MetricRegistry} containing the metrics this reporter will report
     * @param metricNamespace (optional) CloudWatch metric namespace that all metrics reported by this reporter will
     *                        fall under
     */
    public CloudWatchReporter(MetricRegistry registry, String metricNamespace, AmazonCloudWatchAsync cloudWatch) {
        super(registry, "CloudWatchReporter:" + metricNamespace, ALL, TimeUnit.MINUTES, TimeUnit.MINUTES);
        this.cloudWatch = cloudWatch;
        this.metricNamespace = metricNamespace;
    }


    @Override
    public void report(SortedMap<String, Gauge> gauges,
                       SortedMap<String, Counter> counters,
                       SortedMap<String, Histogram> histograms,
                       SortedMap<String, Meter> meters,
                       SortedMap<String, Timer> timers) {

        try {
            List<MetricDatum> data = new ArrayList<MetricDatum>(gauges.size() + counters.size() + meters.size() +
                    2 * histograms.size() + 2 * timers.size()); // something like that


            for (Map.Entry<String, Gauge> gaugeEntry : gauges.entrySet()) {
                DemuxedKey key = new DemuxedKey(gaugeEntry.getKey());
                Gauge gauge = gaugeEntry.getValue();

                Object valueObj = gauge.getValue();
                if (valueObj == null) {
                    continue;
                }

                String valueStr = valueObj.toString();
                if (NumberUtils.isNumber(valueStr)) {
                    Number value = NumberUtils.createNumber(valueStr);
                    data.add(key.newDatum("gauge").withValue(value.doubleValue()));
                }
            }

            for (Map.Entry<String, Counter> counterEntry : counters.entrySet()) {
                DemuxedKey key = new DemuxedKey(counterEntry.getKey());
                Counter counter = counterEntry.getValue();

                count(key, counter, "counterSum", data);
            }

            for (Map.Entry<String, Meter> meterEntry : meters.entrySet()) {
                DemuxedKey key = new DemuxedKey(meterEntry.getKey());
                Meter meter = meterEntry.getValue();

                count(key, meter, "meterSum", data);
            }

            for (Map.Entry<String, Histogram> histogramEntry : histograms.entrySet()) {
                DemuxedKey key = new DemuxedKey(histogramEntry.getKey());
                Histogram histogram = histogramEntry.getValue();

                count(key, histogram, "histogramCount", data);
                sampling(key, histogram, 1.0, "histogramSet", data);
            }

            for (Map.Entry<String, Timer> timerEntry : timers.entrySet()) {
                DemuxedKey key = new DemuxedKey(timerEntry.getKey());
                Timer timer = timerEntry.getValue();

                count(key, timer, "timerCount", data);
                // nanos -> millis
                sampling(key, timer, 0.000001, "timerSet", data);

            }


            // Each CloudWatch API request may contain at maximum 20 datums.
            List<List<MetricDatum>> dataPartitions = Lists.partition(data, 20);
            List<Future<?>> cloudWatchFutures = new ArrayList<Future<?>>(dataPartitions.size());

            for (List<MetricDatum> dataSubset : dataPartitions) {
                cloudWatchFutures.add(cloudWatch.putMetricDataAsync(new PutMetricDataRequest()
                        .withNamespace(metricNamespace)
                        .withMetricData(dataSubset)));
            }
            for (Future<?> cloudWatchFuture : cloudWatchFutures) {
                // We can't let an exception leak out of here, or else the reporter will cease running per mechanics of
                // java.util.concurrent.ScheduledExecutorService.scheduleAtFixedRate(Runnable, long, long, TimeUnit unit)
                try {
                    // See what happened in case of an error.
                    cloudWatchFuture.get();
                } catch (Exception e) {
                    LOG.error("Exception reporting metrics to CloudWatch. The data sent in this CloudWatch API request " +
                            "may have been discarded.", e);
                }
            }

            LOG.info("Sent {} metric data to CloudWatch. namespace: {}", data.size(), metricNamespace);

        } catch (RuntimeException e) {
            LOG.error("Error marshalling CloudWatch metrics.", e);
        }
    }


    private void count(DemuxedKey key, Counting metric, String type, Collection<MetricDatum> data) {
        long count = metric.getCount();
        Long lastCount = lastPolledCounts.get(metric);
        if (lastCount == null) {
            lastCount = 0L;
        }
        double diff = count - lastCount;
        lastPolledCounts.put(metric, count);

        data.add(key.newDatum(type).withValue(diff).withUnit(StandardUnit.Count));
    }

    /**
     * @param rescale the submitted sum by this multiplier. 1.0 is the identity (no rescale).
     */
    private void sampling(DemuxedKey key, Sampling metric, double rescale, String type, Collection<MetricDatum> data) {
        Snapshot snapshot = metric.getSnapshot();
        data.add(key.newDatum(type).withStatisticValues(new StatisticSet()
                .withSum(sum(snapshot.getValues()) * rescale)
                .withSampleCount((double) snapshot.size())
                .withMinimum((double) snapshot.getMin())
                .withMaximum((double) snapshot.getMax())));
    }

    private long sum(long[] values) {
        long sum = 0L;
        for (long value : values) sum += value;
        return sum;
    }

    static final MetricFilter ALL = new MetricFilter() {
        @Override
        public boolean matches(String name, Metric metric) {
            return true;
        }
    };

    static class DemuxedKey {

        final String name;
        final Collection<Dimension> dimensions = new ArrayList<Dimension>();

        DemuxedKey(String s) {
            String[] segments = s.split(NAME_DELIMITER);
            StringBuilder name = new StringBuilder(segments[0]);

            for (int i = 1; i < segments.length; i++) {
                String segment = segments[i];

                if (segment.contains(NAME_SEPARATOR)) {
                    String[] dimension = segment.split(NAME_SEPARATOR, 2);
                    dimensions.add(new Dimension().withName(dimension[0]).withValue(dimension[1]));
                } else {
                    name.append(" ").append(segment);
                }
            }

            this.name = name.toString();
        }

        /**
         * @return new MetricDatum initialized with {@link #name} and {@link #dimensions}
         */
        MetricDatum newDatum(String type) {
            List<Dimension> dimensions = new ArrayList<Dimension>(this.dimensions.size() + 1);
            dimensions.add(new Dimension().withName(METRIC_TYPE).withValue(type));
            dimensions.addAll(this.dimensions);
            return new MetricDatum().withMetricName(name).withDimensions(dimensions);
        }
    }

}
TOP

Related Classes of com.blacklocus.metrics.CloudWatchReporter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.