/************************************************************************* * 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.Iterables; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import org.apache.log4j.Logger; import org.hibernate.Criteria; import org.hibernate.criterion.Restrictions; import javax.persistence.EntityTransaction; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; public class DefaultAbsoluteMetricConverter { private static final Logger LOG = Logger.getLogger(DefaultAbsoluteMetricConverter.class); protected static List<AbsoluteMetricQueueItem> dealWithAbsoluteMetrics( Iterable<AbsoluteMetricQueueItem> dataBatch) { List<AbsoluteMetricQueueItem> regularMetrics = new ArrayList<AbsoluteMetricQueueItem>(); // We need to do some sorting to allow fewer db lookups. There is also logic for different metric types, so they will be sorted now. // Some points do not actually go in. If a data point represents an absolute value, the first one does not go in. // Also, some data points are added while we go through the list (derived metrics) // Deal with the absolute metrics // CPUUtilization // VolumeReadOps // VolumeWriteOps // VolumeConsumedReadWriteOps // VolumeReadBytes // VolumeWriteBytes // VolumeTotalReadTime // VolumeTotalWriteTime // VolumeTotalReadWriteTime (used to calculate VolumeIdleTime) // DiskReadOps // DiskWriteOps // DiskReadBytes // DiskWriteBytes // NetworkIn // NetworkOut // NetworkPacketsIn // NetworkPacketsOut Multimap<String, AbsoluteMetricQueueItem> instanceMetricMap = LinkedListMultimap.create(); Multimap<String, AbsoluteMetricQueueItem> volumeMetricMap = LinkedListMultimap.create(); for (final AbsoluteMetricQueueItem item : dataBatch) { 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 { volumeMetricMap.put(volumeId, 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 { instanceMetricMap.put(instanceId, item); } } else { // not really an absolute metric, just leave it alone regularMetrics.add(item); } } for (List<String> partialVolumeKeySet: Iterables.partition(volumeMetricMap.keySet(), AbsoluteMetricQueue.ABSOLUTE_METRIC_NUM_DB_OPERATIONS_PER_TRANSACTION)) { try (final TransactionResource db = Entities.transactionFor(AbsoluteMetricHistory.class)) { int numVolumes = 0; for (String volumeId: partialVolumeKeySet) { AbsoluteMetricCache cache = new AbsoluteMetricCache(db); cache.load(AbsoluteMetricHelper.AWS_EBS_NAMESPACE, AbsoluteMetricHelper.VOLUME_ID_DIM_NAME, volumeId); for (AbsoluteMetricQueueItem item : volumeMetricMap.get(volumeId)) { String accountId = item.getAccountId(); String nameSpace = item.getNamespace(); MetricDatum datum = item.getMetricDatum(); if (AbsoluteMetricHelper.EBS_ABSOLUTE_METRICS.containsKey(datum.getMetricName())) { // we check if the point below is a 'first' point, or maybe a point in the past. Either case reject it. if (!adjustAbsoluteVolumeStatisticSet(cache, datum, datum.getMetricName(), AbsoluteMetricHelper.EBS_ABSOLUTE_METRICS.get(datum.getMetricName()), volumeId)) continue; } // special cases // 1) VolumeThroughputPercentage -- this is 100% for provisioned volumes, and we need to insert a // data point for every timestamp that a volume event occurs. // To make sure we don't duplicate the effort, we choose one event at random, VolumeReadOps, // and create this new metric arbitrarily if (AbsoluteMetricHelper.VOLUME_READ_OPS_METRIC_NAME.equals(datum.getMetricName())) { // special case regularMetrics.add(AbsoluteMetricHelper.createVolumeThroughputMetric(accountId, nameSpace, datum)); } // 2) VolumeIdleTime -- we piggy back off of the metric we don't need VolumeTotalReadWriteTime, and convert it to VolumeIdleTime if (AbsoluteMetricHelper.VOLUME_TOTAL_READ_WRITE_TIME_METRIC_NAME.equals(datum.getMetricName())) { AbsoluteMetricHelper.convertVolumeTotalReadWriteTimeToVolumeIdleTime(datum); } // 3) VolumeQueueLength -- this one comes in essentially correct, but we don't have a time duration for it, so we piggy back off // the absolute metric framework if (AbsoluteMetricHelper.VOLUME_QUEUE_LENGTH_METRIC_NAME.equals(datum.getMetricName())) { if (!adjustAbsoluteVolumeQueueLengthStatisticSet(cache, datum, volumeId)) continue; } // Once here, our item has been appropriately adjusted. Add it regularMetrics.add(item); } numVolumes++; if (numVolumes % AbsoluteMetricQueue.ABSOLUTE_METRIC_NUM_DB_OPERATIONS_UNTIL_SESSION_FLUSH == 0) { Entities.flushSession(AbsoluteMetricHistory.class); Entities.clearSession(AbsoluteMetricHistory.class); } } db.commit(); } } for (List<String> partialInstanceKeySet: Iterables.partition(instanceMetricMap.keySet(), AbsoluteMetricQueue.ABSOLUTE_METRIC_NUM_DB_OPERATIONS_PER_TRANSACTION)) { try (final TransactionResource db = Entities.transactionFor(AbsoluteMetricHistory.class)) { int numInstances = 0; for (String instanceId: partialInstanceKeySet) { AbsoluteMetricCache cache = new AbsoluteMetricCache(db); cache.load(AbsoluteMetricHelper.AWS_EC2_NAMESPACE, AbsoluteMetricHelper.INSTANCE_ID_DIM_NAME, instanceId); for (AbsoluteMetricQueueItem item : instanceMetricMap.get(instanceId)) { String accountId = item.getAccountId(); String nameSpace = item.getNamespace(); MetricDatum datum = item.getMetricDatum(); if (AbsoluteMetricHelper.EC2_ABSOLUTE_METRICS.containsKey(datum.getMetricName())) { if (!adjustAbsoluteInstanceStatisticSet(cache, datum, datum.getMetricName(), AbsoluteMetricHelper.EC2_ABSOLUTE_METRICS.get(datum.getMetricName()), instanceId)) continue; } else if (AbsoluteMetricHelper.CPU_UTILIZATION_MS_ABSOLUTE_METRIC_NAME.equals(datum.getMetricName())) { // special case // we check if the point below is a 'first' point, or maybe a point in the past. Either case reject it. if (!adjustAbsoluteInstanceCPUStatisticSet(cache, datum, AbsoluteMetricHelper.CPU_UTILIZATION_MS_ABSOLUTE_METRIC_NAME, AbsoluteMetricHelper.CPU_UTILIZATION_METRIC_NAME, instanceId)) continue; } // Once here, our item has been appropriately adjusted. Add it regularMetrics.add(item); } numInstances++; if (numInstances % AbsoluteMetricQueue.ABSOLUTE_METRIC_NUM_DB_OPERATIONS_UNTIL_SESSION_FLUSH == 0) { Entities.flushSession(AbsoluteMetricHistory.class); Entities.clearSession(AbsoluteMetricHistory.class); } } db.commit(); } } return regularMetrics; } private static boolean adjustAbsoluteInstanceCPUStatisticSet(AbsoluteMetricCache cache, MetricDatum datum, String absoluteMetricName, String relativeMetricName, String instanceId) { AbsoluteMetricHelper.MetricDifferenceInfo info = AbsoluteMetricHelper.calculateDifferenceSinceLastEvent(cache, AbsoluteMetricHelper.AWS_EC2_NAMESPACE, absoluteMetricName, AbsoluteMetricHelper.INSTANCE_ID_DIM_NAME, instanceId, datum.getTimestamp(), datum.getValue()); if (info != null) { // calculate percentage double percentage = 0.0; if (info.getElapsedTimeInMillis() != 0) { // don't want to divide by 0 percentage = 100.0 * (info.getValueDifference() / info.getElapsedTimeInMillis()); } datum.setMetricName(relativeMetricName); datum.setValue(null); StatisticSet statisticSet = new StatisticSet(); statisticSet.setMaximum(percentage); statisticSet.setMinimum(percentage); double sampleCount = (double) info.getElapsedTimeInMillis() / 60000.0; // number of minutes (this weights the value) statisticSet.setSum(sampleCount * percentage); statisticSet.setSampleCount(sampleCount); datum.setStatisticValues(statisticSet); datum.setUnit(Units.Percent.toString()); return true; //don't continue; } return false; // continue } private static boolean adjustAbsoluteInstanceStatisticSet(AbsoluteMetricCache cache, MetricDatum datum, String absoluteMetricName, String relativeMetricName, String instanceId) { if (instanceId == null) return false; AbsoluteMetricHelper.MetricDifferenceInfo info = AbsoluteMetricHelper.calculateDifferenceSinceLastEvent(cache, AbsoluteMetricHelper.AWS_EC2_NAMESPACE, absoluteMetricName, AbsoluteMetricHelper.INSTANCE_ID_DIM_NAME, instanceId, datum.getTimestamp(), datum.getValue()); if (info != null) { datum.setMetricName(relativeMetricName); // we need to weigh this data based on the time. use a statistic set instead of the value datum.setValue(null); StatisticSet statisticSet = new StatisticSet(); double sampleCount = (double) info.getElapsedTimeInMillis() / 60000.0; // number of minutes (this weights the value) statisticSet.setSum(info.getValueDifference()); statisticSet.setMaximum(info.getValueDifference() / sampleCount); statisticSet.setMinimum(info.getValueDifference() / sampleCount); statisticSet.setSampleCount(sampleCount); datum.setStatisticValues(statisticSet); return true; //don't continue; } return false; // continue } static boolean adjustAbsoluteVolumeStatisticSet(AbsoluteMetricCache cache, MetricDatum datum, String absoluteMetricName, String relativeMetricName, String volumeId) { if (volumeId == null) return false; AbsoluteMetricHelper.MetricDifferenceInfo info = AbsoluteMetricHelper.calculateDifferenceSinceLastEvent(cache, AbsoluteMetricHelper.AWS_EBS_NAMESPACE, absoluteMetricName, AbsoluteMetricHelper.VOLUME_ID_DIM_NAME, volumeId, datum.getTimestamp(), datum.getValue()); if (info != null) { datum.setMetricName(relativeMetricName); // we need to weigh this data based on the time. use a statistic set instead of the value datum.setValue(null); StatisticSet statisticSet = new StatisticSet(); double sampleCount = (double) info.getElapsedTimeInMillis() / 60000.0; // number of minutes (this weights the value) statisticSet.setSum(info.getValueDifference()); statisticSet.setMaximum(info.getValueDifference() / sampleCount); statisticSet.setMinimum(info.getValueDifference() / sampleCount); statisticSet.setSampleCount(sampleCount); datum.setStatisticValues(statisticSet); return true; //don't continue; } return false; // continue } static boolean adjustAbsoluteVolumeQueueLengthStatisticSet(AbsoluteMetricCache cache, MetricDatum datum, String volumeId) { // the metric value is correct, we just need a statistic set with the sample count. // to get this we create a placeholder absolute metric, value always 0, just to get time duration/sample count MetricDatum absolutePlaceHolder = new MetricDatum(); absolutePlaceHolder.setMetricName(AbsoluteMetricHelper.VOLUME_QUEUE_LENGTH_PLACEHOLDER_ABSOLUTE_METRIC_NAME); absolutePlaceHolder.setValue(0.0); absolutePlaceHolder.setTimestamp(datum.getTimestamp()); if (!adjustAbsoluteVolumeStatisticSet(cache, absolutePlaceHolder, absolutePlaceHolder.getMetricName(), AbsoluteMetricHelper.VOLUME_QUEUE_LENGTH_PLACEHOLDER_METRIC_NAME, volumeId)) return false; // otherwise, we have a duration/sample count double sampleCount = absolutePlaceHolder.getStatisticValues().getSampleCount(); double value = datum.getValue(); datum.setValue(null); StatisticSet statisticSet = new StatisticSet(); statisticSet.setMaximum(value); statisticSet.setMinimum(value); statisticSet.setSum(value * sampleCount); statisticSet.setSampleCount(sampleCount); datum.setStatisticValues(statisticSet); return true; } public static class AbsoluteMetricCache { private EntityTransaction db; private Set<AbsoluteMetricLoadCacheKey> loaded = Sets.newHashSet(); private Map<AbsoluteMetricCacheKey, AbsoluteMetricHistory> cacheMap = Maps.newHashMap(); public AbsoluteMetricCache(EntityTransaction db) { this.db = db; } public void load(String namespace, String dimensionName, String dimensionValue) { AbsoluteMetricLoadCacheKey loadKey = new AbsoluteMetricLoadCacheKey(namespace, dimensionName); if (!loaded.contains(loadKey)) { Criteria criteria = Entities.createCriteria(AbsoluteMetricHistory.class) .add( Restrictions.eq("namespace", namespace) ) // .add( Restrictions.eq( "dimensionName", dimensionName ) ); .add( Restrictions.eq( "dimensionValue", dimensionValue ) ); List<AbsoluteMetricHistory> list = (List<AbsoluteMetricHistory>) criteria.list(); for (AbsoluteMetricHistory item: list) { cacheMap.put(new AbsoluteMetricCacheKey(item), item); } loaded.add(loadKey); } } public AbsoluteMetricHistory lookup(String namespace, String metricName, String dimensionName, String dimensionValue) { return cacheMap.get(new AbsoluteMetricCacheKey(namespace, metricName, dimensionName, dimensionValue)); } public void put(String namespace, String metricName, String dimensionName, String dimensionValue, AbsoluteMetricHistory lastEntity) { cacheMap.put(new AbsoluteMetricCacheKey(namespace, metricName, dimensionName, dimensionValue), lastEntity); } } public static class AbsoluteMetricLoadCacheKey { private String namespace; private String dimensionValue; public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getDimensionValue() { return dimensionValue; } public void setDimensionValue(String dimensionValue) { this.dimensionValue = dimensionValue; } public AbsoluteMetricLoadCacheKey(String namespace, String dimensionValue) { this.namespace = namespace; this.dimensionValue = dimensionValue; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AbsoluteMetricLoadCacheKey that = (AbsoluteMetricLoadCacheKey) o; if (dimensionValue != null ? !dimensionValue.equals(that.dimensionValue) : that.dimensionValue != null) return false; if (namespace != null ? !namespace.equals(that.namespace) : that.namespace != null) return false; return true; } @Override public int hashCode() { int result = namespace != null ? namespace.hashCode() : 0; result = 31 * result + (dimensionValue != null ? dimensionValue.hashCode() : 0); return result; } } public static class AbsoluteMetricCacheKey { private String namespace; private String metricName; private String dimensionName; private String dimensionValue; 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 AbsoluteMetricCacheKey(String namespace, String metricName, String dimensionName, String dimensionValue) { super(); this.namespace = namespace; this.metricName = metricName; this.dimensionName = dimensionName; this.dimensionValue = dimensionValue; } public AbsoluteMetricCacheKey(AbsoluteMetricHistory item) { super(); this.namespace = item.getNamespace(); this.metricName = item.getMetricName(); this.dimensionName = item.getDimensionName(); this.dimensionValue = item.getDimensionValue(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((dimensionName == null) ? 0 : dimensionName.hashCode()); result = prime * result + ((dimensionValue == null) ? 0 : dimensionValue.hashCode()); result = prime * result + ((metricName == null) ? 0 : metricName.hashCode()); result = prime * result + ((namespace == null) ? 0 : namespace.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AbsoluteMetricCacheKey other = (AbsoluteMetricCacheKey) obj; if (dimensionName == null) { if (other.dimensionName != null) return false; } else if (!dimensionName.equals(other.dimensionName)) return false; if (dimensionValue == null) { if (other.dimensionValue != null) return false; } else if (!dimensionValue.equals(other.dimensionValue)) return false; if (metricName == null) { if (other.metricName != null) return false; } else if (!metricName.equals(other.metricName)) return false; if (namespace == null) { if (other.namespace != null) return false; } else if (!namespace.equals(other.namespace)) return false; return true; } } }