/**
* ICE Futures, US
*/
package org.helios.apmrouter.metric;
import org.helios.apmrouter.util.BitMaskedEnum;
import org.helios.apmrouter.util.BitMaskedEnum.IntBitMaskOperations;
import org.helios.apmrouter.util.IO;
import org.snmp4j.PDU;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
import java.util.zip.GZIPOutputStream;
import static org.helios.apmrouter.util.Methods.nvl;
/**
* <p>Title: MetricType</p>
* <p>Description: Enumerates the metricId type interpretations and provides accessor methods for the individual types</p>
* <p>Company: ICE Futures US</p>
* @author Whitehead (nicholas.whitehead@theice.com)
* @version $LastChangedRevision$
* <p><code>org.helios.apmrouter.metric.MetricType</code></p>
*/
@SuppressWarnings("rawtypes")
public enum MetricType implements IMetricDataAccessor, BitMaskedEnum, IntBitMaskOperations<MetricType> {
/** Standard numeric metricId type, conflation is additive */
LONG_COUNTER(new LongMDA()),
/** Standard numeric metricId type, conflation is averaging */
LONG_GAUGE(new LongMDA()),
/** Locally maintained delta numeric metricId type, conflation is additive */
DELTA_COUNTER(new DeltaMDA()),
/** Locally maintained delta numeric metricId type, conflation is averaging */
DELTA_GAUGE(new DeltaMDA()),
/** Increments a server managed global counter */
INCREMENTOR(new LongMDA()),
/** Increments a server managed global counter which is flushed and zeroed out at the end of the interval */
INTERVAL_INCREMENTOR(new LongMDA()),
/** A throwable handler metricId type */
ERROR(new ErrorMDA()),
/** A char sequence type message metricId type */
STRING(new StringMDA()),
/** An SNMP PDU */
PDU(new PduMDA()),
/** A catch all metricId type in the form of a byte array for everything else */
BLOB(new BlobMDA());
/** Map of MetricTypes keyed by the ordinal */
public static final Map<Integer, MetricType> ORD2ENUM;
static {
int m = 1;
Map<Integer, MetricType> tmp = new HashMap<Integer, MetricType>(MetricType.values().length);
for(MetricType mt: MetricType.values()) {
tmp.put(mt.ordinal(), mt);
mt.mask = m;
m = m*2;
}
ORD2ENUM = Collections.unmodifiableMap(tmp);
}
public static void main(String[] args) {
log("MetricType");
for(MetricType mt: MetricType.values()) {
log(mt.name() + ":" + mt.mask);
}
log("========================");
Set<Integer> longMasks = new TreeSet<Integer>();
for(MetricType mt: MetricType.values()) {
longMasks.add(mt.mask);
}
log("All:" + longMasks);
longMasks.clear();
for(MetricType mt: MetricType.getLongMetricTypes()) {
longMasks.add(mt.mask);
}
log("Longs:" + longMasks);
}
public static void log(Object msg) {
System.out.println(msg);
}
private MetricType(IMetricDataAccessor mda) {
this.mda = mda;
}
/** The MDA for this metric type */
private final IMetricDataAccessor mda;
private int mask;
/**
* Returns this type's metric data accessor
* @return this type's metric data accessor
*/
public IMetricDataAccessor getDataAccessor() {
return mda;
}
/** Indicates if direct byte buffers should be used */
private static boolean direct = false;
/** Indicates if byte buffers should compressed for non-numeric types */
private static boolean compress = false;
/**
* Determines if this metricId type is long based
* @return true if this metricId type is long based, false otherwise
*/
public boolean isLong() {
return ordinal() <= INTERVAL_INCREMENTOR.ordinal();
}
/**
* Determines if this metricId type is an incrementor
* @return true if this metricId type is an incrementor, false otherwise
*/
public boolean isIncrementor() {
return this == INTERVAL_INCREMENTOR || this == INCREMENTOR;
}
/**
* Determines if this metricId type is a gauge
* @return true if this metricId type is a gauge, false otherwise
*/
public boolean isGauge() {
return (this==LONG_GAUGE || this==DELTA_GAUGE);
}
/**
* Determines if this metricId type is a counter
* @return true if this metricId type is a counter, false otherwise
*/
public boolean isCounter() {
return (this==LONG_COUNTER|| this==DELTA_COUNTER);
}
/**
* Determines if this metricId type is a delta
* @return true if this metricId type is a delta, false otherwise
*/
public boolean isDelta() {
return (this==DELTA_COUNTER|| this==DELTA_GAUGE);
}
/**
* Returns an array of long metric types
* @return an array of long metric types
*/
public static MetricType[] getLongMetricTypes() {
Set<MetricType> set = new HashSet<MetricType>();
for(MetricType mt: MetricType.values()) {
if(mt.isLong()) set.add(mt);
}
return set.toArray(new MetricType[set.size()]);
}
/**
* Decodes the passed ordinal to a MetricType.
* Throws a runtime exception if the ordinal is invalud
* @param ordinal The ordinal to decode
* @return the decoded MetricType
*/
public static MetricType valueOf(int ordinal) {
MetricType mt = ORD2ENUM.get(ordinal);
if(mt==null) throw new IllegalArgumentException("The passed ordinal [" + ordinal + "] is not a valid MetricType ordinal", new Throwable());
return mt;
}
/**
* Attempts to decode an arbitrary object into a MetricType
* @param metricCode The arbitrary object to convert
* @return a MetricType if successfully converted.
*/
public static MetricType valueOf(Object metricCode) {
if(metricCode==null) throw new IllegalArgumentException("The passed metricCode was null", new Throwable());
if(metricCode instanceof MetricType) return (MetricType)metricCode;
if(metricCode instanceof Number) return valueOf(((Number)metricCode).intValue());
try {
int type = Integer.parseInt(metricCode.toString().trim());
return valueOf(type);
} catch (NumberFormatException nfe) {/* No Op */}
return valueOfName(metricCode.toString());
}
/**
* Decodes the passed ordinal to a MetricType.
* Throws a runtime exception if the ordinal is invalud
* @param ordinal The ordinal to decode
* @return the decoded MetricType
*/
public static MetricType valueOf(byte ordinal) {
int ord = ordinal;
return valueOf(ord);
}
/**
* Decodes the passed name to a MetricType.
* Throws a runtime exception if the ordinal is invalid
* @param name The metricId type name to decode. Trimmed and uppercased.
* @return the decoded MetricType
*/
public static MetricType valueOfName(CharSequence name) {
String n = nvl(name, "MetricType Name").toString().trim().toUpperCase();
try {
return MetricType.valueOf(n);
} catch (Exception e) {
int id = -1;
try { id = Integer.parseInt(n); } catch (Exception ex) {}
if(id!=-1) {
return MetricType.valueOf(id);
}
throw new IllegalArgumentException("The passed name [" + name + "] is not a valid MetricType name", new Throwable());
}
}
/**
* <p>Title: LongMDA</p>
* <p>Description: The {@link IMetricDataAccessor} for long metricId types</p>
* <p>Company: ICE Futures US</p>
* @author Whitehead (nicholas.whitehead@theice.com)
* @version $LastChangedRevision$
* <p><code>org.helios.apmrouter.metric.MetricType.LongMDA</code></p>
*/
private static class LongMDA implements IMetricDataAccessor<long[]> {
private static final Class<?> REF_CLASS = Long.class;
private static final long[] NULL_LONG = {0};
protected MetricType getType() {
return LONG_COUNTER;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#write(java.lang.Object)
*/
@Override
public ICEMetricValue write(long[] value) {
return new ICEMetricValue(getType(), value[0]);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#writeObject(java.lang.Object)
*/
@Override
public ICEMetricValue writeObject(Object value) {
long actual = 0;
if(value==null) {
actual = 0;
} else if(REF_CLASS.equals(value.getClass())) {
actual = (Long)value;
} else if(value instanceof Number) {
actual = ((Number)value).longValue();
} else {
try { actual = new Double(value.toString().trim()).longValue(); } catch (Exception e) {
actual = 0;
}
}
return new ICEMetricValue(getType(), actual);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#read(org.helios.apmrouter.metric.ICEMetricValue)
*/
@Override
public long[] read(ICEMetricValue metricValue) {
return new long[]{metricValue.getLongValue()};
}
}
/**
* <p>Title: DeltaMDA</p>
* <p>Description: The {@link IMetricDataAccessor} for delta metricId types</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.metric.MetricType.DeltaMDA</code></p>
*/
private static class DeltaMDA extends LongMDA {
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.MetricType.LongMDA#getType()
*/
@Override
protected MetricType getType() {
return DELTA_GAUGE;
}
}
/**
* <p>Title: StringMDA</p>
* <p>Description: The {@link IMetricDataAccessor} for string metricId types</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.metric.MetricType.StringMDA</code></p>
*/
private static class StringMDA implements IMetricDataAccessor<CharSequence> {
private static final Class<?> REF_CLASS = CharSequence.class;
private static final ByteBuffer NULL_BYTES = ByteBuffer.wrap(new byte[]{});
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#write(java.lang.Object)
*/
@Override
public ICEMetricValue write(CharSequence value) {
return new ICEMetricValue(STRING, value==null ? NULL_BYTES : allocate(value.toString().getBytes()));
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#writeObject(java.lang.Object)
*/
@Override
public ICEMetricValue writeObject(Object value) {
return new ICEMetricValue(STRING, value==null ? NULL_BYTES : allocate(value.toString().getBytes()));
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#read(org.helios.apmrouter.metric.ICEMetricValue)
*/
@Override
public CharSequence read(ICEMetricValue metricValue) {
ByteBuffer buff = metricValue.getRawValue();
byte[] bytes = new byte[buff.limit()];
buff.position(0);
buff.get(bytes);
String s = new String(bytes);
return s;
}
}
/**
* <p>Title: ErrorMDA</p>
* <p>Description: The {@link IMetricDataAccessor} for error metricId types</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.metric.MetricType.ErrorMDA</code></p>
*/
private static class ErrorMDA implements IMetricDataAccessor<Throwable> {
private static final ByteBuffer NULL_BYTES = ByteBuffer.wrap(new byte[]{});
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#write(java.lang.Object)
*/
@Override
public ICEMetricValue write(Throwable value) {
Throwable et = new Throwable("Throwable Stub. Type [" + value.getClass().getName() + "] Message [" + value.getMessage() + "]");
et.setStackTrace(value.getStackTrace());
return new ICEMetricValue(ERROR, value==null ? NULL_BYTES : IO.writeToByteBuffer(et, direct, false));
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#writeObject(java.lang.Object)
*/
@Override
public ICEMetricValue writeObject(Object value) {
return write((Throwable)value);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#read(org.helios.apmrouter.metric.ICEMetricValue)
*/
@Override
public Throwable read(ICEMetricValue metricValue) {
return getThrowable(metricValue.getRawValue());
}
/**
* Reads a throwable from the byte buffer, returning contrived place-holders if the bytebuffer's payload is zero or deserialization fails.
* @param buff The byte buffer to read from
* @return A throwable
*/
protected Throwable getThrowable(final ByteBuffer buff) {
buff.flip();
if(buff.capacity()<1) return new Throwable("Null Throwable");
return (Throwable)IO.readFromByteBuffer(buff);
}
// private final AtomicLong exec = new AtomicLong(0L);
// private final AtomicLong alt = new AtomicLong(0L);
/**
* Converts the passed throwable to a byte array. If the standard java serialization fails,
* creates a contrived exception using the stack trace as the error message.
* @param t the throwable to serialize
* @return a byte array
*/
protected byte[] getBytes(Throwable t) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
ObjectOutputStream oos = null;
long start = 0L;
// boolean trace = exec.incrementAndGet()%1000000==0;
// if(trace) start = System.nanoTime();
try {
oos = new ObjectOutputStream(baos);
oos.writeObject(t);
oos.flush();
//baos.flush();
} catch (Exception e) {
// alt.incrementAndGet();
Throwable et = new Throwable("Non-Serializable Throwable Stub. Type [" + t.getClass().getName() + "] Message [" + t.getMessage() + "]");
et.setStackTrace(t.getStackTrace());
return getBytes(t);
} finally {
try { oos.close(); } catch (Exception ex) {}
try { baos.close(); } catch (Exception ex) {}
}
byte[] bytes = baos.toByteArray();
// if(trace) {
// long elapsed = System.nanoTime()-start;
// long execCount = exec.get();
// long altCount = alt.get();
// if(execCount>BaseTracingOperations.TOTAL_EXEC_COUNT) {
// System.err.println("Throwable Ser Elapsed:" + elapsed + " ns. " + TimeUnit.MILLISECONDS.convert(elapsed, TimeUnit.NANOSECONDS) + " ms. Size:" + bytes.length + " Invoke Count:" + execCount + " Overage:" + (execCount-BaseTracingOperations.TOTAL_EXEC_COUNT) + " Alt:" + altCount);
// } else {
// System.out.println("Throwable Ser Elapsed:" + elapsed + " ns. " + TimeUnit.MILLISECONDS.convert(elapsed, TimeUnit.NANOSECONDS) + " ms. Size:" + bytes.length + " Invoke Count:" + execCount + " Alt:" + altCount);
// }
// //new Throwable().printStackTrace(System.err);
// }
return bytes;
}
}
/**
* <p>Title: BlobMDA</p>
* <p>Description: The {@link IMetricDataAccessor} for arbitrary serializable objects</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.metric.MetricType.BlobMDA</code></p>
*/
private static class BlobMDA implements IMetricDataAccessor<Serializable> {
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#write(java.lang.Object)
*/
@SuppressWarnings("synthetic-access")
@Override
public ICEMetricValue write(Serializable value) {
return new ICEMetricValue(BLOB, IO.writeToByteBuffer(value, direct, compress));
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#writeObject(java.lang.Object)
*/
@Override
public ICEMetricValue writeObject(Object value) {
return new ICEMetricValue(BLOB, IO.writeToByteBuffer(value, direct, compress));
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#read(org.helios.apmrouter.metric.ICEMetricValue)
*/
@Override
public Serializable read(ICEMetricValue metricValue) {
return (Serializable)IO.readFromByteBuffer(metricValue.getRawValue());
}
}
/**
* <p>Title: PduMDA</p>
* <p>Description: The {@link IMetricDataAccessor} for SNMP PDU metric types</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.metric.MetricType.PduMDA</code></p>
*/
private static class PduMDA implements IMetricDataAccessor<PDU> {
/** TODO: Replace IO.writeToBuffer with BEROutputStream */
@Override
public ICEMetricValue write(org.snmp4j.PDU value) {
return new ICEMetricValue(PDU, IO.writePDUToByteBuffer(value, direct));
}
@Override
public ICEMetricValue writeObject(Object value) {
return new ICEMetricValue(PDU, IO.writePDUToByteBuffer((PDU)value, direct));
}
@Override
public org.snmp4j.PDU read(ICEMetricValue metricValue) {
return IO.readPDUFromByteBuffer(metricValue.getRawValue());
}
}
/**
* Compresses a value payload byte array in a metricId in accordance with the {@link MetricType#isCompress()} option
* @param payload The byte array to compress
* @return The compressed payload
*/
public static byte[] compress(byte[] payload) {
if(!compress) return payload;
if(payload==null) return new byte[]{};
ByteArrayOutputStream baos = null;
GZIPOutputStream gzos = null;
try {
baos = new ByteArrayOutputStream(payload.length);
gzos = new GZIPOutputStream(baos);
gzos.write(payload);
gzos.close();
return baos.toByteArray();
} catch (Exception e) {
return payload;
}
}
/**
* Allocates byte buffers in accordance with the {@link MetricType#isDirect()} option
* @param size The allocation size
* @return the allocated byte buffer
*/
public static ByteBuffer allocate(int size) {
return direct ? ByteBuffer.allocateDirect(size) : ByteBuffer.allocate(size);
}
/**
* Allocates byte buffers in accordance with the {@link MetricType#isDirect()} option
* @param bytes The bytes to allocate a ByteBuffer for
* @return the allocated byte buffer
*/
public static ByteBuffer allocate(byte[] bytes) {
return direct ? ByteBuffer.allocateDirect(bytes.length).put(bytes) : ByteBuffer.wrap(bytes);
}
/**
* Returns the passed long in the form of a little endian formatted byte array
* @param payloadLength The long value to encode
* @return an byte array
*/
public static byte[] encodeLittleEndianLongBytes(long payloadLength) {
return ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(payloadLength).array();
}
/**
* Decodes the little endian encoded bytes to a long
* @param bytes The bytes to decode
* @return the decoded long value
*/
public static long decodeLittleEndianLongBytes(byte[] bytes) {
return ((ByteBuffer) ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(bytes).flip()).getLong();
}
/**
* Indicates if metricId value byte buffers are allocated direct or on heap
* @return true if metricId value byte buffers are allocated direct, false if they allocated on heap
*/
public static boolean isDirect() {
return direct;
}
/**
* Sets if metricId value byte buffers are allocated direct or on heap
* @param direct true if metricId value byte buffers are allocated direct, false if they allocated on heap
*/
public static void setDirect(boolean direct) {
MetricType.direct = direct;
}
/**
* Indicates if non-numeric metricId values are being compressed
* @return true if values are compressed, false otherwise
*/
public static boolean isCompress() {
return compress;
}
/**
* Sets the compression option for non-numeric metricId values
* @param compress true to compress, false otherwise
*/
public static void setCompress(boolean compress) {
MetricType.compress = compress;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#write(java.lang.Object)
*/
@Override
public ICEMetricValue write(Object value) {
return mda.write(value);
}
public ICEMetricValue write(long value) {
if(isLong()) {
return ((LongMDA)mda).write(new long[]{value});
}
return mda.write(value);
}
public ICEMetricValue write(Number value) {
if(isLong() && value!=null) {
return ((LongMDA)mda).write(new long[]{value.longValue()});
}
return mda.write(value);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#writeObject(java.lang.Object)
*/
@Override
public ICEMetricValue writeObject(Object value) {
return mda.writeObject(value);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.IMetricDataAccessor#read(org.helios.apmrouter.metric.ICEMetricValue)
*/
@Override
public Object read(ICEMetricValue metricValue) {
if(isLong()) {
return ((LongMDA)mda).read(metricValue)[0];
}
return mda.read(metricValue);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.util.BitMaskedEnum.IntBitMaskOperations#forOrdinal(int)
*/
@Override
public MetricType forOrdinal(int ordinal) {
MetricType t = ORD2ENUM.get(ordinal);
if(t==null) throw new IllegalArgumentException("Invalid MetricType ordinal [" + ordinal + "]", new Throwable());
return t;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.util.BitMaskedEnum.IntBitMaskOperations#forCode(java.lang.Number)
*/
@Override
public MetricType forCode(Number code) {
return forOrdinal(code.intValue());
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.util.BitMaskedEnum.IntBitMaskOperations#isEnabled(int)
*/
@Override
public boolean isEnabled(int mask) {
return mask == (this.mask | mask);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.util.BitMaskedEnum.IntBitMaskOperations#getMask()
*/
@Override
public int getMask() {
return mask;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.util.BitMaskedEnum.IntBitMaskOperations#enable(int)
*/
@Override
public int enable(int mask) {
return (mask | this.mask);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.util.BitMaskedEnum.IntBitMaskOperations#disable(int)
*/
@Override
public int disable(int mask) {
return (mask & ~this.mask);
}
/**
* Returns the bit mask representing each of the metric type ordinals passed
* @param ordinals the oridnals to mask
* @return the mask
*/
public static int getMaskFor(int[] ordinals) {
if(ordinals==null || ordinals.length==0) return 0;
int m = 0;
for(int o: ordinals) {
m = (m | ORD2ENUM.get(o).mask);
}
return m;
}
}
/*
@Override
public ByteBuffer write(long...value) {
long actual = (value==null || value.length<1) ? 0 : value[0];
ByteBuffer bb = allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(actual);
bb.flip();
return bb;
}
@Override
public ByteBuffer writeObject(Object value) {
long actual = 0;
if(value==null) {
actual = 0;
} else if(REF_CLASS.equals(value.getClass())) {
actual = (Long)value;
} else if(value instanceof Number) {
actual = ((Number)value).longValue();
} else {
try { actual = new Double(value.toString().trim()).longValue(); } catch (Exception e) {
actual = 0;
}
}
ByteBuffer bb = allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(actual);
bb.flip();
return bb;
}
@Override
public long[] read(ByteBuffer metricBuffer) {
if(metricBuffer==null) return NULL_LONG;
metricBuffer.rewind();
return new long[]{metricBuffer.order(ByteOrder.LITTLE_ENDIAN).getLong()};
}
*/