com.eucalyptus.cluster.callback.reporting.CloudWatchHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.cluster.callback.reporting.CloudWatchHelper.java

Source

/*************************************************************************
 * Copyright 2009-2015 Eucalyptus Systems, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 *
 * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
 * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
 * additional information or have any questions.
 ************************************************************************/
package com.eucalyptus.cluster.callback.reporting;

import static com.eucalyptus.compute.common.internal.vm.VmInstance.VmStateSet.TORNDOWN;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;

import javax.annotation.Nullable;

import org.apache.log4j.Logger;
import org.hibernate.criterion.Restrictions;

import com.eucalyptus.cloudwatch.common.CloudWatch;
import com.eucalyptus.cloudwatch.common.msgs.Dimension;
import com.eucalyptus.cloudwatch.common.msgs.Dimensions;
import com.eucalyptus.cloudwatch.common.msgs.MetricData;
import com.eucalyptus.cloudwatch.common.msgs.MetricDatum;
import com.eucalyptus.cloudwatch.common.msgs.PutMetricDataResponseType;
import com.eucalyptus.cloudwatch.common.msgs.PutMetricDataType;
import com.eucalyptus.cluster.callback.DescribeSensorCallback.GetTimestamp;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.Topology;
import com.eucalyptus.compute.common.internal.vm.VmRuntimeState;
import com.eucalyptus.entities.EntityCache;
import com.eucalyptus.reporting.event.InstanceUsageEvent;
import com.eucalyptus.util.CollectionUtils;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.HasName;
import com.eucalyptus.util.Pair;
import com.eucalyptus.util.TypeMapper;
import com.eucalyptus.util.TypeMappers;
import com.eucalyptus.util.async.AsyncRequests;
import com.eucalyptus.compute.common.internal.vm.VmInstance;
import com.eucalyptus.compute.common.internal.vm.VmInstanceTag;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;

import edu.ucsb.eucalyptus.msgs.BaseMessage;
import edu.ucsb.eucalyptus.msgs.DescribeSensorsResponse;
import edu.ucsb.eucalyptus.msgs.MetricCounterType;
import edu.ucsb.eucalyptus.msgs.MetricDimensionsType;
import edu.ucsb.eucalyptus.msgs.MetricDimensionsValuesType;
import edu.ucsb.eucalyptus.msgs.MetricsResourceType;
import edu.ucsb.eucalyptus.msgs.SensorsResourceType;

public class CloudWatchHelper {

    private InstanceInfoProvider instanceInfoProvider;

    public CloudWatchHelper(InstanceInfoProvider instanceInfoProvider) {
        this.instanceInfoProvider = instanceInfoProvider;

    }

    private static final Logger LOG = Logger.getLogger(CloudWatchHelper.class);
    private static final String RESOURCE_TYPE_INSTANCE = "instance";

    private static class DiskReadWriteMetricTypeCache {

        private Map<String, MetricDimensionsValuesType> eventMap = Maps.newConcurrentMap();

        private String mapKey(SensorsResourceType sensorData, MetricDimensionsType dimensionType,
                MetricDimensionsValuesType value) {

            String SEPARATOR = "|";
            // sensor data should include resource Uuid and resource name
            String resourceUUID = (sensorData != null) ? sensorData.getResourceUuid() : null;
            String resourceName = (sensorData != null) ? sensorData.getResourceName() : null;
            // dimension type should include dimension name
            String dimensionName = (dimensionType != null) ? dimensionType.getDimensionName() : null;
            // value should include timestamp
            String valueTimestampStr = (value != null && value.getTimestamp() != null)
                    ? value.getTimestamp().toString()
                    : null;
            return resourceUUID + SEPARATOR + resourceName + SEPARATOR + dimensionName + SEPARATOR
                    + valueTimestampStr;

        }

        public void putEventInCache(SensorsResourceType sensorData, MetricDimensionsType dimensionType,
                MetricDimensionsValuesType value) {
            eventMap.put(mapKey(sensorData, dimensionType, value), value);
        }

        public MetricDimensionsValuesType getEventFromCache(SensorsResourceType sensorData,
                MetricDimensionsType dimensionType, MetricDimensionsValuesType value) {
            return eventMap.get(mapKey(sensorData, dimensionType, value));
        }
    }

    private static class EC2DiskMetricCacheKey {
        private String resourceUuid;
        private String resourceName;
        private Long currentTimeStamp;
        private String metricName;

        private EC2DiskMetricCacheKey(String resourceUuid, String resourceName, Long currentTimeStamp,
                String metricName) {
            super();
            this.resourceUuid = resourceUuid;
            this.resourceName = resourceName;
            this.currentTimeStamp = currentTimeStamp;
            this.metricName = metricName;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((currentTimeStamp == null) ? 0 : currentTimeStamp.hashCode());
            result = prime * result + ((metricName == null) ? 0 : metricName.hashCode());
            result = prime * result + ((resourceName == null) ? 0 : resourceName.hashCode());
            result = prime * result + ((resourceUuid == null) ? 0 : resourceUuid.hashCode());
            return result;
        }

