/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2012, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.destination.h2timeseries; import static org.helios.apmrouter.destination.h2timeseries.H2TimeSeries.AVG; import static org.helios.apmrouter.destination.h2timeseries.H2TimeSeries.CNT; import static org.helios.apmrouter.destination.h2timeseries.H2TimeSeries.MAX; import static org.helios.apmrouter.destination.h2timeseries.H2TimeSeries.MIN; import static org.helios.apmrouter.destination.h2timeseries.H2TimeSeries.PERIOD; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectStreamException; import java.io.PushbackInputStream; import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.util.Arrays; import java.util.Date; import java.util.Map; import java.util.Random; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicLong; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.sql.rowset.serial.SerialBlob; import javax.sql.rowset.serial.SerialException; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.Logger; import org.h2.tools.SimpleResultSet; import org.helios.apmrouter.collections.ArrayOverflowException; import org.helios.apmrouter.collections.ConcurrentLongSlidingWindow; import org.helios.apmrouter.collections.ILongSlidingWindow; import org.helios.apmrouter.util.SystemClock; /** * <p>Title: UnsafeH2TimeSeries</p> * <p>Description: An reimplementation of {@link H2TimeSeries} using unsafe arrays for storage</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.destination.h2timeseries.UnsafeH2TimeSeries</code></p> */ public class UnsafeH2TimeSeries implements Serializable { /** */ private static final long serialVersionUID = -963955749386799856L; /** Static class logger */ protected static final Logger LOG = Logger.getLogger(UnsafeH2TimeSeries.class); /** The STEP size in ms. */ protected final long step; /** The WIDTH, or number of entries in the window */ protected final int width; /** Indicates if the data stream should be compressed */ protected final boolean compressed; /** A counter of unexpected derived periods */ protected static final AtomicLong periodErrors = new AtomicLong(0L); /** The value store for the periods */ protected final ConcurrentLongSlidingWindow periods; /** The value store for the mins */ protected final ConcurrentLongSlidingWindow mins; /** The value store for the maxes */ protected final ConcurrentLongSlidingWindow maxes; /** The value store for the averages */ protected final ConcurrentLongSlidingWindow averages; /** The value store for the counts */ protected final ConcurrentLongSlidingWindow counts; /** A counter of serialization reads */ private static final AtomicLong SerializationReads = new AtomicLong(0L); /** A counter of serialization writes */ private static final AtomicLong SerializationWrites = new AtomicLong(0L); /** The timestamp of the last serialization metric reset */ private static final AtomicLong LastReset = new AtomicLong(System.currentTimeMillis()); /** The rolling deser bytes */ protected static final ILongSlidingWindow deserBytes = new ConcurrentLongSlidingWindow(60); /** The count of UnsafeH2TimeSeries allocated instances */ private static final AtomicLong AllocatedInstances = new AtomicLong(0L); /** * Resets the statistics */ public static void resetStats() { periodErrors.set(0L); SerializationReads.set(0L); SerializationWrites.set(0L); AllocatedInstances.set(0L); deserBytes.clear(); LastReset.set(System.currentTimeMillis()); } /** * Returns the count of UnsafeH2TimeSeries allocated instances * @return the count of UnsafeH2TimeSeries allocated instances */ public static long getAllocatedInstances() { return AllocatedInstances.get(); } /** * Returns the rolling average of the DB read deserialization bytes * @return the rolling average of the DB read deserialization bytes */ public static long getRollingDeserBytes() { return deserBytes.avg(); } /** * Returns the number of Serialization Reads since the last metric reset * @return The number of Serialization Reads since the last metric reset */ public static long getSerializationReads() { return SerializationReads.get(); } /** * Returns the number of period errors since the last metric reset * @return the number of period errors since the last metric reset */ public static long getRolledPeriodErrors() { return periodErrors.get(); } /** * Returns the number of Serialization Writes since the last metric reset * @return The number of Serialization Writes since the last metric reset */ public static long getSerializationWrites() { return SerializationWrites.get(); } /** * Returns the UTC long timestamp of the last serialization metric reset * @return the UTC long timestamp of the last serialization metric reset */ public static long getLastResetTimestamp() { return LastReset.get(); } /** * Returns the date of the last serialization metric reset * @return the date of the last serialization metric reset */ public static Date getLastResetDate() { return new Date(LastReset.get()); } /** * Resets the serialization metrics and sets the last reset timestamp to current. */ public static void resetSerializationMetrics() { SerializationReads.set(0L); SerializationWrites.set(0L); LastReset.set(System.currentTimeMillis()); } Object writeReplace() throws ObjectStreamException { return UnsafeH2TimeSeries.serialize(this); } protected transient byte[] inBytes; Object readResolve() throws ObjectStreamException { UnsafeH2TimeSeries uts = UnsafeH2TimeSeries.deserialize(inBytes); inBytes = null; return uts; } /** * Deallocates this UnsafeH2TimeSeries */ public void destroy() { periods.destroy(); mins.destroy(); maxes.destroy(); averages.destroy(); counts.destroy(); AllocatedInstances.decrementAndGet(); } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buff = new byte[4096]; int bytesRead = -1; while((bytesRead = in.read(buff))!=-1) { baos.write(buff, 0, bytesRead); } inBytes = baos.toByteArray(); } /** * Creates a new UnsafeH2TimeSeries * @param step The STEP size in ms. * @param width The WIDTH, or number of entries in the window * @param compressed True to compress the time series entries, false otherwise * @param sticky Indicates if the metric is sticky * @return a new UnsafeH2TimeSeries */ public static byte[] make(long step, int width, boolean compressed, boolean sticky) { try { return serialize(new UnsafeH2TimeSeries(step, width, compressed)); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * Creates a new compressed UnsafeH2TimeSeries * @param step The STEP size in ms. * @param width The WIDTH, or number of entries in the window * @param sticky Indicates if the metric is sticky * @return a new UnsafeH2TimeSeries */ public static byte[] make(long step, int width, boolean sticky) { try { return serialize(new UnsafeH2TimeSeries(step, width, true)); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * Creates a new UnsafeH2TimeSeries * @param step The STEP size in ms. * @param width The WIDTH, or number of entries in the window * @param compressed True to compress the time series entries, false otherwise */ public UnsafeH2TimeSeries(long step, int width, boolean compressed) { super(); this.step = step; this.width = width-1; this.compressed = compressed; periods = new ConcurrentLongSlidingWindow(width); mins = new ConcurrentLongSlidingWindow(width); maxes = new ConcurrentLongSlidingWindow(width); averages = new ConcurrentLongSlidingWindow(width); counts = new ConcurrentLongSlidingWindow(width); AllocatedInstances.incrementAndGet(); RefQueueCleaner.createRef(this); } /** * Returns an input stream containing the bytes for this UnsafeH2TimeSeries * @return an input stream containing the bytes for this UnsafeH2TimeSeries * @throws IOException thrown on any IO error */ public InputStream toInputStream() throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream(serialize(this)); return bais; } /** * Creates a new UnsafeH2TimeSeries from an input stream * @param is The input stream to read from * @throws IOException thrown on any IO error */ public UnsafeH2TimeSeries(InputStream is, long length) throws IOException { if(is==null) throw new IllegalArgumentException("InputStream was null", new Throwable()); PushbackInputStream pushIs = new PushbackInputStream(is, (int)length); byte[] gzipTest = new byte[2]; pushIs.read(gzipTest, 0, 2); compressed = isGzip(gzipTest); pushIs.unread(gzipTest); GZIPInputStream dis = null; DataInputStream dais = null; if(compressed) { dis = new GZIPInputStream(pushIs); } dais = new DataInputStream(compressed ? dis : pushIs); step = dais.readLong(); width = dais.readInt(); int sz = dais.readInt(); periods = new ConcurrentLongSlidingWindow(width); mins = new ConcurrentLongSlidingWindow(width); maxes = new ConcurrentLongSlidingWindow(width); averages = new ConcurrentLongSlidingWindow(width); counts = new ConcurrentLongSlidingWindow(width); for(int i = 0; i < sz; i++) { periods.insert(dais.readLong()); mins.insert(dais.readLong()); maxes.insert(dais.readLong()); averages.insert(dais.readLong()); counts.insert(dais.readLong()); } AllocatedInstances.incrementAndGet(); RefQueueCleaner.createRef(this); } /** * Recreates a new UnsafeH2TimeSeries * @param step The STEP size in ms. * @param width The WIDTH, or number of entries in the window * @param compressed True to compress the time series entries, false otherwise * @param arr The byte array to re-initialize with */ public UnsafeH2TimeSeries(long step, int width, boolean compressed, byte[] arr) { this(step, width, compressed); if(arr!=null) { byte[][] split = split(arr, 5); periods.reinitAndLoad(split[PERIOD]); mins.reinitAndLoad(split[MIN]); maxes.reinitAndLoad(split[MAX]); averages.reinitAndLoad(split[AVG]); counts.reinitAndLoad(split[CNT]); } // periods.insert(UnsafeLongArray.convert(split[PERIOD])); // mins.insert(UnsafeLongArray.convert(split[MIN])); // maxes.insert(UnsafeLongArray.convert(split[MAX])); // averages.insert(UnsafeLongArray.convert(split[AVG])); // counts.insert(UnsafeLongArray.convert(split[CNT])); } /** * Splits an array of bytes representing longs into <code>each</code> equally sized arrays * @param arr The array to split * @param each The number of groups to divide into * @return an array of byte arrays */ public static byte[][] split(byte[] arr, int each) { if(arr==null) throw new IllegalArgumentException("The passed array was null", new Throwable()); int len = arr.length; if(len<8) throw new IllegalArgumentException("The passed array was less than 8 bytes", new Throwable()); if(len%8!=0) throw new IllegalArgumentException("The passed array failed mod check [" + len + "]", new Throwable()); int totalEntries = len/8; if(totalEntries%each!=0) throw new IllegalArgumentException("The total entries failed mod check [" + totalEntries + "/" + each + "]", new Throwable()); int longsEach = totalEntries/each; final int bytesEach = longsEach << 3; byte[][] ret = new byte[each][bytesEach]; int srcPos = 0; try { for(int i = 0; i < 5; i++) { System.arraycopy(arr, srcPos, ret[i], 0, bytesEach); srcPos += bytesEach; } } catch (Exception ex) { throw new RuntimeException(ex); } return ret; } /** * Creates a new UnsafeH2TimeSeries and adds a new value * @param conn The connection to lookup the existing time-series * @param step The STEP size in ms. * @param width The WIDTH, or number of entries in the window * @param compressed True to compress, false otherwise * @param sticky Indicates if the metric is sticky * @param id The metric ID of the metric to upsert a time-series entry for * @param ts The timestamp of the value to add * @param value The value to add * @throws Exception Thrown on any error */ public static void make_and_add(Connection conn, long step, int width, boolean compressed, boolean sticky, long id, Timestamp ts, long value) throws Exception { PreparedStatement ps = null; ResultSet rset = null; UnsafeH2TimeSeries mvd = null; try { ps = conn.prepareStatement("SELECT V FROM UNSAFE_METRIC_VALUES WHERE ID = ?"); ps.setLong(1, id); rset = ps.executeQuery(); if(rset.next()) { byte[] utsBytes = rset.getBytes(1); deserBytes.insert(utsBytes.length); mvd = UnsafeH2TimeSeries.deserialize(utsBytes); } else { mvd = new UnsafeH2TimeSeries(step, width, compressed); } mvd.addValue(ts.getTime(), value); rset.close(); ps.close(); ps = conn.prepareStatement("UPDATE UNSAFE_METRIC_VALUES SET V = ? WHERE ID = ?"); ps.setBytes(1, UnsafeH2TimeSeries.serialize(mvd)); ps.setLong(2, id); ps.execute(); } finally { if(mvd!=null) mvd.destroy(); if(rset!=null) try { rset.close(); } catch (Exception ex) { /* No Op */ } if(ps!=null) try { ps.close(); } catch (Exception ex) {/* No Op */} } } /** * Creates a new compressed UnsafeH2TimeSeries and adds a new value * @param conn The connection to lookup the existing time-series * @param step The STEP size in ms. * @param width The WIDTH, or number of entries in the window * @param sticky Indicates if the metric is sticky * @param id The metric ID of the metric to upsert a time-series entry for * @param ts The timestamp of the value to add * @param value The value to add * @throws Exception Thrown on any error */ public static void make_and_add(Connection conn, long step, int width, boolean sticky, long id, Timestamp ts, long value) throws Exception { make_and_add(conn, step, width, true, sticky, id, ts, value); } /** * Tests a byte array to see if it is a valid UnsafeH2TimeSeries * @param data The byte array to test * @return true if the array is a valid UnsafeH2TimeSeries * @throws Exception thrown if the byte array is invalid */ public static boolean isType(byte[] data) throws Exception { if(data==null) return false; UnsafeH2TimeSeries hts = null; try { hts = deserialize(data); return true; } catch (Exception ex) { ex.printStackTrace(System.err); throw ex; } finally { if(hts!=null) hts.destroy(); } } /** * Adds a value to the time-series window * @param timestamp The timestamp in long UTC * @param value The long value to add * @return The prior period's slot if a roll occurred, null if it did not */ public synchronized long[] addValue(long timestamp, long value) { final long period = SystemClock.period(step, timestamp); final long currentPeriod; if(periods.size()==0) { // Insert periods.insert(period); mins.insert(value); maxes.insert(value); averages.insert(value); counts.insert(1L); return null; } currentPeriod = periods.get(0); boolean update = period==currentPeriod; if(!update) { // FIXME: Not sure why it happens, but if it is legit, find the bucket and update it. if(period-currentPeriod < step) { periodErrors.incrementAndGet(); throw new IllegalStateException("Unexpected period increment.\n\tCurrent Period:" + new Date(currentPeriod) + "\n\tNew Period:" + new Date(period) + "\n\tDiff:" + (period-currentPeriod), new Throwable()); } // Roll new period final long[] rolledPeriod = getArray(0); final long[] droppedPeriod = new long[5]; // <---- use this guy to roll up to the next tier. if(periods.size()==step) { // Oldest period dropped droppedPeriod[PERIOD] = periods.insert(period); droppedPeriod[MIN] = mins.insert(value); droppedPeriod[MAX] = maxes.insert(value); droppedPeriod[AVG] = averages.insert(value); droppedPeriod[CNT] = counts.insert(1L); } else { // No drop yet periods.insert(period); mins.insert(value); maxes.insert(value); averages.insert(value); counts.insert(1L); } return rolledPeriod; } // Update current period final long[] newValues = calcValue(getArray(0), period, value); periods.set(newValues[PERIOD]); mins.set(newValues[MIN]); maxes.set(newValues[MAX]); averages.set(newValues[AVG]); counts.set(newValues[CNT]); return null; } /** * Returns an array of the values for the specified period index * @param index The index representing the period to retrieve * @return an array of the values for the specified period index */ protected long[] getArray(int index) { if(index+1 > periods.size()) throw new ArrayOverflowException("Attempted to access at index [" + index + "] but size is [" + periods.size() + "]", new Throwable()); return new long[]{periods.get(index), mins.get(index), maxes.get(index), averages.get(index), counts.get(index)}; } /** * Returns an array of the values for the specified period * @param timestamp The period to retrieve * @return an array of the values for the specified period or null if the period was not found */ protected long[] getPeriodValues(long timestamp) { int index = periods.find(timestamp); if(index>0) return getArray(index); return null; } /** * Calculates the new value range * @param current The current array or null if there is none * @param period The period this value will be allocated to * @param newValue The submitted value * @return The new value array */ protected long[] calcValue(long[] current, long period, long newValue) { if(current==null) { return new long[]{period, newValue, newValue, newValue, 1}; } if(newValue<current[MIN]) current[MIN] = newValue; if(newValue>current[MAX]) current[MAX] = newValue; current[AVG] = current[AVG]==0 ? newValue : (current[AVG]+newValue)/2; current[CNT] = current[CNT]+1; return current; } /** * Returns the number of periods in this time series * @return the number of periods in this time series */ public int size() { return periods.size(); } /** * Returns the time range of values held in this time-series * @return a long array with the start time and end time, or null if there are no entries */ public long[] getTimeRange() { if(periods.isEmpty()) return null; return new long[]{ getArray(0)[PERIOD], getArray(size()-1)[PERIOD] }; } /** * Returns the date range of values held in this time-series * @return a {@link java.sql.Date} array with the start time and end time, or null if there are no entries */ public java.sql.Date[] getDateRange() { if(periods.isEmpty()) return null; return new java.sql.Date[]{ new java.sql.Date(getArray(0)[PERIOD]), new java.sql.Date(getArray(size()-1)[PERIOD]) }; } /** * Returns the STEP in ms. * @return the STEP */ public long getStep() { return step; } /** * Returns the WIDTH of the time-series window * @return the WIDTH */ public int getWidth() { return width; } /** * Returns the current number of items in the window * @return the size */ public int getSize() { return size(); } /** * Returns the number of bytes this object is expected to seralize to * @return the number of bytes this object is expected to seralize to */ public int byteSize() { return ((8+4+4) /* step, width and size */ + (size()*8*5)) /* # of longs in each window X the # of windows*/; } /** * Returns all the resolved values in the time-series in a result set * @param data The byte array * @return A result set iterating all the values in the time-series * @throws Exception thrown on any error */ public static ResultSet allvalues(byte[] data) throws Exception { UnsafeH2TimeSeries mvd = null; try { mvd = deserialize(data); SimpleResultSet rs = new SimpleResultSet(); rs.addColumn("TS", Types.TIMESTAMP, 1, 22); rs.addColumn("MIN", Types.NUMERIC, 255, 22); rs.addColumn("MAX", Types.NUMERIC, 255, 22); rs.addColumn("AVG", Types.NUMERIC, 255, 22); rs.addColumn("CNT", Types.NUMERIC, 255, 22); for(int i = 0; i < mvd.periods.size(); i++) { long[] row = mvd.getArray(i); if(row==null) continue; rs.addRow( new java.sql.Timestamp(row[PERIOD]), row[MIN], row[MAX], row[AVG], row[CNT]); } return rs; } finally { if(mvd!=null) mvd.destroy(); } } /** * Exposed as the SQL function <b><code>UNSAFE_MV</code></b> * @param conn The H2 connection * @param oldestPeriod The oldest period to retrieve values for * @param ids An array of IDs to get data for * @return A result set * @throws SQLException */ public static ResultSet getValues(Connection conn, long oldestPeriod, Long...ids) throws SQLException { SimpleResultSet rs = new SimpleResultSet(); rs.addColumn("ID", Types.NUMERIC, 255, 22); rs.addColumn("TS", Types.TIMESTAMP, 1, 22); rs.addColumn("MIN", Types.NUMERIC, 255, 22); rs.addColumn("MAX", Types.NUMERIC, 255, 22); rs.addColumn("AVG", Types.NUMERIC, 255, 22); rs.addColumn("CNT", Types.NUMERIC, 255, 22); String url = conn.getMetaData().getURL(); if (url.equals("jdbc:columnlist:connection")) { return rs; } Arrays.sort(ids); PreparedStatement ps = null; ResultSet rset = null; try { StringBuilder q = new StringBuilder("SELECT V, ID FROM UNSAFE_METRIC_VALUES"); if(ids!=null && ids.length>0 && ids[0] != -1L) { q.append(" WHERE ID IN ("); q.append(Arrays.toString(ids).replace("[", "").replace("]", "")); q.append(")"); } ps = conn.prepareStatement(q.toString()); //ps.setArray(1, conn.createArrayOf("java.lang.Long", ids)); rset = ps.executeQuery(); while(rset.next()) { UnsafeH2TimeSeries mvd = null; try { byte[] utsBytes = rset.getBytes(1); deserBytes.insert(utsBytes.length); mvd = UnsafeH2TimeSeries.deserialize(utsBytes); Map<Long, long[]> orderedMap = new TreeMap<Long, long[]>(); for(int i = 0; i < mvd.getSize(); i++) { long[] row = mvd.getArray(i); if(row[PERIOD]<oldestPeriod) continue; orderedMap.put(row[PERIOD], row); } long mid = rset.getLong(2); for(Map.Entry<Long, long[]> entry: orderedMap.entrySet()) { long[] row = entry.getValue(); if(row==null || row[PERIOD]<oldestPeriod) continue; rs.addRow( mid, new java.sql.Timestamp(row[PERIOD]), row[MIN], row[MAX], row[AVG], row[CNT]); } } finally { if(mvd!=null) mvd.destroy(); } } } finally { if(rset!=null) try { rset.close(); } catch (Exception ex) {/* No Op */} if(ps!=null) try { ps.close(); } catch (Exception ex) {/* No Op */} } return rs; } /** * Adds a value to the UnsafeH2TimeSeries deserialized from the passed byte array * @param data The byte array to be desrialized into a UnsafeH2TimeSeries * @param timestamp The effective timestamp of the data to be added * @param value The data to be added * @throws Exception thrown on any error */ public static void add(byte[] data, Timestamp timestamp, long value) throws Exception { UnsafeH2TimeSeries mvd = null; try { mvd = deserialize(data); mvd.addValue(timestamp.getTime(), value); } finally { if(mvd!=null) mvd.destroy(); } } /** * Serializes a UnsafeH2TimeSeries to a byte array * @param mvd The UnsafeH2TimeSeries to serialize * @return A byte array */ public static byte[] serialize(UnsafeH2TimeSeries mvd) { ByteArrayOutputStream out = new ByteArrayOutputStream(mvd.byteSize()); GZIPOutputStream dos = null; DataOutputStream dout = null; try { if(mvd.compressed) { dos = new GZIPOutputStream(out, mvd.byteSize(), true); } dout = new DataOutputStream(mvd.compressed ? dos : out); dout.writeLong(mvd.step); dout.writeInt(mvd.width); int sz = mvd.periods.size(); dout.writeInt(sz); if(sz>0) { dout.write(mvd.periods.getBytes()); dout.write(mvd.mins.getBytes()); dout.write(mvd.maxes.getBytes()); dout.write(mvd.averages.getBytes()); dout.write(mvd.counts.getBytes()); } if(dos!=null) dos.finish(); dout.flush(); out.flush(); } catch (Exception ex) { throw new RuntimeException(ex); } SerializationWrites.incrementAndGet(); return out.toByteArray(); } /** * Deserializes a UnsafeH2TimeSeries from a byte array * @param arr The byte array to deserialize from * @return The deserialized UnsafeH2TimeSeries */ public static UnsafeH2TimeSeries deserialize(byte[] arr) { arr = testForClassDesc(arr); final boolean gzip = isGzip(arr); ByteArrayInputStream bais = new ByteArrayInputStream(arr); GZIPInputStream dis = null; DataInputStream dais = null; try { if(gzip) { dis = new GZIPInputStream(bais); } dais = new DataInputStream(gzip ? dis : bais); long step = dais.readLong(); int width = dais.readInt(); int arrSize = dais.readInt(); byte[] narr = null; if(arrSize>0) { narr = new byte[arrSize*8*5]; dais.readFully(narr); } SerializationReads.incrementAndGet(); return new UnsafeH2TimeSeries(step, width, gzip, narr); } catch (Exception ex) { throw new RuntimeException(ex); } } /** * Determines if the channel is carrying a gzipped metric submssion * @param arr The byte array being inspected for the gzip magic signature * @return true if the incoming payload is gzipped */ public static boolean isGzip(byte[] arr) { if(arr==null || arr.length<2) return false; int magic1 = (short) (arr[0] & 0xFF); int magic2 = (short) (arr[1] & 0xFF); return magic1 == 31 && magic2 == 139; } public static byte[] testForClassDesc(byte[] arr) { // 27 bytes if(arr==null || arr.length<2) return null; int magic1 = (short) (arr[0] & 0xFF); int magic2 = (short) (arr[1] & 0xFF); if(magic1 == 172 && magic2 == 237) { byte[] newArr = new byte[arr.length-27]; System.arraycopy(arr, 27, newArr, 0, arr.length-27); return newArr; } return arr; } public static void main(String[] args) { BasicConfigurator.configure(); LOG.info("UnsafeH2TimeSeries Test"); UnsafeH2TimeSeries ts = new UnsafeH2TimeSeries(2000, 3, false); Random random = new Random(System.currentTimeMillis()); for(int x = 0; x < 5; x++) { int loops = Math.abs(random.nextInt(100)) + 100; for(int i = 0; i < loops; i++) { long[] rolled = ts.addValue(SystemClock.time(), Math.abs(random.nextInt(100))+1); if(rolled!=null) { LOG.info("Rolled Period:" + Arrays.toString(rolled)); } } LOG.info("Completed Iter " + x); SystemClock.sleep(2000); } LOG.info("Completed Population:\n" + ts); LOG.info("Serializing. Expected Size:" + ts.byteSize()); byte[] arr = UnsafeH2TimeSeries.serialize(ts); LOG.info("Serialized. Actual Size:" + arr.length); UnsafeH2TimeSeries ts2 = UnsafeH2TimeSeries.deserialize(arr); LOG.info("Completed Deserialization:\n" + ts2); LOG.info("Done"); } /** * {@inheritDoc} * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder b = new StringBuilder(); b.append("MetricValue[STEP:").append(step).append(" WIDTH:").append(width).append("]"); for(int i = 0; i < size(); i++) { b.append("\n\t").append(entryToString(getArray(i))); } return b.toString(); } /** * Renders a slot entry as a string * @param arr The entry to render * @return the rendered entry */ protected String entryToString(long[] arr) { StringBuilder b = new StringBuilder(); b.append("[").append(new Date(arr[PERIOD])).append("]:"); b.append("[").append(arr[CNT]).append("]"); b.append(" min:").append(arr[MIN]); b.append(" max:").append(arr[MAX]); b.append(" avg:").append(arr[AVG]); return b.toString(); } }