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

Java tutorial

Introduction

Here is the source code for com.eucalyptus.cluster.callback.reporting.FullTableScanAbsoluteMetricConverter.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 com.eucalyptus.cloudwatch.common.internal.domain.metricdata.Units;
import com.eucalyptus.cloudwatch.common.msgs.Dimension;
import com.eucalyptus.cloudwatch.common.msgs.MetricDatum;
import com.eucalyptus.cloudwatch.common.msgs.StatisticSet;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionResource;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.log4j.Logger;
import org.hibernate.CacheMode;
import org.hibernate.Criteria;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class FullTableScanAbsoluteMetricConverter {

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

    protected static List<AbsoluteMetricQueueItem> dealWithAbsoluteMetrics(
            Iterable<AbsoluteMetricQueueItem> dataBatch) {
        List<AbsoluteMetricQueueItem> regularMetrics = Lists.newArrayList();
        List<SimpleAbsoluteMetricHistory> absoluteMetricsToInsert = Lists.newArrayList();
        SortedAbsoluteMetrics sortedAbsoluteMetrics = sortAbsoluteMetrics(dataBatch);
        regularMetrics.addAll(sortedAbsoluteMetrics.getRegularMetrics());
        AbsoluteMetricMap absoluteMetricMap = sortedAbsoluteMetrics.getAbsoluteMetricMap();
        try (final TransactionResource db = Entities.transactionFor(AbsoluteMetricHistory.class)) {
            int count = 0;
            Criteria criteria = Entities.createCriteria(AbsoluteMetricHistory.class);
            ScrollableResults absoluteMetrics = criteria.setCacheMode(CacheMode.IGNORE)
                    .scroll(ScrollMode.FORWARD_ONLY);
            while (absoluteMetrics.next()) {
                AbsoluteMetricHistory absoluteMetricHistory = (AbsoluteMetricHistory) absoluteMetrics.get(0);
                if (absoluteMetricMap.containsKey(absoluteMetricHistory.getNamespace(),
                        absoluteMetricHistory.getMetricName(), absoluteMetricHistory.getDimensionName(),
                        absoluteMetricHistory.getDimensionValue())) {
                    MetricsAndOtherFields metricsAndOtherFields = absoluteMetricMap.getMetricsAndOtherFields(
                            absoluteMetricHistory.getNamespace(), absoluteMetricHistory.getMetricName(),
                            absoluteMetricHistory.getDimensionName(), absoluteMetricHistory.getDimensionValue());
                    Map<TimestampAndMetricValue, MetricDatum> metricDatumMap = metricsAndOtherFields
                            .getMetricDatumMap();
                    SequentialMetrics sequentialMetrics = calculateSequentialMetrics(absoluteMetricHistory,
                            metricDatumMap, metricsAndOtherFields.getAccountId(),
                            metricsAndOtherFields.getRelativeMetricName());
                    absoluteMetricMap.removeEntries(absoluteMetricHistory.getNamespace(),
                            absoluteMetricHistory.getMetricName(), absoluteMetricHistory.getDimensionName(),
                            absoluteMetricHistory.getDimensionValue());
                    for (AbsoluteMetricQueueItem regularMetric : sequentialMetrics.getRegularMetrics()) {
                        if (AbsoluteMetricHelper.AWS_EBS_NAMESPACE.equals(regularMetric.getNamespace())) {
                            if (AbsoluteMetricHelper.VOLUME_READ_OPS_METRIC_NAME
                                    .equals(regularMetric.getMetricDatum().getMetricName())) { // special case
                                regularMetrics.add(AbsoluteMetricHelper.createVolumeThroughputMetric(
                                        regularMetric.getAccountId(), regularMetric.getNamespace(),
                                        regularMetric.getMetricDatum()));
                            } else if (AbsoluteMetricHelper.VOLUME_TOTAL_READ_WRITE_TIME_METRIC_NAME
                                    .equals(regularMetric.getMetricDatum().getMetricName())) {
                                AbsoluteMetricHelper.convertVolumeTotalReadWriteTimeToVolumeIdleTime(
                                        regularMetric.getMetricDatum());
                            }
                        }
                        regularMetrics.add(regularMetric);
                    }
                    absoluteMetricHistory.setTimestamp(sequentialMetrics.getUpdateTimestamp());
                    absoluteMetricHistory.setLastMetricValue(sequentialMetrics.getUpdateValue());
                    if (++count % AbsoluteMetricQueue.ABSOLUTE_METRIC_NUM_DB_OPERATIONS_UNTIL_SESSION_FLUSH == 0) {
                        Entities.flushSession(AbsoluteMetricHistory.class);
                        Entities.clearSession(AbsoluteMetricHistory.class);
                    }
                }
            }
            db.commit();
        }
        // Now parse entries only in the map...
        for (AbsoluteMetricMap.NamespaceMetricNameAndDimension namespaceMetricNameAndDimension : absoluteMetricMap
                .keySet()) {
            AbsoluteMetricHistory absoluteMetricHistory = new AbsoluteMetricHistory();
            absoluteMetricHistory.setNamespace(namespaceMetricNameAndDimension.getNamespace());
            absoluteMetricHistory.setMetricName(namespaceMetricNameAndDimension.getMetricName());
            absoluteMetricHistory.setDimensionName(namespaceMetricNameAndDimension.getDimensionName());
            absoluteMetricHistory.setDimensionValue(namespaceMetricNameAndDimension.getDimensionValue());
            MetricsAndOtherFields metricsAndOtherFields = absoluteMetricMap.get(namespaceMetricNameAndDimension);
            Map<TimestampAndMetricValue, MetricDatum> metricDataMap = metricsAndOtherFields.getMetricDatumMap();
            if (metricDataMap.size() == 0)
                continue;
            TimestampAndMetricValue firstValue = metricDataMap.keySet().iterator().next();
            metricDataMap.remove(firstValue);
            absoluteMetricHistory.setLastMetricValue(firstValue.getMetricValue());
            absoluteMetricHistory.setTimestamp(firstValue.getTimestamp());
            if (metricDataMap.size() != 0) {
                SequentialMetrics sequentialMetrics = calculateSequentialMetrics(absoluteMetricHistory,
                        metricDataMap, metricsAndOtherFields.getAccountId(),
                        metricsAndOtherFields.getRelativeMetricName());
                for (AbsoluteMetricQueueItem regularMetric : sequentialMetrics.getRegularMetrics()) {
                    if (AbsoluteMetricHelper.AWS_EBS_NAMESPACE.equals(regularMetric.getNamespace())) {
                        if (AbsoluteMetricHelper.VOLUME_READ_OPS_METRIC_NAME
                                .equals(regularMetric.getMetricDatum().getMetricName())) { // special case
                            regularMetrics.add(
                                    AbsoluteMetricHelper.createVolumeThroughputMetric(regularMetric.getAccountId(),
                                            regularMetric.getNamespace(), regularMetric.getMetricDatum()));
                        } else if (AbsoluteMetricHelper.VOLUME_TOTAL_READ_WRITE_TIME_METRIC_NAME
                                .equals(regularMetric.getMetricDatum().getMetricName())) {
                            AbsoluteMetricHelper.convertVolumeTotalReadWriteTimeToVolumeIdleTime(
                                    regularMetric.getMetricDatum());
                        }
                    }
                    regularMetrics.add(regularMetric);
                }
                absoluteMetricHistory.setTimestamp(sequentialMetrics.getUpdateTimestamp());
                absoluteMetricHistory.setLastMetricValue(sequentialMetrics.getUpdateValue());
            }
            absoluteMetricsToInsert.add(convertToSimpleAbsoluteMetricHistory(absoluteMetricHistory));
        }

        // insert all new points
        try (final TransactionResource db = Entities.transactionFor(AbsoluteMetricHistory.class)) {
            int count = 0;
            for (SimpleAbsoluteMetricHistory simpleAbsoluteMetricHistory : absoluteMetricsToInsert) {
                Entities.persist(convertToAbsoluteMetricHistory(simpleAbsoluteMetricHistory));
                if (++count % AbsoluteMetricQueue.ABSOLUTE_METRIC_NUM_DB_OPERATIONS_UNTIL_SESSION_FLUSH == 0) {
                    Entities.flushSession(AbsoluteMetricHistory.class);
                    Entities.clearSession(AbsoluteMetricHistory.class);
                }
            }
            db.commit();
        }
        return regularMetrics;
    }

    private static SimpleAbsoluteMetricHistory convertToSimpleAbsoluteMetricHistory(
            AbsoluteMetricHistory absoluteMetricHistory) {
        SimpleAbsoluteMetricHistory simpleAbsoluteMetricHistory = new SimpleAbsoluteMetricHistory();
        simpleAbsoluteMetricHistory.setNamespace(absoluteMetricHistory.getNamespace());
        simpleAbsoluteMetricHistory.setMetricName(absoluteMetricHistory.getMetricName());
        simpleAbsoluteMetricHistory.setDimensionName(absoluteMetricHistory.getDimensionName());
        simpleAbsoluteMetricHistory.setDimensionValue(absoluteMetricHistory.getDimensionValue());
        simpleAbsoluteMetricHistory.setTimestamp(absoluteMetricHistory.getTimestamp());
        simpleAbsoluteMetricHistory.setLastMetricValue(absoluteMetricHistory.getLastMetricValue());
        return simpleAbsoluteMetricHistory;
    }

    private static AbsoluteMetricHistory convertToAbsoluteMetricHistory(
            SimpleAbsoluteMetricHistory simpleAbsoluteMetricHistory) {
        AbsoluteMetricHistory absoluteMetricHistory = new AbsoluteMetricHistory();
        absoluteMetricHistory.setNamespace(simpleAbsoluteMetricHistory.getNamespace());
        absoluteMetricHistory.setMetricName(simpleAbsoluteMetricHistory.getMetricName());
        absoluteMetricHistory.setDimensionName(simpleAbsoluteMetricHistory.getDimensionName());
        absoluteMetricHistory.setDimensionValue(simpleAbsoluteMetricHistory.getDimensionValue());
        absoluteMetricHistory.setTimestamp(simpleAbsoluteMetricHistory.getTimestamp());
        absoluteMetricHistory.setLastMetricValue(simpleAbsoluteMetricHistory.getLastMetricValue());
        return absoluteMetricHistory;
    }

    private static SequentialMetrics calculateSequentialMetrics(AbsoluteMetricHistory absoluteMetricHistory,
            Map<TimestampAndMetricValue, MetricDatum> metricDatumMap, String accountId, String relativeMetricName) {
        SequentialMetrics sequentialMetrics = new SequentialMetrics();

        Date lastDate = absoluteMetricHistory.getTimestamp();
        Double lastValue = absoluteMetricHistory.getLastMetricValue();

        for (TimestampAndMetricValue value : metricDatumMap.keySet()) {
            double valueDiff = value.getMetricValue() - lastValue;

            // VolumeQueueLength is a special case.  Values in the table are set to 0 but the data set uses the values passed in.
            boolean isVolumeQueueLengthCase = AbsoluteMetricHelper.AWS_EBS_NAMESPACE
                    .equals(absoluteMetricHistory.getNamespace())
                    && AbsoluteMetricHelper.VOLUME_QUEUE_LENGTH_PLACEHOLDER_ABSOLUTE_METRIC_NAME
                            .equals(absoluteMetricHistory.getMetricName());

            // CPUUtilization is also a special case.  The value is a percent.
            boolean isCPUUtilizationCase = AbsoluteMetricHelper.AWS_EC2_NAMESPACE
                    .equals(absoluteMetricHistory.getNamespace())
                    && AbsoluteMetricHelper.CPU_UTILIZATION_MS_ABSOLUTE_METRIC_NAME
                            .equals(absoluteMetricHistory.getMetricName());

            long timeDiff = value.getTimestamp().getTime() - lastDate.getTime();
            if (timeDiff < 0) {
                // a negative value of timeDiff means this data point is not useful
                continue;
            } else if (timeDiff == 0) {
                if (Math.abs(valueDiff) > AbsoluteMetricHelper.TOLERANCE) {
                    LOG.warn("Getting different values " + value.getMetricValue() + " and " + lastValue
                            + " for absolute metric " + absoluteMetricHistory.getMetricName()
                            + " at the same timestamp " + lastDate + ", keeping the second value.");
                }
                continue;
            } else {
                // definitely update the value
                lastDate = value.getTimestamp();
                lastValue = isVolumeQueueLengthCase ? 0 : value.getMetricValue();
                if (timeDiff > AbsoluteMetricHelper.MAX_DIFFERENCE_DURATION_MS) {
                    // Too much time has passed, a useful data point, but we will not report the 'difference'.  We will reset.
                    continue;
                } else if (valueDiff < -AbsoluteMetricHelper.TOLERANCE) { // value has gone "down" (or down more than the AbsoluteMetricCommon.TOLERANCE)
                    // if the value difference is negative (i.e. has gone down, the assumption is that the NC has restarted, and the new
                    // value started from some time in the past.  Best thing to do here is either assume it is a first point again, or
                    // assume the previous point had a 0 value.  Not sure which is the better choice, but for now, we will make it a "first"
                    // point again
                    continue;
                } else { // truncate differences within AbsoluteMetricCommon.TOLERANCE to zero
                    if (Math.abs(valueDiff) < AbsoluteMetricHelper.TOLERANCE) {
                        valueDiff = 0.0;
                    }
                    AbsoluteMetricQueueItem regularMetric = new AbsoluteMetricQueueItem();
                    regularMetric.setNamespace(absoluteMetricHistory.getNamespace());
                    regularMetric.setAccountId(accountId);
                    MetricDatum datum = metricDatumMap.get(value);
                    regularMetric.setMetricDatum(datum);
                    datum.setMetricName(relativeMetricName);
                    datum.setValue(null);
                    StatisticSet statisticSet = new StatisticSet();
                    double sampleCount = (double) timeDiff / 60000.0; // number of minutes (this weights the value)
                    if (isVolumeQueueLengthCase) {
                        datum.setMetricName(AbsoluteMetricHelper.VOLUME_QUEUE_LENGTH_METRIC_NAME); // other values are for the absolute metric history table only
                        statisticSet.setSum(value.getMetricValue() * sampleCount);
                        statisticSet.setMaximum(value.getMetricValue());
                        statisticSet.setMinimum(value.getMetricValue());
                    } else if (isCPUUtilizationCase) {
                        double percentage = 100.0 * (valueDiff / timeDiff);
                        statisticSet.setSum(percentage * sampleCount);
                        statisticSet.setMaximum(percentage);
                        statisticSet.setMinimum(percentage);
                        datum.setUnit(Units.Percent.toString());
                    } else {
                        statisticSet.setSum(valueDiff);
                        statisticSet.setMaximum(valueDiff / sampleCount);
                        statisticSet.setMinimum(valueDiff / sampleCount);
                    }
                    statisticSet.setSampleCount(sampleCount);
                    datum.setStatisticValues(statisticSet);
                    datum.setValue(null);
                    sequentialMetrics.getRegularMetrics().add(regularMetric);
                }
            }
        }
        sequentialMetrics.setUpdateTimestamp(lastDate);
        sequentialMetrics.setUpdateValue(lastValue);
        return sequentialMetrics;
    }

    private static SortedAbsoluteMetrics sortAbsoluteMetrics(Iterable<AbsoluteMetricQueueItem> dataBatch) {
        SortedAbsoluteMetrics sortedAbsoluteMetrics = new SortedAbsoluteMetrics();
        for (AbsoluteMetricQueueItem item : dataBatch) {
            String accountId = item.getAccountId();
            String namespace = item.getNamespace();
            MetricDatum datum = item.getMetricDatum();
            if (AbsoluteMetricHelper.AWS_EBS_NAMESPACE.equals(namespace)) {
                String volumeId = null;
                if ((datum.getDimensions() != null) && (datum.getDimensions().getMember() != null)) {
                    for (Dimension dimension : datum.getDimensions().getMember()) {
                        if (AbsoluteMetricHelper.VOLUME_ID_DIM_NAME.equals(dimension.getName())) {
                            volumeId = dimension.getValue();
                        }
                    }
                }
                if (volumeId == null) {
                    continue; // this data point doesn't count.
                } else {
                    if (AbsoluteMetricHelper.EBS_ABSOLUTE_METRICS.containsKey(datum.getMetricName())) {
                        addAccountAbsoluteMetricMap(sortedAbsoluteMetrics, accountId,
                                AbsoluteMetricHelper.AWS_EBS_NAMESPACE, datum.getMetricName(),
                                AbsoluteMetricHelper.VOLUME_ID_DIM_NAME, volumeId,
                                AbsoluteMetricHelper.EBS_ABSOLUTE_METRICS.get(datum.getMetricName()),
                                datum.getTimestamp(), datum.getValue(), datum);
                    } else if (AbsoluteMetricHelper.VOLUME_QUEUE_LENGTH_METRIC_NAME.equals(datum.getMetricName())) {
                        addAccountAbsoluteMetricMap(sortedAbsoluteMetrics, accountId,
                                AbsoluteMetricHelper.AWS_EBS_NAMESPACE,
                                AbsoluteMetricHelper.VOLUME_QUEUE_LENGTH_PLACEHOLDER_ABSOLUTE_METRIC_NAME,
                                AbsoluteMetricHelper.VOLUME_ID_DIM_NAME, volumeId,
                                AbsoluteMetricHelper.VOLUME_QUEUE_LENGTH_PLACEHOLDER_METRIC_NAME,
                                datum.getTimestamp(), datum.getValue(), datum);
                    } else {
                        sortedAbsoluteMetrics.getRegularMetrics().add(item);
                    }
                }
            } else if (AbsoluteMetricHelper.AWS_EC2_NAMESPACE.equals(namespace)) {
                String instanceId = null;
                if ((datum.getDimensions() != null) && (datum.getDimensions().getMember() != null)) {
                    for (Dimension dimension : datum.getDimensions().getMember()) {
                        if (AbsoluteMetricHelper.INSTANCE_ID_DIM_NAME.equals(dimension.getName())) {
                            instanceId = dimension.getValue();
                        }
                    }
                }
                if (instanceId == null) {
                    continue; // this data point doesn't count.
                } else if (AbsoluteMetricHelper.EC2_ABSOLUTE_METRICS.containsKey(datum.getMetricName())) {
                    addAccountAbsoluteMetricMap(sortedAbsoluteMetrics, accountId,
                            AbsoluteMetricHelper.AWS_EC2_NAMESPACE, datum.getMetricName(),
                            AbsoluteMetricHelper.INSTANCE_ID_DIM_NAME, instanceId,
                            AbsoluteMetricHelper.EC2_ABSOLUTE_METRICS.get(datum.getMetricName()),
                            datum.getTimestamp(), datum.getValue(), datum);
                } else if (AbsoluteMetricHelper.CPU_UTILIZATION_MS_ABSOLUTE_METRIC_NAME
                        .equals(datum.getMetricName())) {
                    addAccountAbsoluteMetricMap(sortedAbsoluteMetrics, accountId,
                            AbsoluteMetricHelper.AWS_EC2_NAMESPACE,
                            AbsoluteMetricHelper.CPU_UTILIZATION_MS_ABSOLUTE_METRIC_NAME,
                            AbsoluteMetricHelper.INSTANCE_ID_DIM_NAME, instanceId,
                            AbsoluteMetricHelper.CPU_UTILIZATION_METRIC_NAME, datum.getTimestamp(),
                            datum.getValue(), datum);
                } else {
                    sortedAbsoluteMetrics.getRegularMetrics().add(item);
                }
            } else {
                // not really an absolute metric, just leave it alone
                sortedAbsoluteMetrics.getRegularMetrics().add(item);
            }
        }
        return sortedAbsoluteMetrics;
    }

    private static void addAccountAbsoluteMetricMap(SortedAbsoluteMetrics sortedAbsoluteMetrics, String accountId,
            String namespace, String metricName, String dimensionName, String dimensionValue,
            String relativeMetricName, Date timestamp, Double lastMetricValue, MetricDatum datum) {
        sortedAbsoluteMetrics.getAbsoluteMetricMap().addEntry(accountId, namespace, metricName, dimensionName,
                dimensionValue, relativeMetricName, timestamp, lastMetricValue, datum);
    }

    /**
    * Created by ethomas on 7/15/15.
    */
    public static class AbsoluteMetricMap {

        private static class NamespaceMetricNameAndDimension {
            private String namespace;
            private String metricName;
            private String dimensionName;
            private String dimensionValue;

            NamespaceMetricNameAndDimension(String namespace, String metricName, String dimensionName,
                    String dimensionValue) {
                this.namespace = namespace;
                this.metricName = metricName;
                this.dimensionName = dimensionName;
                this.dimensionValue = dimensionValue;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o)
                    return true;
                if (o == null || getClass() != o.getClass())
                    return false;

                NamespaceMetricNameAndDimension namespaceMetricNameAndDimension = (NamespaceMetricNameAndDimension) o;

                if (dimensionName != null ? !dimensionName.equals(namespaceMetricNameAndDimension.dimensionName)
                        : namespaceMetricNameAndDimension.dimensionName != null)
                    return false;
                if (dimensionValue != null ? !dimensionValue.equals(namespaceMetricNameAndDimension.dimensionValue)
                        : namespaceMetricNameAndDimension.dimensionValue != null)
                    return false;
                if (metricName != null ? !metricName.equals(namespaceMetricNameAndDimension.metricName)
                        : namespaceMetricNameAndDimension.metricName != null)
                    return false;
                if (namespace != null ? !namespace.equals(namespaceMetricNameAndDimension.namespace)
                        : namespaceMetricNameAndDimension.namespace != null)
                    return false;

                return true;
            }

            @Override
            public int hashCode() {
                int result = namespace != null ? namespace.hashCode() : 0;
                result = 31 * result + (metricName != null ? metricName.hashCode() : 0);
                result = 31 * result + (dimensionName != null ? dimensionName.hashCode() : 0);
                result = 31 * result + (dimensionValue != null ? dimensionValue.hashCode() : 0);
                return result;
            }

            public String getNamespace() {
                return namespace;
            }

            public String getMetricName() {
                return metricName;
            }

            public String getDimensionName() {
                return dimensionName;
            }

            public String getDimensionValue() {
                return dimensionValue;
            }
        }

        private Map<NamespaceMetricNameAndDimension, MetricsAndOtherFields> metricMap = Maps.newHashMap();

        public void addEntry(String accountId, String namespace, String metricName, String dimensionName,
                String dimensionValue, String relativeMetricName, Date timestamp, Double lastMetricValue,
                MetricDatum datum) {
            NamespaceMetricNameAndDimension namespaceMetricNameAndDimension = new NamespaceMetricNameAndDimension(
                    namespace, metricName, dimensionName, dimensionValue);
            if (!metricMap.containsKey(namespaceMetricNameAndDimension)) {
                metricMap.put(namespaceMetricNameAndDimension,
                        new MetricsAndOtherFields(accountId, relativeMetricName));
            }
            if (relativeMetricName == null) {
                throw new IllegalArgumentException("RelativeMetricName can not be null");
            }
            if (accountId == null) {
                throw new IllegalArgumentException("AccountId can not be null");
            }
            MetricsAndOtherFields value = metricMap.get(namespaceMetricNameAndDimension);
            if (!relativeMetricName.equals(value.getRelativeMetricName())) {
                throw new IllegalArgumentException("RelativeMetricName has two values for " + namespace + "/"
                        + dimensionName + "/" + dimensionValue);
            }
            if (!accountId.equals(value.getAccountId())) {
                throw new IllegalArgumentException("RelativeMetricName has two values for " + namespace + "/"
                        + dimensionName + "/" + dimensionValue);
            }
            value.getMetricDatumMap().put(new TimestampAndMetricValue(timestamp, lastMetricValue), datum);
        }

        public boolean containsKey(String namespace, String metricName, String dimensionName,
                String dimensionValue) {
            return metricMap.containsKey(
                    new NamespaceMetricNameAndDimension(namespace, metricName, dimensionName, dimensionValue));
        }

        public MetricsAndOtherFields getMetricsAndOtherFields(String namespace, String metricName,
                String dimensionName, String dimensionValue) {
            return metricMap
                    .get(new NamespaceMetricNameAndDimension(namespace, metricName, dimensionName, dimensionValue));
        }

        public void removeEntries(String namespace, String metricName, String dimensionName,
                String dimensionValue) {
            metricMap.remove(
                    new NamespaceMetricNameAndDimension(namespace, metricName, dimensionName, dimensionValue));
        }

        public Set<NamespaceMetricNameAndDimension> keySet() {
            return metricMap.keySet();
        }

        public MetricsAndOtherFields get(NamespaceMetricNameAndDimension namespaceMetricNameAndDimension) {
            return metricMap.get(namespaceMetricNameAndDimension);
        }
    }

    static class SimpleAbsoluteMetricHistory {
        public SimpleAbsoluteMetricHistory() {
        }

        private String namespace;
        private String metricName;
        private String dimensionName;
        private String dimensionValue;
        private Date timestamp;
        private Double lastMetricValue;

        public String getNamespace() {
            return namespace;
        }

        public void setNamespace(String namespace) {
            this.namespace = namespace;
        }

        public String getMetricName() {
            return metricName;
        }

        public void setMetricName(String metricName) {
            this.metricName = metricName;
        }

        public String getDimensionName() {
            return dimensionName;
        }

        public void setDimensionName(String dimensionName) {
            this.dimensionName = dimensionName;
        }

        public String getDimensionValue() {
            return dimensionValue;
        }

        public void setDimensionValue(String dimensionValue) {
            this.dimensionValue = dimensionValue;
        }

        public Date getTimestamp() {
            return timestamp;
        }

        public void setTimestamp(Date timestamp) {
            this.timestamp = timestamp;
        }

        public Double getLastMetricValue() {
            return lastMetricValue;
        }

        public void setLastMetricValue(Double lastMetricValue) {
            this.lastMetricValue = lastMetricValue;
        }
    }

    private static class MetricsAndOtherFields {
        private String accountId;
        private String relativeMetricName;
        private Map<TimestampAndMetricValue, MetricDatum> metricDatumMap = Maps.newTreeMap();

        MetricsAndOtherFields(String accountId, String relativeMetricName) {
            this.accountId = accountId;
            this.relativeMetricName = relativeMetricName;
        }

        public String getAccountId() {
            return accountId;
        }

        public String getRelativeMetricName() {
            return relativeMetricName;
        }

        public Map<TimestampAndMetricValue, MetricDatum> getMetricDatumMap() {
            return metricDatumMap;
        }
    }

    static class TimestampAndMetricValue implements Comparable<TimestampAndMetricValue> {
        private Date timestamp;
        private Double metricValue;

        TimestampAndMetricValue(Date timestamp, Double metricValue) {
            this.timestamp = timestamp;
            this.metricValue = metricValue;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            TimestampAndMetricValue that = (TimestampAndMetricValue) o;

            if (metricValue != null ? !metricValue.equals(that.metricValue) : that.metricValue != null)
                return false;
            if (timestamp != null ? !timestamp.equals(that.timestamp) : that.timestamp != null)
                return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = timestamp != null ? timestamp.hashCode() : 0;
            result = 31 * result + (metricValue != null ? metricValue.hashCode() : 0);
            return result;
        }

        public Date getTimestamp() {
            return timestamp;
        }

        public Double getMetricValue() {
            return metricValue;
        }

        public void setTimestamp(Date timestamp) {
            this.timestamp = timestamp;
        }

        public void setMetricValue(Double metricValue) {
            this.metricValue = metricValue;
        }

        @Override
        public int compareTo(TimestampAndMetricValue that) {
            if (this.timestamp.compareTo(that.timestamp) < 0) {
                return -1;
            } else if (this.timestamp.compareTo(that.timestamp) > 0) {
                return 1;
            }

            if (this.metricValue.compareTo(that.metricValue) < 0) {
                return -1;
            } else if (this.metricValue.compareTo(that.metricValue) > 0) {
                return 1;
            }
            return 0;
        }
    }

    private static class SortedAbsoluteMetrics {
        private List<AbsoluteMetricQueueItem> regularMetrics = Lists.newArrayList();
        private AbsoluteMetricMap absoluteMetricMap = new AbsoluteMetricMap();

        SortedAbsoluteMetrics() {
        }

        public List<AbsoluteMetricQueueItem> getRegularMetrics() {
            return regularMetrics;
        }

        public AbsoluteMetricMap getAbsoluteMetricMap() {
            return absoluteMetricMap;
        }
    }

    private static class SequentialMetrics {
        private Collection<AbsoluteMetricQueueItem> regularMetrics = Lists.newArrayList();

        private Date updateTimestamp;
        private Double updateValue;

        public void setUpdateTimestamp(Date updateTimestamp) {
            this.updateTimestamp = updateTimestamp;
        }

        public void setUpdateValue(Double updateValue) {
            this.updateValue = updateValue;
        }

        public Collection<AbsoluteMetricQueueItem> getRegularMetrics() {
            return regularMetrics;
        }

        public Date getUpdateTimestamp() {
            return updateTimestamp;
        }

        public Double getUpdateValue() {
            return updateValue;
        }
    }
}