        public String getResourceUuid() {
            return resourceUuid;
        }

        public String getResourceName() {
            return resourceName;
        }

        public Long getCurrentTimeStamp() {
            return currentTimeStamp;
        }

        public String getMetricName() {
            return metricName;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            EC2DiskMetricCacheKey other = (EC2DiskMetricCacheKey) obj;
            if (currentTimeStamp == null) {
                if (other.currentTimeStamp != null)
                    return false;
            } else if (!currentTimeStamp.equals(other.currentTimeStamp))
                return false;
            if (metricName == null) {
                if (other.metricName != null)
                    return false;
            } else if (!metricName.equals(other.metricName))
                return false;
            if (resourceName == null) {
                if (other.resourceName != null)
                    return false;
            } else if (!resourceName.equals(other.resourceName))
                return false;
            if (resourceUuid == null) {
                if (other.resourceUuid != null)
                    return false;
            } else if (!resourceUuid.equals(other.resourceUuid))
                return false;
            return true;
        }

    }

    private static class EC2DiskMetricCacheValue {
        private EC2DiskMetricCacheKey key;
        private Double value;

        public EC2DiskMetricCacheValue(EC2DiskMetricCacheKey key, Double value) {
            this.key = key;
            this.value = value;
        }

        public void addValue(Double currentValue) {
            this.value += currentValue;
        }

        public String getMetricName() {
            return key.getMetricName();
        }

        public Double getValue() {
            return value;
        }

        public Long getTimeStamp() {
            return key.getCurrentTimeStamp();
        }

        public String getResourceName() {
            return key.getResourceName();
        }

        public String getResourceUuid() {
            return key.getResourceUuid();
        }

    }

    private static class EC2DiskMetricCache {
        private ConcurrentMap<EC2DiskMetricCacheKey, EC2DiskMetricCacheValue> cacheMap = Maps.newConcurrentMap();

        public void addToMetric(String resourceUuid, String resourceName, String metricName, Double currentValue,
                Long currentTimeStamp) {
            EC2DiskMetricCacheKey key = new EC2DiskMetricCacheKey(resourceUuid, resourceName, currentTimeStamp,
                    metricName);
            EC2DiskMetricCacheValue value = cacheMap.get(key);
            if (value == null) {
                cacheMap.put(key, new EC2DiskMetricCacheValue(key, currentValue));
            } else {
                value.addValue(currentValue);
            }
        }

        public void initializeMetrics(String resourceUuid, String resourceName, Long currentTimeStamp) {
            for (String metricName : EC2_DISK_METRICS) {
                addToMetric(resourceUuid, resourceName, metricName, 0.0, currentTimeStamp);
            }
        }

        public Collection<Supplier<InstanceUsageEvent>> getMetrics() {
            ArrayList<Supplier<InstanceUsageEvent>> suppliers = Lists.newArrayList();
            for (final EC2DiskMetricCacheValue value : cacheMap.values()) {
                suppliers.add(new Supplier<InstanceUsageEvent>() {
                    @Override
                    public InstanceUsageEvent get() {
                        return new InstanceUsageEvent(value.getResourceUuid(), value.getResourceName(),
                                value.getMetricName(), 0L, // TODO: deal with sequence numbers?
                                "Ephemeral", value.getValue(), value.getTimeStamp());
                    }
                });
            }
            return suppliers;
        }

    }

    private Supplier<InstanceUsageEvent> combineReadWriteDiskMetric(String readMetricName, String writeMetricName,
            ConcurrentMap<String, DiskReadWriteMetricTypeCache> metricCacheMap, String combinedMetricName,
            MetricsResourceType metricType, SensorsResourceType sensorData, MetricDimensionsType dimensionType,
            MetricDimensionsValuesType thisValueType) throws Exception {
        metricCacheMap.putIfAbsent(readMetricName, new DiskReadWriteMetricTypeCache());
        metricCacheMap.putIfAbsent(writeMetricName, new DiskReadWriteMetricTypeCache());

        String matchingMetricName = null;
        String otherMetricName = null;
        if (metricType.getMetricName().equals(readMetricName)) {
            matchingMetricName = readMetricName;
            otherMetricName = writeMetricName;
        } else if (metricType.getMetricName().equals(writeMetricName)) {
            matchingMetricName = writeMetricName;
            otherMetricName = readMetricName;
        }
        if (matchingMetricName != null && otherMetricName != null) {
            metricCacheMap.get(matchingMetricName).putEventInCache(sensorData, dimensionType, thisValueType);
            MetricDimensionsValuesType otherValueType = metricCacheMap.get(otherMetricName)
                    .getEventFromCache(sensorData, dimensionType, thisValueType);
            if (otherValueType != null) {
                return createDiskOpsCacheSupplier(sensorData, combinedMetricName, dimensionType,
                        thisValueType.getValue() + otherValueType.getValue(),
                        thisValueType.getTimestamp().getTime());
            }
        }
        return null;
    }

