/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, 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.trace; import org.helios.apmrouter.OpCode; import org.helios.apmrouter.metric.ICEMetric; import org.helios.apmrouter.metric.IMetric; import org.helios.apmrouter.metric.MetricType; import org.helios.apmrouter.metric.catalog.ICEMetricCatalog; import org.helios.apmrouter.metric.catalog.IDelegateMetric; import org.helios.apmrouter.sender.ISender; import org.helios.apmrouter.sender.SenderFactory; import org.helios.apmrouter.unsafe.UnsafeAdapter; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import sun.misc.Unsafe; import sun.nio.ch.DirectBuffer; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.*; import static org.helios.apmrouter.util.Methods.nvl; /** * <p>Title: DirectMetricCollection</p> * <p>Description: </p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.trace.DirectMetricCollection</code></p> */ public class DirectMetricCollection implements Runnable { /** The unsafe instance */ protected static final Unsafe unsafe; /** The initial capacity allocation which will be equal to the JVM's memory page size */ public static final int INITIAL; /** The extend capacity allocation */ public static final int EXTEND; /** The byte array offset */ public static final int BYTE_ARRAY_OFFSET; /** The offset from the address where the actual metric data starts */ public static final int METRIC_OFFSET = 10; /** The offset from the address of the byte order byte */ public static final int BYTE_ORDER_OFFSET = 1; /** The offset from the address where the DCM byte size is */ public static final int SIZE_OFFSET = 2; /** The offset from the address where the DCM metric count is */ public static final int COUNT_OFFSET = 6; /** Zero byte literal */ public static final byte BYTE_ZERO = 0; /** One byte literal */ public static final byte BYTE_ONE = 1; /** The byte order indicator */ public static final byte BYTE_ORDER = ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN) ? BYTE_ZERO : BYTE_ONE; /** The reverse byte order indicator */ public static final byte R_BYTE_ORDER = BYTE_ORDER==BYTE_ONE ? BYTE_ZERO : BYTE_ONE; /** The reverse byte order */ public static final ByteOrder REV_BYTE_ORDER = ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN) ? ByteOrder.BIG_ENDIAN: ByteOrder.LITTLE_ENDIAN; /** the address of the direct memory allocation */ protected long address = 0; /** the current size of this collection in bytes */ protected int size = 0; /** the current capacity of this collection in bytes */ protected int capacity = 0; /** The DMC sender */ protected static volatile ISender sender; static { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe = (Unsafe)field.get(null); BYTE_ARRAY_OFFSET = unsafe.arrayBaseOffset(byte[].class); INITIAL = unsafe.pageSize(); EXTEND = unsafe.pageSize()/8; } catch (Exception e) { throw new RuntimeException("Failed to get the Unsafe instance", e); } } /** * Creates a new DirectMetricCollection using the default initial capacity of {@link #INITIAL} and loads it with the passed metrics * @param metrics The initial metrics to load * @return the loaded DirectMetricCollection */ public static DirectMetricCollection newDirectMetricCollection(IMetric...metrics) { return newDirectMetricCollection(INITIAL, metrics); } /** * Creates a new DirectMetricCollection and loads it with the passed metrics * @param initialCapacity The inital capacity of this DMC in bytes * @param metrics The initial metrics to load * @return the loaded DirectMetricCollection */ public static DirectMetricCollection newDirectMetricCollection(int initialCapacity, IMetric...metrics) { if(sender==null) { sender = SenderFactory.getInstance().getDefaultSender(); } DirectMetricCollection dmc = new DirectMetricCollection(initialCapacity); for(IMetric m: metrics) { dmc._append(m); } return dmc; } /** * Determines if the DMC is full * @param maxByteSize The maximum number of bytes * @param maxMetricCount The maximum number of metrics * @return true if the DMC is full, false otherwise */ protected boolean isFull(int maxByteSize, int maxMetricCount) { return (getSize()>=maxByteSize || getMetricCount()>=maxMetricCount); } /** * Loads a collection of IMetrics * @param maxByteSize The maximum number of bytes * @param maxMetricCount The maximum number of metrics * @param metrics the metrics to load * @return true if the DMC is full after this operation, false otherwise */ public boolean append(int maxByteSize, int maxMetricCount, Collection<IMetric> metrics) { if(metrics!=null && !metrics.isEmpty()) { for(IMetric metric: metrics) { _append(metric); } } return isFull(maxByteSize, maxMetricCount); } /** * Returns the current byte order * @return the current byte order */ private byte getByteOrder() { return unsafe.getByte(address + BYTE_ORDER_OFFSET); } /** * Reverses the byte order byte * @return this DMC */ public DirectMetricCollection reverseByteOrder() { unsafe.putByte(address + BYTE_ORDER_OFFSET, unsafe.getByte(address + BYTE_ORDER_OFFSET)==BYTE_ZERO ? BYTE_ONE : BYTE_ZERO); return this; } /** * Loads an array of IMetrics * @param maxByteSize The maximum number of bytes * @param maxMetricCount The maximum number of metrics * @param metrics the metrics to load * @return true if the DMC is full after this operation, false otherwise */ public boolean append(int maxByteSize, int maxMetricCount, IMetric...metrics) { _check(); for(IMetric metric: metrics) { _append(metric); } return isFull(maxByteSize, maxMetricCount); } /** * Overrides the opCode * @param opCode The op code to set to */ public void setOpCode(OpCode opCode) { nvl(opCode, "SenderOpCode"); unsafe.putByte(address, opCode.op()); } /** * Returns the currently set op code * @return the currently set op code */ public OpCode getOpCode() { return OpCode.valueOf(unsafe.getByte(address)); } /** * Checks the memory allocation */ private void _check() { if(address==0) throw new RuntimeException("Call to DCM with unallocated address", new Throwable()); } /** * <p>Sends this collection * {@inheritDoc} * @see java.lang.Runnable#run() */ public void run() { sender.send(this); } /** * Loads an IMetric * @param metric the metric to load * @return the number of metrics in the collection after this operation completes */ protected int _append(IMetric metric) { return _appendOpt(metric); } /** * Loads an IMetric * @param metric the metric to load * @return the number of metrics in the collection after this operation completes */ private int _appendDebug(IMetric metric) { while(size + metric.getSerSize()+6 > capacity) extend(); long token = metric.getToken(); final int currentSize = size; int VS = 0; // write the place-holder for the size writeInt(0); VS+= 4; // write the type code writeByte((byte)metric.getType().ordinal()); VS+= 1; if(token!=-1) { writeByte(BYTE_ONE); VS+= 1; writeLong(token); VS+= 8; } else { writeByte(BYTE_ZERO); VS+= 1; byte[] fqnBytes = metric.getFQN().getBytes(); writeInt(fqnBytes.length); VS+= 4; writeBytes(fqnBytes); VS+= fqnBytes.length; } writeLong(metric.getTime()); VS+= 8; if(metric.getType().isLong()) { writeLong(metric.getLongValue()); VS+= 8; } else { ByteBuffer bb = metric.getRawValue(); int sz = bb.limit(); writeInt(sz); VS+= 4; writeBytes(bb); VS+= sz; } // ========================================================== // TXCONTEXT // PreTXByte:64/54 // PostTXByte:65/55 // ========================================================== if(metric.hasTXContext()) { log("PreTXByte:" + size + "/" + VS); writeByte(BYTE_ONE); VS+= 1; log("PostTXByte:" + size + "/" + VS); TXContext tx = metric.getTXContext(); writeLong(tx.getTxId().getLeastSignificantBits()); VS+= 8; writeLong(tx.getTxId().getMostSignificantBits()); VS+= 8; writeInt(tx.getTxQualifier()); VS+= 4; writeInt(tx.getTxThreadId()); VS+= 4; log("Attached TX Context " + metric.getTXContext()); } else { writeByte(BYTE_ZERO); VS+= 1; } // ========================================================== int metricSize = size - currentSize; unsafe.putInt(address + currentSize, metricSize); log("Record Size:" + metricSize); // update the DMC size setSize(size); log("Metric Size:" + metricSize + " New Buff Size:" + size + " Size Offset:" + currentSize + " GS:" + getSize() + " RS:" + unsafe.getInt(address + currentSize) + " VS:" + VS); return updateCount(); } private int _appendOpt(IMetric metric) { while(size + metric.getSerSize()+6 > capacity) extend(); long token = metric.getToken(); final int currentSize = size; // write the place-holder for the size writeInt(0); // write the type code writeByte((byte)metric.getType().ordinal()); if(token!=-1) { writeByte(BYTE_ONE); writeLong(token); } else { writeByte(BYTE_ZERO); byte[] fqnBytes = metric.getFQN().getBytes(); writeInt(fqnBytes.length); writeBytes(fqnBytes); } writeLong(metric.getTime()); if(metric.getType().isLong()) { writeLong(metric.getLongValue()); } else { ByteBuffer bb = metric.getRawValue(); int sz = bb.limit(); writeInt(sz); writeBytes(bb); } // ========================================================== // TXCONTEXT // ========================================================== if(metric.hasTXContext()) { writeByte(BYTE_ONE); TXContext tx = metric.getTXContext(); writeLong(tx.getTxId().getLeastSignificantBits()); writeLong(tx.getTxId().getMostSignificantBits()); writeInt(tx.getTxQualifier()); writeInt(tx.getTxThreadId()); } else { writeByte(BYTE_ZERO); } // ========================================================== int metricSize = size - currentSize; unsafe.putInt(address + currentSize, metricSize); // update the DMC size setSize(size); return updateCount(); } /** * Decodes this DMC back into an array of IMetrics * @return an array of IMetrics */ public IMetric[] decode() { List<IMetric> metrics = new ArrayList<IMetric>(getMetricCount()); for(IMetric metric: newMetricReader()) { metrics.add(metric); } return metrics.toArray(new IMetric[0]); } /** * Recreates a DirectMetricCollection from a ChannelBuffer * @param cb The ChannelBuffer to read the bytes from * @return the new DirectMetricCollection * FIXME: the order of ints and longs is wrong when we do this */ public static DirectMetricCollection fromChannelBuffer(ChannelBuffer cb) { ChannelBuffer rbuff = ChannelBuffers.directBuffer(cb.order().equals(ByteOrder.LITTLE_ENDIAN) ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN, cb.readableBytes()); rbuff.writeBytes(cb); byte[] bytes = new byte[rbuff.readableBytes()]; rbuff.readBytes(bytes); DirectMetricCollection d = new DirectMetricCollection(bytes.length); UnsafeAdapter.copyMemory(bytes, BYTE_ARRAY_OFFSET, null, d.address, bytes.length); d.size = bytes.length; d.setSize(bytes.length); bytes = null; return d; } /** * Writes this DMC to a direct {@link ChannelBuffer} and then destroys. * @return a loaded {@link ChannelBuffer} * TODO: Get rid of the call to ICEMetricCatalog. Need to store the type even if it is tokenized. */ public ChannelBuffer toChannelBufferX() { shrinkWrap(); byte[] bytes = new byte[size]; UnsafeAdapter.copyMemory(null, address, bytes, BYTE_ARRAY_OFFSET, size); destroy(); ChannelBuffer cb = ChannelBuffers.directBuffer(bytes.length); cb.writeBytes(bytes); return cb; } /** * Writes this DMC to a direct {@link ChannelBuffer} and then destroys. * @return a loaded {@link ChannelBuffer} * TODO: Get rid of the call to ICEMetricCatalog. Need to store the type even if it is tokenized. */ public ChannelBuffer toChannelBuffer() { shrinkWrap(); ChannelBuffer cb = ChannelBuffers.directBuffer(getSize()); // =================================== // WRITE HEADER // =================================== // the op code (0 for metrics, 1 for direct metrics, etc.) cb.writeByte(getOpCode().op()); // The byte order of this message cb.writeByte(BYTE_ORDER); // The size of this message in bytes cb.writeInt(getSize()); // the number of metrics in this dmc cb.writeInt(getMetricCount()); // =================================== // WRITE BODY // =================================== Reader r = new Reader(); while(r._next()) { int msize = r.readInt(); cb.writeInt(msize); // the size of this metric byte typeOrdinal = r.readByte(); MetricType t = MetricType.valueOf(typeOrdinal); cb.writeByte(typeOrdinal); // the metric type byte byte isToken = r.readByte(); // the token indicator cb.writeByte(isToken); // the token indicator if(BYTE_ONE==isToken) { long token = r.readLong(); cb.writeLong(token); // the token } else { //byte[] fqn = r.readString().getBytes(); int fqnSize = r.readInt(); byte[] fqnBytes = r.readBytes(fqnSize); cb.writeInt(fqnSize); // the fqn length cb.writeBytes(fqnBytes); // the fqn } cb.writeLong(r.readLong()); if(t.isLong()) { cb.writeLong(r.readLong()); } else { int byteLength = r.readInt(); cb.writeInt(byteLength); // the length of the next sequence of bytes ByteBuffer bb = r.readByteBuffer(byteLength); cb.writeBytes(bb); // the value bytes } // ========================================================== // TXCONTEXT // ========================================================== byte tx = r.readByte(); cb.writeByte(tx); if(tx==BYTE_ONE) { long txIdLeast = r.readLong(); long txIdMost = r.readLong(); int txQualifier = r.readInt(); int txThreadId = r.readInt(); cb.writeLong(txIdLeast); cb.writeLong(txIdMost); cb.writeInt(txQualifier); cb.writeInt(txThreadId); //log("Wrote TXContext to ChannelBuffer" + String.format("[%s-%s-%s]", txId, txQualifier, txThreadId)); // cb.writeBytes(r.readBytes(TXContext.TXCONTEXT_SIZE));txId, } } // byte[] bytes = new byte[size]; // UnsafeAdapter.copyMemory(null, (address), bytes, BYTE_ARRAY_OFFSET, size); // destroy(); // cb.writeBytes(bytes); // logBits(cb); // logBits(bytes); return cb; } private void logBits(ChannelBuffer cb) { try { int size = cb.getInt(0); int count = cb.getInt(4); System.err.println("logBits [" + size + "/" + count + "]"); } catch (Exception e) { e.printStackTrace(System.err); } } private void logBits(byte[] bytes) { try { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); DataInputStream dis = new DataInputStream(bais); int size = dis.readInt(); int count = dis.readInt(); System.err.println("logBits [" + size + "/" + count + "]"); } catch (Exception e) { e.printStackTrace(System.err); } } /** * Copies memory in from another address * @param otherAddress The other address * @param byteCount The number of bytes to copy */ protected void copyFrom(long otherAddress, int byteCount) { while(size + byteCount > capacity) extend(); unsafe.copyMemory(otherAddress, address+size, byteCount); size += byteCount; updateCount(); setSize(size); } /** * Copies memory in from another address * @param srcObject The source object * @param srcOffset The source object offset * @param byteCount The number of bytes to copy */ protected void copyFrom(Object srcObject, long srcOffset, int byteCount) { while(size + byteCount > capacity) extend(); UnsafeAdapter.copyMemory(srcObject, srcOffset, null, address, byteCount); size += byteCount; updateCount(); setSize(size); } public static void log(Object msg) { System.out.println(msg); } /** * <p>Title: Reader</p> * <p>Description: Base class for cursor controlled readers</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.trace.DirectMetricCollection.Reader</code></p> */ protected class Reader { /** The current offset for this reader */ private int offset = METRIC_OFFSET; /** The number of metrics to read */ private final int metricCount = getMetricCount(); /** The current cursor */ private int cursor = -1; /** The root address offset for the current record */ private int rootAddress = METRIC_OFFSET; /** The byte size of the current record */ private int byteCount = -1; protected Reader() { byteCount = getRecordSize(); } /** * Returns the number of bytes in the current record * @return the number of bytes in the current record */ public int getRecordByteSize() { return byteCount; } /** * Returns the memory offset of the first byte of the current record * @return the memory offset of the first byte of the current record */ public int getRecordOffset() { return rootAddress; } /** * Returns the Reader's cursor offset within the current record * @return the Reader's cursor offset within the current record */ public int getReaderRecordOffset() { return offset; } public int getRecordRemainingBytes() { return byteCount-offset+rootAddress; } public boolean hasNext() { return cursor<metricCount-1; // 1 2 } /** * No Op. Not supported yet. */ public void remove() { } /** * Increments the offset to the next root address * @return true if the offset was incremented, false if the cursor is EOF * FIXME: After record 0 is run, (rootAddress + byteCount = offset) but is 3 more ON SERVER SIDE. */ protected boolean _next() { cursor++; if(cursor==metricCount) return false; if(cursor==0) return true; // since we're already at the 0th record. // roll into the next record. rootAddress += byteCount; offset = rootAddress; byteCount = getRecordSize(); return true; } /** * Returns the size of the record without incrementing the counter * @return the size of the record in bytes */ protected int getRecordSize() { //System.out.println("[" + Thread.currentThread() + "] Getting current record size [" + rootAddress + "], Cursor:" + cursor + " MetricCount:" + metricCount); int i = unsafe.getInt(address + rootAddress); return getByteOrder()==BYTE_ZERO ? i : Integer.reverseBytes(i); } /** * Reads a byte * @return the read byte */ protected byte readByte() { byte b = unsafe.getByte(address + offset); offset++; return b; } /** * Reads an int * @return the read int */ protected int readInt() { int i = unsafe.getInt(address + offset); offset += 4; return getByteOrder()==BYTE_ZERO ? i : Integer.reverseBytes(i); } /** * Reads a long * @return the read long */ protected long readLong() { long v = unsafe.getLong(address + offset); offset += 8; return getByteOrder()==BYTE_ZERO ? v : Long.reverseBytes(v); } /** * Reads a string * @return the read string */ protected String readString() { return new String(readBytes(readInt())); } /** * Reads the specified number of bytes and returns them in a byte array * @param bytesToRead the number of bytes to read * @return a byte array */ protected byte[] readBytes(int bytesToRead) { byte[] bytes = new byte[bytesToRead]; UnsafeAdapter.copyMemory(null, (address + offset), bytes, BYTE_ARRAY_OFFSET, bytesToRead); offset += bytesToRead; return bytes; } /** * Reads the specified number of bytes and returns them in a direct ByteBuffer * @param bytesToRead the number of bytes to read * @return a direct ByteBuffer */ protected ByteBuffer readByteBuffer(int bytesToRead) { ByteBuffer bb = ByteBuffer.allocateDirect(byteCount); bb.put(readBytes(bytesToRead)); bb.flip(); return bb; } } /** * <p>Title: MetricReader</p> * <p>Description: A cursor supported metric decoder</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.trace.DirectMetricCollection.MetricReader</code></p> */ protected class MetricReader extends Reader implements Iterator<IMetric>, Iterable<IMetric> { /** * {@inheritDoc} * @see java.util.Iterator#next() */ @SuppressWarnings("unused") @Override public IMetric next() { return nextOpt(); } private IMetric nextDebug() { log("Reading next IMetric"); if(!_next()) throw new RuntimeException("Iterator fetched pass EOF", new Throwable()); IDelegateMetric dmetric = null; int recordSize = readInt(); // skip size log("\tRecord Size:" + recordSize + " Offset:" + getReaderRecordOffset()); byte typeOrdinal = readByte(); log("\tType Ord:" + typeOrdinal + " Offset:" + getReaderRecordOffset()); MetricType type = MetricType.valueOf(typeOrdinal); byte isToken = readByte(); log("\tisToken:" + isToken + " Offset:" + getReaderRecordOffset()); if(BYTE_ONE==isToken) { long token = readLong(); log("\tToken:" + token + " Offset:" + getReaderRecordOffset()); dmetric = ICEMetricCatalog.getInstance().get(token); } else { String fqn = readString(); log("\tFQN:" + fqn + " Offset:" + getReaderRecordOffset()); dmetric = ICEMetricCatalog.getInstance().build(fqn, type); } long time = readLong(); log("\tTime:" + new Date(time) + " Offset:" + getReaderRecordOffset()); ICEMetric metric = null; if(type.isLong()) { long value = readLong(); log("\tLong Value:" + value + " Offset:" + getReaderRecordOffset()); metric = ICEMetric.newMetric(time, value, type, dmetric); } else { int bbSize = readInt(); log("\tBB Size:" + bbSize + " Offset:" + getReaderRecordOffset()); ByteBuffer bb = readByteBuffer(bbSize); log("\tBB:" + bb.toString() + " Offset:" + getReaderRecordOffset()); metric = ICEMetric.newMetric(time, bb, type, dmetric); } // ========================================================== // TXCONTEXT // ========================================================== byte tp = readByte(); log("\tTP:" + tp + " Offset:" + getReaderRecordOffset()); //log("\nRead Byte: [" + tp + "]\nRemaining Bytes:" + getRecordRemainingBytes() + "\nValue:" + metric.getLongValue()); if(getRecordRemainingBytes()>=TXContext.TXCONTEXT_SIZE) { long txIdLeast = readLong(); long txIdMost = readLong(); UUID uuid = new UUID(txIdMost, txIdLeast); log("\tTXID:" + uuid + " Offset:" + getReaderRecordOffset()); int txQualifier = readInt(); log("\tTXQual:" + txQualifier + " Offset:" + getReaderRecordOffset()); int txThreadId = readInt(); log("\tTXThreadID:" + txThreadId + " Offset:" + getReaderRecordOffset()); TXContext tx = new TXContext(uuid, txQualifier, txThreadId); metric.attachTXContext(tx); log("Attached TX Context " + metric.getTXContext()); } // ========================================================== log("\tRemaining Bytes:" + getRecordRemainingBytes()); return metric; } private IMetric nextOpt() { if(!_next()) throw new RuntimeException("Iterator fetched pass EOF", new Throwable()); IDelegateMetric dmetric = null; readInt(); // skip size byte typeOrdinal = readByte(); MetricType type = MetricType.valueOf(typeOrdinal); byte isToken = readByte(); long token = -1; if(BYTE_ONE==isToken) { token = readLong(); dmetric = ICEMetricCatalog.getInstance().get(token); } else { String fqn = readString(); dmetric = ICEMetricCatalog.getInstance().build(fqn, type); } long time = readLong(); ICEMetric metric = null; if(type.isLong()) { long value = readLong(); metric = ICEMetric.newMetric(time, value, type, dmetric, token); } else { int bbSize = readInt(); ByteBuffer bb = readByteBuffer(bbSize); metric = ICEMetric.newMetric(time, bb, type, dmetric, token); } // ========================================================== // TXCONTEXT // ========================================================== byte tp = readByte(); //log("\nRead Byte: [" + tp + "]\nRemaining Bytes:" + getRecordRemainingBytes() + "\nValue:" + metric.getLongValue()); if(getRecordRemainingBytes()>=TXContext.TXCONTEXT_SIZE) { long txIdLeast = readLong(); long txIdMost = readLong(); int txQualifier = readInt(); int txThreadId = readInt(); TXContext tx = new TXContext(new UUID(txIdMost, txIdLeast), txQualifier, txThreadId); metric.attachTXContext(tx); } // ========================================================== return metric; } /** * {@inheritDoc} * @see java.lang.Iterable#iterator() */ @Override public Iterator<IMetric> iterator() { return this; } } /** * <p>Title: SplitReader</p> * <p>Description: A reader implementation for breaking up a DMC into an array of DMCs, each with a maximum byte size. * This is intended to support transmissions using protocols with limited payload size.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.trace.DirectMetricCollection.SplitReader</code></p> */ public class SplitReader extends Reader implements SplitDMC { /** The maximum size of one split DirectMetricCollection */ private final int maxSize; /** The current DMC */ private DirectMetricCollection dmc; /** The droup count, which is metrics too large to serialize */ private int drops = 0; /** * The droup count, which is metrics too large to serialize * @return the number of dropped metrics */ public int getDrops() { return drops; } /** * Creates a new SplitReader * @param maxSize The maximum size of one split DirectMetricCollection */ public SplitReader(int maxSize) { this.maxSize = maxSize; dmc = DirectMetricCollection.newDirectMetricCollection(maxSize); } /** * {@inheritDoc} * @see java.util.Iterator#next() */ @Override public DirectMetricCollection next() { while(_next()) { int recordSize = getRecordByteSize(); int recordOffset = getRecordOffset(); if(recordSize<maxSize) { if(dmc.size + recordSize < maxSize) { dmc.copyFrom(address + recordOffset, recordSize); } else { DirectMetricCollection retMet = dmc; dmc = DirectMetricCollection.newDirectMetricCollection(maxSize); return retMet.shrinkWrap(); } } else { drops++; } } return dmc.shrinkWrap(); } @Override public Iterator<DirectMetricCollection> iterator() { return this; } } /** * Returns a new SplitReader for splitting this DMC into multiple DMCs each with a byte size smaller than the passed value. * @param maxSize The maximum size of the split DMCs * @return a SplitReader */ public SplitDMC newSplitReader(int maxSize) { if(maxSize>getSize()) { return new OneDMCReader(this); } return new SplitReader(maxSize); } public interface SplitDMC extends Iterator<DirectMetricCollection>, Iterable<DirectMetricCollection> { /** * The droup count, which is metrics too large to serialize * @return the number of dropped metrics */ public int getDrops(); } private class OneDMCReader implements SplitDMC { /** The single DMC */ private final List<DirectMetricCollection> dmc; /** The single DMC iterator*/ private final Iterator<DirectMetricCollection> dmcIter; /** * {@inheritDoc} * @see org.helios.apmrouter.trace.DirectMetricCollection.SplitDMC#getDrops() */ public int getDrops() { return 0; } /** * Creates a new OneDMCReader * @param dmc The DMC to wrap */ OneDMCReader(DirectMetricCollection dmc) { this.dmc = Collections.singletonList(dmc); dmcIter = this.dmc.iterator(); } @Override public boolean hasNext() { return dmcIter.hasNext(); } @Override public DirectMetricCollection next() { return dmcIter.next(); } @Override public void remove() { // No Op } @Override public Iterator<DirectMetricCollection> iterator() { return dmcIter; } } /** * Returns a MetricReader that can iteratively decode all the {@link IMetric}s in this DMC. * @return a MetricReader */ public MetricReader newMetricReader() { return new MetricReader(); } /** * Splits this DMC into multiple DMCs where each one is smaller than the passed maximum size in bytes. * This is provided for UDP based senders where the maximum message size may be smaller than the total in a combined DMC. * @param maxSize The maximum size in bytes for all DMCs returned * @return an array of DMCs */ public DirectMetricCollection[] split(final int maxSize) { List<DirectMetricCollection> splits = new ArrayList<DirectMetricCollection>(); for(DirectMetricCollection d: newSplitReader(maxSize)) { splits.add(d); } return splits.toArray(new DirectMetricCollection[0]); } /** * Reallocated the memory block to be sized exactly for current size. (i.e. so that <b><code>capacity == size</code></b>) * @return this DMC */ protected DirectMetricCollection shrinkWrap() { if(capacity > size) { address = unsafe.reallocateMemory(address, size); capacity = size; } unsafe.putInt(address+SIZE_OFFSET, size); return this; } /** * Returns the current size in bytes of this collection * @return the current size in bytes of this collection */ public int getSize() { int i = unsafe.getInt(address+SIZE_OFFSET); return getByteOrder()==BYTE_ZERO ? i : Integer.reverseBytes(i); } /** * Updates the total size field * @param i the new size */ private void setSize(int i) { unsafe.putInt(address+SIZE_OFFSET, getByteOrder()==BYTE_ZERO ? i : Integer.reverseBytes(i)); } /** * Returns the current memory capacity in bytes of this collection * @return the current memory capacity in bytes of this collection */ public int getCapacity() { return capacity; } /** * {@inheritDoc} * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("DirectMetricCollection [size="); builder.append(getSize()); builder.append(", capacity="); builder.append(getCapacity()); builder.append(", metricCount="); builder.append(getMetricCount()); builder.append("]"); return builder.toString(); } /** * Writes the bytes contained in the passed ByteBuffer into this collection * @param bb the ByteBuffer to write */ protected void writeBytes(ByteBuffer bb) { if(bb.isDirect()) { DirectBuffer db = (DirectBuffer)bb; int byteCount = bb.limit(); UnsafeAdapter.copyMemory(null, db.address(), null, address + size, byteCount); size += byteCount; } else { byte[] bytes = new byte[bb.limit()]; bb.get(bytes); writeBytes(bytes); //size += bytes.length; } } /** * Writes the passed bytes to the memory block * @param bytes the bytes to write */ protected void writeBytes(byte[] bytes) { UnsafeAdapter.copyMemory(bytes, BYTE_ARRAY_OFFSET, null, address + size, bytes.length); size += bytes.length; } /** * Writes the passed long value to the memory block * @param v the long value to write */ protected void writeLong(long v) { unsafe.putLong(address + size, v); size += 8; } /** * Writes the passed int value to the memory block * @param i the int value to write */ protected void writeInt(int i) { unsafe.putInt(address + size, i); size += 4; } /** * Writes the passed byte to the memory block * @param b the byte to write */ protected void writeByte(byte b) { unsafe.putByte(address + size, b); size += 1; } /** * <p>Creates a new DirectMetricCollection. * <p>This ctor writes some header bytes to the memory block. * <p><b>NOTE:</b> If you update the header, make sure you update {@link METRIC_OFFSET} accordingly ! * @param initialCapacity The initial capacity of this DMC */ private DirectMetricCollection(int initialCapacity) { address = unsafe.allocateMemory(initialCapacity); capacity = initialCapacity; size = 0; // the op code (0 for metrics) writeByte(OpCode.SEND_METRIC.op()); // The byte order of this message writeByte(BYTE_ORDER); // the byte size of this DMC set during shrinkWrap() writeInt(0); // the number of metrics in this dmc writeInt(0); // set the initial size setSize(size); } /** * Updates the count of metrics * @return the new metric count */ private int updateCount() { int cnt = unsafe.getInt(address+COUNT_OFFSET)+1; unsafe.putInt(address+COUNT_OFFSET, cnt); return cnt; } /** * Returns the number of metrics contained * @return the number of metrics contained */ public synchronized int getMetricCount() { if(address==0) { throw new RuntimeException("Attempted to access destroyed DMA", new Throwable()); } int i = unsafe.getInt(address + COUNT_OFFSET); return getByteOrder()==BYTE_ZERO ? i : Integer.reverseBytes(i); } /** * Extends the allocated memory space by {@link #EXTEND} bytes */ private void extend() { address = unsafe.reallocateMemory(address, capacity + EXTEND); capacity += EXTEND; } /** * Deallocates the memory reserved for this object */ public void destroy() { if(address!=0) { unsafe.freeMemory(address); address = 0; } } /** * <p>Deallocates the memory if it has not been deallocated already * {@inheritDoc} * @see java.lang.Object#finalize() */ @Override protected void finalize() throws Throwable { destroy(); super.finalize(); } }