package com.linkedin.thirdeye.api; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInput; import java.io.DataInputStream; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.linkedin.thirdeye.util.NumberUtils; /** * @author kgopalak */ public class MetricTimeSeries { private static final Logger LOGGER = LoggerFactory.getLogger(MetricTimeSeries.class); Map<Long, ByteBuffer> timeseries; private MetricSchema schema; /** * @param schema */ public MetricTimeSeries(MetricSchema schema) { timeseries = new HashMap<Long, ByteBuffer>(); this.schema = schema; } public MetricSchema getSchema() { return schema; } /** * @param timeWindow * @param value */ public void set(long timeWindow, String name, Number value) { initBufferForTimeWindow(timeWindow); ByteBuffer buffer = timeseries.get(timeWindow); buffer.position(schema.getOffset(name)); MetricType metricType = schema.getMetricType(name); switch (metricType) { case SHORT: buffer.putShort(value.shortValue()); break; case INT: buffer.putInt(value.intValue()); break; case LONG: buffer.putLong(value.longValue()); break; case FLOAT: buffer.putFloat(value.floatValue()); break; case DOUBLE: buffer.putDouble(value.doubleValue()); break; } } private void initBufferForTimeWindow(long timeWindow) { if (!timeseries.containsKey(timeWindow)) { byte[] bytes = new byte[schema.getRowSizeInBytes()]; timeseries.put(timeWindow, ByteBuffer.wrap(bytes)); } } public Number get(long timeWindow, String name) { ByteBuffer buffer = timeseries.get(timeWindow); Number ret = 0; if (buffer != null) { buffer = buffer.duplicate(); MetricType metricType = schema.getMetricType(name); buffer.position(schema.getOffset(name)); switch (metricType) { case SHORT: ret = buffer.getShort(); break; case INT: ret = buffer.getInt(); break; case LONG: ret = buffer.getLong(); break; case FLOAT: ret = buffer.getFloat(); break; case DOUBLE: ret = buffer.getDouble(); break; } } return ret; } public void increment(long timeWindow, String name, Number delta) { initBufferForTimeWindow(timeWindow); ByteBuffer buffer = timeseries.get(timeWindow); Number newValue; if (buffer != null) { // get Old Value Number oldValue = get(timeWindow, name); MetricType metricType = schema.getMetricType(name); switch (metricType) { case SHORT: newValue = oldValue.shortValue() + delta.shortValue(); break; case INT: newValue = oldValue.intValue() + delta.intValue(); break; case LONG: newValue = oldValue.longValue() + delta.longValue(); break; case FLOAT: newValue = oldValue.floatValue() + delta.floatValue(); break; case DOUBLE: newValue = oldValue.doubleValue() + delta.doubleValue(); break; default: throw new UnsupportedOperationException( "unknown metricType:" + metricType + " for column:" + name); } } else { newValue = delta; } set(timeWindow, name, newValue); } public void aggregate(MetricTimeSeries series) { for (long timeWindow : series.timeseries.keySet()) { for (int i = 0; i < schema.getNumMetrics(); i++) { String metricName = schema.getMetricName(i); Number delta = series.get(timeWindow, metricName); increment(timeWindow, metricName, delta); } } } /** * @param series * A time series whose values should be reflected in this time series * @param timeRange * Only include values from series that are in this time range */ public void aggregate(MetricTimeSeries series, TimeRange timeRange) { for (long timeWindow : series.timeseries.keySet()) { if (timeRange.contains(timeWindow)) { for (int i = 0; i < schema.getNumMetrics(); i++) { String metricName = schema.getMetricName(i); Number delta = series.get(timeWindow, metricName); increment(timeWindow, metricName, delta); } } } } public static MetricTimeSeries fromBytes(byte[] buf, MetricSchema schema) throws IOException { MetricTimeSeries series = new MetricTimeSeries(schema); DataInput in = new DataInputStream(new ByteArrayInputStream(buf)); int numTimeWindows = in.readInt(); int bufferSize = in.readInt(); for (int i = 0; i < numTimeWindows; i++) { long timeWindow = in.readLong(); byte[] bytes = new byte[bufferSize]; in.readFully(bytes); series.timeseries.put(timeWindow, ByteBuffer.wrap(bytes)); } return series; } /** * @return */ public Set<Long> getTimeWindowSet() { return timeseries.keySet(); } public byte[] toBytes() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutput out = new DataOutputStream(baos); // write the number of timeWindows out.writeInt(timeseries.size()); // write the size of the metric buffer for each timeWindow out.writeInt(schema.getRowSizeInBytes()); for (long time : timeseries.keySet()) { out.writeLong(time); out.write(timeseries.get(time).array()); } return baos.toByteArray(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("("); for (long timeWindow : timeseries.keySet()) { sb.append("["); String delim = ""; ByteBuffer buffer = timeseries.get(timeWindow); buffer.rewind(); for (int i = 0; i < schema.getNumMetrics(); i++) { if (i > 0) { delim = ","; } sb.append(delim).append(NumberUtils.readFromBuffer(buffer, schema.getMetricType(i))); } sb.append("]"); sb.append("@"); sb.append(timeWindow); sb.append(" "); } sb.setLength(sb.length() - 1); sb.append(")"); return sb.toString(); } public Number[] getMetricSums() { Number[] result = new Number[schema.getNumMetrics()]; for (int i = 0; i < schema.getNumMetrics(); i++) { result[i] = 0; } for (Long time : timeseries.keySet()) { for (int i = 0; i < schema.getNumMetrics(); i++) { String metricName = schema.getMetricName(i); MetricType metricType = schema.getMetricType(i); Number metricValue = get(time, metricName); switch (metricType) { case INT: result[i] = result[i].intValue() + metricValue.intValue(); break; case SHORT: result[i] = result[i].shortValue() + metricValue.shortValue(); break; case LONG: result[i] = result[i].longValue() + metricValue.longValue(); break; case FLOAT: result[i] = result[i].floatValue() + metricValue.floatValue(); break; case DOUBLE: result[i] = result[i].doubleValue() + metricValue.doubleValue(); break; default: throw new IllegalStateException(); } } } return result; } @Override public int hashCode() { return timeseries.keySet().hashCode() + 13 * schema.hashCode(); } @Override public boolean equals(Object o) { if (!(o instanceof MetricTimeSeries)) { return false; } MetricTimeSeries ts = (MetricTimeSeries) o; return getTimeWindowSet().equals(ts.getTimeWindowSet()) && Arrays.equals(getMetricSums(), ts.getMetricSums()); } }