    private Supplier<InstanceUsageEvent> createDiskOpsCacheSupplier(final SensorsResourceType sensorData,
            final String combinedMetricName, final MetricDimensionsType dimensionType, final Double value,
            final Long usageTimeStamp) {

        return new Supplier<InstanceUsageEvent>() {
            @Override
            public InstanceUsageEvent get() {
                return new InstanceUsageEvent(sensorData.getResourceUuid(), sensorData.getResourceName(),
                        combinedMetricName, dimensionType.getSequenceNum(), dimensionType.getDimensionName(), value,
                        usageTimeStamp);
            }
        };
    }

    private static final Set<String> EC2_DISK_METRICS = ImmutableSet.of("DiskReadOps", "DiskWriteOps",
            "DiskReadBytes", "DiskWriteBytes");

    private static final Set<String> UNSUPPORTED_EC2_METRICS = ImmutableSet.of("NetworkInExternal",
            "NetworkOutExternal", "VolumeQueueLength", "VolumeTotalReadTime", "VolumeTotalWriteTime",
            "VolumeTotalReadWriteTime", "VolumeConsumedReadWriteOps", "DiskTotalReadTime", "DiskTotalWriteTime",
            "DiskConsumedReadWriteOps");

    private static final Map<String, String> ABSOLUTE_METRICS = new ImmutableMap.Builder<String, String>()
            .put("CPUUtilization", "CPUUtilizationMSAbsolute") // this is actually the data in milliseconds, not percentage
            .put("VolumeReadOps", "VolumeReadOpsAbsolute") // this is actually the total volume read Ops since volume creation, not for the period
            .put("VolumeWriteOps", "VolumeWriteOpsAbsolute") // this is actually the total volume write Ops since volume creation, not for the period
            .put("VolumeConsumedReadWriteOps", "VolumeConsumedReadWriteOpsAbsolute") // this is actually the total volume consumed read write Ops since volume creation, not for the period
            .put("VolumeReadBytes", "VolumeReadBytesAbsolute") // this is actually the total volume read bytes since volume creation, not for the period
            .put("VolumeWriteBytes", "VolumeWriteBytesAbsolute") // this is actually the total volume write bytes since volume creation, not for the period
            .put("VolumeTotalReadTime", "VolumeTotalReadTimeAbsolute") // this is actually the total volume read time since volume creation, not for the period
            .put("VolumeTotalWriteTime", "VolumeTotalWriteTimeAbsolute") // this is actually the total volume read and write time since volume creation, not for the period
            .put("VolumeTotalReadWriteTime", "VolumeTotalReadWriteTimeAbsolute") // this is actually the total volume read and write time since volume creation, not for the period
            .put("DiskReadOps", "DiskReadOpsAbsolute") // this is actually the total disk read Ops since instance creation, not for the period
            .put("DiskWriteOps", "DiskWriteOpsAbsolute") // this is actually the total disk write Ops since instance creation, not for the period
            .put("DiskReadBytes", "DiskReadBytesAbsolute") // this is actually the total disk read bytes since instance creation, not for the period
            .put("DiskWriteBytes", "DiskWriteBytesAbsolute") // this is actually the total disk write bytes since instance creation, not for the period
            .put("NetworkIn", "NetworkInAbsolute") // this is actually the total network in bytes since instance creation, not for the period
            .put("NetworkOut", "NetworkOutAbsolute") // this is actually the total network out bytes since instance creation, not for the period
            .build();

    private static final Map<String, String> metricsToUnitTypes = new ImmutableMap.Builder<String, String>()
            .putAll(metricsToUnitType(Bytes.class)).putAll(metricsToUnitType(Count.class))
            .putAll(metricsToUnitType(Seconds.class)).putAll(metricsToUnitType(Percent.class)).build();

    private static <E extends Enum<E>> Map<String, String> metricsToUnitType(final Class<E> unitEnum) {
        return CollectionUtils.putAll(EnumSet.allOf(unitEnum), Maps.<String, String>newHashMap(),
                Functions.toStringFunction(), Functions.constant(unitEnum.getSimpleName()));
    }

    private enum Bytes {
        VolumeReadBytes, VolumeWriteBytes, DiskReadBytes, DiskWriteBytes, NetworkIn, NetworkOut,
    }

    private enum Count {
        VolumeWriteOps, VolumeQueueLength, VolumeConsumedReadWriteOps, DiskReadOps, DiskWriteOps, StatusCheckFailed, StatusCheckFailed_Instance, StatusCheckFailed_System, VolumeReadOps
    }

    private enum Seconds {
        VolumeTotalReadTime, VolumeTotalWriteTime, VolumeTotalReadWriteTime, VolumeIdleTime
    }

    private enum Percent {
        VolumeThroughputPercentage, CPUUtilization
    }

    private String containsUnitType(final String metricType) {
        final String unitType = metricsToUnitTypes.get(metricType);
        if (unitType == null) {
            throw new NoSuchElementException("Unknown system unit type : " + metricType);
        }
        return unitType;
    }

    public static ServiceConfiguration createServiceConfiguration() {
        return Topology.lookup(CloudWatch.class);
    }

    public void sendSystemMetric(ServiceConfiguration serviceConfiguration, PutMetricDataType putMetricData)
            throws Exception {
        BaseMessage reply = AsyncRequests.dispatch(serviceConfiguration, putMetricData).get();
        if (!(reply instanceof PutMetricDataResponseType)) {
            throw new EucalyptusCloudException("Unable to send put metric data to cloud watch");
        }
    }

    public interface InstanceInfoProvider {
        String getAutoscalingGroupName(String instanceId);

        String getInstanceId(String instanceId);

        String getImageId(String instanceId);

        String getVmTypeDisplayName(String instanceId);

        String getAccountNumber(String instanceId);

        Integer getStatusCheckFailed(String instanceId);

        Integer getInstanceStatusCheckFailed(String instanceId);

        Integer getSystemStatusCheckFailed(String instanceId);

        boolean getMonitoring(String instanceId);
    }

    public static class DefaultInstanceInfoProvider implements InstanceInfoProvider {

        private static final EntityCache<VmInstance, CloudWatchInstanceInfo> instanceInfoCache = new EntityCache<>(
                VmInstance.named(null), Restrictions.not(VmInstance.criterion(TORNDOWN.array())),
                Sets.newHashSet("bootRecord.vmType"), Sets.newHashSet("networkGroups", "bootRecord.machineImage"),
                TypeMappers.lookup(VmInstance.class, CloudWatchInstanceInfo.class));

        private static final EntityCache<VmInstanceTag, CloudWatchInstanceGroupInfo> instanceGroupInfoCache = new EntityCache<>(
                VmInstanceTag.key("aws:autoscaling:groupName"),
                TypeMappers.lookup(VmInstanceTag.class, CloudWatchInstanceGroupInfo.class));

        private static final AtomicReference<Map<String, CloudWatchInstanceInfo>> instances = new AtomicReference<>(
                Collections.<String, CloudWatchInstanceInfo>emptyMap());

        private static final AtomicReference<Map<String, CloudWatchInstanceGroupInfo>> instanceGroups = new AtomicReference<>(
                Collections.<String, CloudWatchInstanceGroupInfo>emptyMap());

        @TypeMapper
        public enum VmInstanceToCloudWatchInstanceInfo implements Function<VmInstance, CloudWatchInstanceInfo> {
            INSTANCE;

            @Override
            public CloudWatchInstanceInfo apply(final VmInstance instance) {
                return new CloudWatchInstanceInfo(instance.getInstanceId(),
                        instance.getBootRecord().getDisplayMachineImageId(),
                        instance.getBootRecord().getVmType().getDisplayName(), instance.getOwnerAccountNumber(),
                        instance.getRuntimeState().getInstanceStatus() == VmRuntimeState.InstanceStatus.Ok ? 0 : 1,
                        Objects.firstNonNull(instance.getMonitoring(), Boolean.FALSE));
            }
        }

        @TypeMapper
        public enum VmInstanceTagToCloudWatchInstanceGroupInfo
                implements Function<VmInstanceTag, CloudWatchInstanceGroupInfo> {
            INSTANCE;

            @Override
            public CloudWatchInstanceGroupInfo apply(final VmInstanceTag instanceTag) {
                return new CloudWatchInstanceGroupInfo(instanceTag.getResourceId(), instanceTag.getValue());
            }
        }

        private static class CloudWatchInstanceInfo implements HasName<CloudWatchInstanceInfo> {
            private final String instanceId;
            private final String imageId;
            private final String vmTypeDisplayName;
            private final String accountId;
            private final Integer systemStatusCheckFailed; //
            private final boolean monitoring;

            public CloudWatchInstanceInfo(final String instanceId, final String imageId,
                    final String vmTypeDisplayName, final String accountId, final Integer systemStatusCheckFailed,
                    final boolean monitoring) {
                this.instanceId = instanceId;
                this.imageId = imageId;
                this.vmTypeDisplayName = vmTypeDisplayName;
                this.accountId = accountId;
                this.systemStatusCheckFailed = systemStatusCheckFailed;
                this.monitoring = monitoring;
            }

            public String getInstanceId() {
                return instanceId;
            }

            public String getImageId() {
                return imageId;
            }

            public String getVmTypeDisplayName() {
                return vmTypeDisplayName;
            }

            public String getAccountId() {
                return accountId;
            }

            public Integer getSystemStatusCheckFailed() {
                return systemStatusCheckFailed;
            }

            public boolean getMonitoring() {
                return monitoring;
            }

            @Override
            public String getName() {
                return getInstanceId();
            }

            @Override
            public int compareTo(final CloudWatchInstanceInfo o) {
                return getInstanceId().compareTo(o.getInstanceId());
            }
        }

        private static class CloudWatchInstanceGroupInfo implements HasName<CloudWatchInstanceGroupInfo> {
            private final String instanceId;
            private final String autoScalingGroup;

            public CloudWatchInstanceGroupInfo(final String instanceId, final String autoScalingGroup) {
                this.instanceId = instanceId;
                this.autoScalingGroup = autoScalingGroup;
            }

            public String getAutoScalingGroup() {
                return autoScalingGroup;
            }

            public String getInstanceId() {
                return instanceId;
            }

            @Override
            public String getName() {
                return getInstanceId();
            }

            @Override
            public int compareTo(final CloudWatchInstanceGroupInfo o) {
                return getInstanceId().compareTo(o.getInstanceId());
            }
        }

        public static synchronized void refresh() {
            instances.set(ImmutableMap.copyOf(CollectionUtils.putAll(instanceInfoCache.get(),
                    Maps.<String, CloudWatchInstanceInfo>newHashMap(), HasName.GET_NAME,
                    Functions.<CloudWatchInstanceInfo>identity())));
            instanceGroups.set(ImmutableMap.copyOf(CollectionUtils.putAll(instanceGroupInfoCache.get(),
                    Maps.<String, CloudWatchInstanceGroupInfo>newHashMap(), HasName.GET_NAME,
                    Functions.<CloudWatchInstanceGroupInfo>identity())));
        }

        private CloudWatchInstanceInfo lookupInstance(String instanceId) {
            final CloudWatchInstanceInfo instance = instances.get().get(instanceId);
            if (instance == null) {
                throw new NoSuchElementException(instanceId);
            }
            return instance;
        }

        private CloudWatchInstanceGroupInfo lookupInstanceGroup(String instanceId) {
            final CloudWatchInstanceGroupInfo instanceGroupInfo = instanceGroups.get().get(instanceId);
            if (instanceGroupInfo == null) {
                throw new NoSuchElementException(instanceId);
            }
            return instanceGroupInfo;
        }

        @Override
        public String getAutoscalingGroupName(String instanceId) {
            return lookupInstanceGroup(instanceId).getAutoScalingGroup();
        }

        @Override
        public String getInstanceId(String instanceId) {
            return lookupInstance(instanceId).getInstanceId();
        }

        @Override
        public String getImageId(String instanceId) {
            return lookupInstance(instanceId).getImageId();
        }

        @Override
        public String getVmTypeDisplayName(String instanceId) {
            return lookupInstance(instanceId).getVmTypeDisplayName();
        }

        @Override
        public String getAccountNumber(String instanceId) {
            return lookupInstance(instanceId).getAccountId();
        }

        @Override
        public Integer getStatusCheckFailed(final String instanceId) {
            return getSystemStatusCheckFailed(instanceId);
        }

        @Override
        public Integer getInstanceStatusCheckFailed(final String instanceId) {
            return 0;
        }

        @Override
        public Integer getSystemStatusCheckFailed(String instanceId) {
            return lookupInstance(instanceId).getSystemStatusCheckFailed();
        }

        @Override
        public boolean getMonitoring(String instanceId) {
            return lookupInstance(instanceId).getMonitoring();
        }
    }

    public List<AbsoluteMetricQueueItem> collectMetricData(final Collection<String> expectedInstanceIds,
            final DescribeSensorsResponse msg) throws Exception {
        ArrayList<AbsoluteMetricQueueItem> absoluteMetricQueueItems = new ArrayList<>();

        // cloudwatch metric caches
        final ConcurrentMap<String, DiskReadWriteMetricTypeCache> metricCacheMap = Maps.newConcurrentMap();

        final EC2DiskMetricCache ec2DiskMetricCache = new EC2DiskMetricCache();

        for (final SensorsResourceType sensorData : msg.getSensorsResources()) {
            if (!RESOURCE_TYPE_INSTANCE.equals(sensorData.getResourceType())
                    || !expectedInstanceIds.contains(sensorData.getResourceName()))
                continue;

            for (final MetricsResourceType metricType : sensorData.getMetrics()) {
                for (final MetricCounterType counterType : metricType.getCounters()) {
                    for (final MetricDimensionsType dimensionType : counterType.getDimensions()) {
                        // find and fire most recent value for metric/dimension
                        final List<MetricDimensionsValuesType> values = Lists
                                .newArrayList(stripMilliseconds(dimensionType.getValues()));

                        //CloudWatch use case of metric data
                        // best to enter older data first...
                        Collections.sort(values, Ordering.natural().onResultOf(GetTimestamp.INSTANCE));
                        for (final MetricDimensionsValuesType value : values) {
                            if (LOG.isTraceEnabled()) {
                                LOG.trace("ResourceUUID: " + sensorData.getResourceUuid());
                                LOG.trace("ResourceName: " + sensorData.getResourceName());
                                LOG.trace("Metric: " + metricType.getMetricName());
                                LOG.trace("Dimension: " + dimensionType.getDimensionName());
                                LOG.trace("Timestamp: " + value.getTimestamp());
                                LOG.trace("Value: " + value.getValue());
                            }
                            final Long currentTimeStamp = value.getTimestamp().getTime();
                            final Double currentValue = value.getValue();
                            if (currentValue == null) {
                                LOG.debug("Event received with null 'value', skipping for cloudwatch");
                                continue;
                            }
                            boolean hasEc2DiskMetricName = EC2_DISK_METRICS
                                    .contains(metricType.getMetricName().replace("Volume", "Disk"));
                            // Let's try only creating "zero" points for timestamps from disks
                            if (hasEc2DiskMetricName) {
                                ec2DiskMetricCache.initializeMetrics(sensorData.getResourceUuid(),
                                        sensorData.getResourceName(), currentTimeStamp); // Put a place holder in in case we don't have any non-EBS volumes
                            }
                            boolean isEbsMetric = dimensionType.getDimensionName().startsWith("vol-");
                            boolean isEc2DiskMetric = !isEbsMetric && hasEc2DiskMetricName;

                            if (isEbsMetric || !isEc2DiskMetric) {
                                addToQueueItems(absoluteMetricQueueItems, new Supplier<InstanceUsageEvent>() {
                                    @Override
                                    public InstanceUsageEvent get() {
                                        return new InstanceUsageEvent(sensorData.getResourceUuid(),
                                                sensorData.getResourceName(), metricType.getMetricName(),
                                                dimensionType.getSequenceNum(), dimensionType.getDimensionName(),
                                                currentValue, currentTimeStamp);
                                    }
                                });

                                if (isEbsMetric) {
                                    // special case to calculate VolumeConsumedReadWriteOps
                                    // As it is (VolumeThroughputPercentage / 100) * (VolumeReadOps + VolumeWriteOps), and we are hard coding
                                    // VolumeThroughputPercentage as 100%, we will just use VolumeReadOps + VolumeWriteOps

                                    // And just in case VolumeReadOps is called DiskReadOps we do both cases...
                                    addToQueueItems(absoluteMetricQueueItems,
                                            combineReadWriteDiskMetric("DiskReadOps", "DiskWriteOps",
                                                    metricCacheMap, "DiskConsumedReadWriteOps", metricType,
                                                    sensorData, dimensionType, value));
                                    addToQueueItems(absoluteMetricQueueItems,
                                            combineReadWriteDiskMetric("VolumeReadOps", "VolumeWriteOps",
                                                    metricCacheMap, "VolumeConsumedReadWriteOps", metricType,
                                                    sensorData, dimensionType, value));

                                    // Also need VolumeTotalReadWriteTime to compute VolumeIdleTime
                                    addToQueueItems(absoluteMetricQueueItems,
                                            combineReadWriteDiskMetric("VolumeTotalReadTime",
                                                    "VolumeTotalWriteTime", metricCacheMap,
                                                    "VolumeTotalReadWriteTime", metricType, sensorData,
                                                    dimensionType, value));
                                }
                            } else {
                                // see if it is a volume metric
                                String metricName = metricType.getMetricName().replace("Volume", "Disk");
                                ec2DiskMetricCache.addToMetric(sensorData.getResourceUuid(),
                                        sensorData.getResourceName(), metricName, currentValue, currentTimeStamp);
                            }
                        }
                    }
                }
            }

            if (Iterables
                    .tryFind(absoluteMetricQueueItems,
                            withMetric("AWS/EC2", null, "InstanceId", sensorData.getResourceName()))
                    .isPresent()
                    && !Iterables.tryFind(absoluteMetricQueueItems, withMetric("AWS/EC2",
                            Count.StatusCheckFailed.name(), "InstanceId", sensorData.getResourceName()))
                            .isPresent()) {
                absoluteMetricQueueItems.addAll(buildInstanceStatusPut(sensorData.getResourceName()));
            }
        }
        Collection<Supplier<InstanceUsageEvent>> ec2DiskMetrics = ec2DiskMetricCache.getMetrics();
        List<Supplier<InstanceUsageEvent>> ec2DiskMetricsSorted = Lists.newArrayList(ec2DiskMetrics);
        Collections.sort(ec2DiskMetricsSorted,
                Ordering.natural().onResultOf(new Function<Supplier<InstanceUsageEvent>, Long>() {
                    @Override
                    @Nullable
                    public Long apply(@Nullable Supplier<InstanceUsageEvent> supplier) {
                        return supplier.get().getValueTimestamp();
                    }
                }));
        for (Supplier<InstanceUsageEvent> ec2DiskMetric : ec2DiskMetricsSorted) {
            try {
                addToQueueItems(absoluteMetricQueueItems, ec2DiskMetric);
            } catch (Exception ex) {
                LOG.debug("Unable to add system metric " + ec2DiskMetric, ex);
            }
        }
        return absoluteMetricQueueItems;
    }

    private List<MetricDimensionsValuesType> stripMilliseconds(ArrayList<MetricDimensionsValuesType> values) {
        List<MetricDimensionsValuesType> newValues = new ArrayList<>();
        for (MetricDimensionsValuesType value : values) {
            MetricDimensionsValuesType newValue = new MetricDimensionsValuesType();
            // round down to the lowest second
            newValue.setTimestamp(
                    value.getTimestamp() != null ? new Date((value.getTimestamp().getTime() / 1000L) * 1000L)
                            : null);
            newValue.setValue(value.getValue());
            newValues.add(newValue);
        }
        return newValues;
    }

    public static List<PutMetricDataType> consolidatePutMetricDataList(
            final List<PutMetricDataType> putMetricDataList) {
        final int MAX_PUT_METRIC_DATA_ITEMS = 20;
        final LinkedHashMap<Pair<String, String>, List<MetricDatum>> metricDataMap = new LinkedHashMap<>();
        for (final PutMetricDataType putMetricData : putMetricDataList) {
            final Pair<String, String> userIdAndNamespacePair = Pair.pair(putMetricData.getUserId(),
                    putMetricData.getNamespace());
            if (!metricDataMap.containsKey(userIdAndNamespacePair)) {
                metricDataMap.put(userIdAndNamespacePair, new ArrayList<MetricDatum>());
            }
            metricDataMap.get(userIdAndNamespacePair).addAll(putMetricData.getMetricData().getMember());
        }
        final ArrayList<PutMetricDataType> retVal = new ArrayList<>();
        for (final Map.Entry<Pair<String, String>, List<MetricDatum>> metricDataEntry : metricDataMap.entrySet()) {
            for (final List<MetricDatum> datums : Iterables.partition(metricDataEntry.getValue(),
                    MAX_PUT_METRIC_DATA_ITEMS)) {
                final MetricData metricData = new MetricData();
                metricData.setMember(Lists.newArrayList(datums));
                final PutMetricDataType putMetricData = new PutMetricDataType();
                putMetricData.setUserId(metricDataEntry.getKey().getLeft());
                putMetricData.markPrivileged();
                putMetricData.setNamespace(metricDataEntry.getKey().getRight());
                putMetricData.setMetricData(metricData);
                retVal.add(putMetricData);
            }
        }
        return retVal;
    }

    private void addToQueueItems(List<AbsoluteMetricQueueItem> queueItems,
            Supplier<InstanceUsageEvent> cloudWatchSupplier) throws Exception {
        if (cloudWatchSupplier == null)
            return;
        final InstanceUsageEvent event = cloudWatchSupplier.get();
        LOG.trace(event);

        if (!instanceInfoProvider.getInstanceId(event.getInstanceId()).equals(event.getInstanceId())
                || !instanceInfoProvider.getMonitoring(event.getInstanceId())) {
            LOG.trace("Instance : " + event.getInstanceId() + " monitoring is not enabled");
            return;
        }

        if (instanceInfoProvider.getInstanceId(event.getInstanceId()).equals(event.getInstanceId())
                && instanceInfoProvider.getMonitoring(event.getInstanceId())) {

            AbsoluteMetricQueueItem newQueueItem = new AbsoluteMetricQueueItem();
            MetricDatum metricDatum = new MetricDatum();
            ArrayList<Dimension> dimArray = Lists.newArrayList();

            if (event.getDimension() != null && event.getValue() != null) {

                if (event.getDimension().startsWith("vol-")) {
                    newQueueItem.setNamespace("AWS/EBS");
                    Dimension volDim = new Dimension();
                    volDim.setName("VolumeId");
                    volDim.setValue(event.getDimension());
                    dimArray.add(volDim);
                    // Need to replace metric name
                    if (event.getMetric().startsWith("Disk")) {
                        final String convertedEBSMetricName = event.getMetric().replace("Disk", "Volume");
                        metricDatum.setMetricName(convertedEBSMetricName);
                    } else {
                        metricDatum.setMetricName(event.getMetric());
                    }
                } else {
                    newQueueItem.setNamespace("AWS/EC2");
                    populateInstanceDimensions(event.getInstanceId(), dimArray);

                    // convert ephemeral disks metrics
                    if (UNSUPPORTED_EC2_METRICS.contains(event.getMetric())) {
                        return;
                    } else {
                        metricDatum.setMetricName(event.getMetric());
                    }
                }
            } else {
                LOG.debug("Event does not contain a dimension");
                return;
            }

            Dimensions dims = new Dimensions();
            dims.setMember(dimArray);

            metricDatum.setTimestamp(new Date(event.getValueTimestamp()));
            metricDatum.setDimensions(dims);
            metricDatum.setValue(event.getValue());

            final String unitType = containsUnitType(metricDatum.getMetricName());
            metricDatum.setUnit(unitType);

            if (ABSOLUTE_METRICS.containsKey(metricDatum.getMetricName())) {
                metricDatum.setMetricName(ABSOLUTE_METRICS.get(metricDatum.getMetricName()));
            }

            newQueueItem.setMetricDatum(metricDatum);
            newQueueItem.setAccountId(instanceInfoProvider.getAccountNumber(event.getInstanceId()));
            queueItems.add(newQueueItem);
        }
    }

    private void populateInstanceDimensions(final String instanceId, final ArrayList<Dimension> dimArray) {
        // get autoscaling group name if it exists
        try {
            String autoscalingGroupName = instanceInfoProvider.getAutoscalingGroupName(instanceId);
            if (autoscalingGroupName != null) {
                Dimension autoscalingGroupNameDim = new Dimension();
                autoscalingGroupNameDim.setName("AutoScalingGroupName");
                autoscalingGroupNameDim.setValue(autoscalingGroupName);
                dimArray.add(autoscalingGroupNameDim);
            }
        } catch (Exception ex) {
            // no autoscaling group, don't bother adding
        }
        Dimension instanceIdDim = new Dimension();
        instanceIdDim.setName("InstanceId");
        instanceIdDim.setValue(instanceInfoProvider.getInstanceId(instanceId));
        dimArray.add(instanceIdDim);

        Dimension imageIdDim = new Dimension();
        imageIdDim.setName("ImageId");
        imageIdDim.setValue(instanceInfoProvider.getImageId(instanceId));
        dimArray.add(imageIdDim);

        Dimension instanceTypeDim = new Dimension();
        instanceTypeDim.setName("InstanceType");
        instanceTypeDim.setValue(instanceInfoProvider.getVmTypeDisplayName(instanceId));
        dimArray.add(instanceTypeDim);
    }

    private List<AbsoluteMetricQueueItem> buildInstanceStatusPut(final String instanceId) throws Exception {
        final List<Pair<String, Double>> instanceStatusDatums = ImmutableList.<Pair<String, Double>>builder()
                .add(Pair.pair(Count.StatusCheckFailed.name(),
                        instanceInfoProvider.getStatusCheckFailed(instanceId).doubleValue()))
                .add(Pair.pair(Count.StatusCheckFailed_Instance.name(),
                        instanceInfoProvider.getInstanceStatusCheckFailed(instanceId).doubleValue()))
                .add(Pair.pair(Count.StatusCheckFailed_System.name(),
                        instanceInfoProvider.getSystemStatusCheckFailed(instanceId).doubleValue()))
                .build();

        final ArrayList<Dimension> dimArray = Lists.newArrayList();
        populateInstanceDimensions(instanceId, dimArray);
        final Dimensions dimensions = new Dimensions();
        dimensions.setMember(dimArray);

        final List<AbsoluteMetricQueueItem> queueItems = Lists.newArrayList();
        for (final Pair<String, Double> datum : instanceStatusDatums) {
            final MetricDatum metricDatum = new MetricDatum();
            metricDatum.setMetricName(datum.getLeft());
            metricDatum.setDimensions(dimensions);
            metricDatum.setTimestamp(new Date());
            metricDatum.setValue(datum.getRight());
            metricDatum.setUnit(Count.class.getSimpleName());
            final AbsoluteMetricQueueItem queueItem = new AbsoluteMetricQueueItem();
            queueItem.setNamespace("AWS/EC2");
            queueItem.setMetricDatum(metricDatum);
            queueItem.setAccountId(instanceInfoProvider.getAccountNumber(instanceId));
            queueItems.add(queueItem);
        }
        return queueItems;
    }

    private static Predicate<AbsoluteMetricQueueItem> withMetric(final String namespace, final String name,
            final String dimensionName, final String dimensionValue) {
        return new Predicate<AbsoluteMetricQueueItem>() {
            private final Predicate<MetricDatum> metricDatumPredicate = Predicates.and(
                    name == null ? Predicates.<MetricDatum>alwaysTrue() : withMetric(name),
                    withMetricDimension(dimensionName, dimensionValue));

            @Override
            public boolean apply(@Nullable final AbsoluteMetricQueueItem queueItem) {
                return queueItem != null && namespace.equals(queueItem.getNamespace())
                        && queueItem.getMetricDatum() != null
                        && metricDatumPredicate.apply(queueItem.getMetricDatum());
            }
        };
    }

    private static Predicate<MetricDatum> withMetric(final String name) {
        return new Predicate<MetricDatum>() {
            @Override
            public boolean apply(@Nullable final MetricDatum metricDatum) {
                return metricDatum != null && name.equals(metricDatum.getMetricName());
            }
        };
    }

    private static Predicate<MetricDatum> withMetricDimension(final String dimensionName,
            final String dimensionValue) {
        return new Predicate<MetricDatum>() {
            private final Predicate<Dimension> dimensionPredicate = withDimension(dimensionName, dimensionValue);

            @Override
            public boolean apply(@Nullable final MetricDatum metricDatum) {
                return metricDatum != null && metricDatum.getDimensions() != null
                        && metricDatum.getDimensions().getMember() != null && Iterables
                                .tryFind(metricDatum.getDimensions().getMember(), dimensionPredicate).isPresent();
            }
        };
    }

    private static Predicate<Dimension> withDimension(final String dimensionName, final String dimensionValue) {
        return new Predicate<Dimension>() {
            @Override
            public boolean apply(@Nullable final Dimension dimension) {
                return dimension != null && dimensionName.equals(dimension.getName())
                        && dimensionValue.equals(dimension.getValue());
            }
        };
    }
}