/** * 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.util.SystemClock; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.UUID; /** * <p>Title: TXContext</p> * <p>Description: Tracks a distributed and incrementing context across multiple tracing points and JVMs * so that trace points can be correlated on the server side and their sequence correctly interpreted.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.trace.TXContext</code></p> */ public class TXContext { /** The TXContext ID for this TXContext */ private final UUID txId; /** The TXContext qualifier for this instance */ private final int txQualifier; /** The TXContext thread qualifier for this instance */ private final int txThreadId; /** The size of a TXContext in bytes */ public static final int TXCONTEXT_SIZE = 8 + 8 + 4 + 4; /** Zero byte literal */ public static final byte BYTE_ZERO = 0; /** One byte literal */ public static final byte BYTE_ONE = 1; /** The default NULL UUID for a null context */ private static final UUID NULLUUID = new UUID(-1L, -1L); /** The default NULL context generated by the thread local */ private static final TXContext NULLContext = new TXContext(NULLUUID, -1, -1); /** The TXContext for the current thread */ private static final InheritableThreadLocal<TXContext> currentContext = new InheritableThreadLocal<TXContext>(){ @Override protected TXContext initialValue() { return NULLContext; } @Override protected TXContext childValue(TXContext parentValue) { return parentValue==NULLContext ? NULLContext : parentValue.rollThread(); } }; /** * Determines if there is a real TXContext associated with the current thread * @return true if there is a real TXContext associated with the current thread, false otherwise */ public static boolean hasContext() { TXContext txc = currentContext.get(); if(txc==NULLContext) { currentContext.remove(); return false; } return true; } /** * Increments or initiates the current context * @return the new TXContext */ public static TXContext rollContext() { TXContext txc = currentContext.get(); if(txc==NULLContext) return txc.startContext(); return txc.rollQualifier(); } /** * Clears the context for the current thread. */ public static void clearContext() { currentContext.remove(); } /** * Retrieves the context for the current thread if one exists. * @return the context for the current thread or null if one does not exist */ public static TXContext getContext() { TXContext txc = currentContext.get(); if(txc==NULLContext) { currentContext.remove(); return null; } return txc; } //public static TXContext /** * Creates a new TXContext * @param txId The TXContext ID for this TXContext * @param txQualifier The TXContext qualifier for this instance * @param txThreadId The TXContext thread qualifier for this instance */ TXContext(UUID txId, int txQualifier, int txThreadId) { this.txId = txId; this.txQualifier = txQualifier; this.txThreadId = txThreadId; } /** * Returns the TXContext ID for this TXContext * @return the TXContext ID for this TXContext */ public UUID getTxId() { return txId; } /** * Returns the TXContext qualifier for this instance * @return the TXContext qualifier for this instance */ public int getTxQualifier() { return txQualifier; } /** * Returns the TXContext thread qualifier for this instance * @return the TXContext thread qualifier for this instance */ public int getTxThreadId() { return txThreadId; } /** * Starts a new originating context * @return an originating context */ private TXContext startContext() { TXContext txc = new TXContext(UUID.randomUUID(), 0, 0); currentContext.set(txc); return txc; } /** * Generates the next sequential TXContext within one thread * @return the next sequential TXContext */ private TXContext rollQualifier() { TXContext txc = new TXContext(txId, txQualifier+1, txThreadId); currentContext.set(txc); return txc; } /** * Generates the next sequential TXContext within one thread * @return the next sequential TXContext */ private TXContext rollThread() { TXContext txc = new TXContext(txId, txQualifier+1, txThreadId+1); currentContext.set(txc); return txc; } /** * {@inheritDoc} * @see java.lang.Object#toString() */ @Override public String toString() { return String.format("%s-%s-%s", txId.toString(), txQualifier, txThreadId); } /** * Encodes this TXContext to a byte array in the native byte order * @return a byte array containing the encoded TXContext */ public byte[] encode() { return encode(this); } /** * Encodes this TXContext to a byte array in the specified byte order * @param byteOrder The byte order to encode in * @return a byte array containing the encoded TXContext */ public byte[] encode(ByteOrder byteOrder) { return encode(this, byteOrder); } /** * Returns the passed TXContext in the form of a byte array in the specified byte order * @param context The context to encode * @param byteOrder The byte order to encode in * @return The encoded TXContext */ public static byte[] encode(TXContext context, ByteOrder byteOrder) { return ByteBuffer.allocate(TXCONTEXT_SIZE).order(byteOrder) .put(byteOrder==ByteOrder.LITTLE_ENDIAN ? BYTE_ZERO : BYTE_ONE) .putLong(context.txId.getLeastSignificantBits()) .putLong(context.txId.getMostSignificantBits()) .putInt(context.txQualifier) .putInt(context.txThreadId) .array(); } /** * Returns the passed TXContext in the form of a byte array in the native byte order * @param context The context to encode * @return The encoded TXContext */ public static byte[] encode(TXContext context) { return encode(context, ByteOrder.nativeOrder()); } /** * Decodes the passed bytes to a TXContext * @param bytes The bytes to decode * @return the decoded TXContext */ public static TXContext decode(byte[] bytes) { return decode(bytes, false); } /** * Decodes the passed bytes to a TXContext * @param bytes The bytes to decode * @param reverse true to reverse the byte code from the one encoded * @return the decoded TXContext */ public static TXContext decode(byte[] bytes, boolean reverse) { if(bytes==null || bytes.length!=TXCONTEXT_SIZE) throw new IllegalArgumentException("Byte size not valid for an encoded TXContext [" + (bytes==null ? "<null>" : bytes.length) + "]"); ByteBuffer bb = null; if(reverse) { bb= ByteBuffer.wrap(bytes).order(bytes[0]==BYTE_ZERO ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); } else { bb = ByteBuffer.wrap(bytes).order(bytes[0]==BYTE_ZERO ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); } bb.get(); return new TXContext(new UUID(bb.getLong(), bb.getLong()), bb.getInt(), bb.getInt()); } public static void main(String[] args) { log("TXContext Test"); log("Has:" + hasContext()); log("Actual:" + rollContext()); log("Has:" + hasContext()); for(int i = 0; i < 10; i++) { rollContext(); } log("Current:" + getContext()); clearContext(); log("Has:" + hasContext()); log("Actual:" + rollContext()); log("========================================"); byte[] encoded = getContext().encode(); log("Decoded:" + decode(encoded)); new Thread() { public void run() { log("Has:" + hasContext()); log("Current:" + getContext()); log("Rolled:" + rollContext()); } }.start(); clearContext(); SystemClock.sleep(1000); new Thread() { public void run() { log("Has:" + hasContext()); log("Current:" + getContext()); log("Rolled:" + rollContext()); new Thread() { public void run() { log("Has:" + hasContext()); log("Current:" + getContext()); log("Rolled:" + rollContext()); } }.start(); } }.start(); SystemClock.sleep(5000); } public static void log(Object msg) { System.out.println("[" + Thread.currentThread().getName() + "]" + msg); } /** * {@inheritDoc} * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((txId == null) ? 0 : txId.hashCode()); result = prime * result + txQualifier; result = prime * result + txThreadId; return result; } /** * {@inheritDoc} * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } TXContext other = (TXContext) obj; if (txId == null) { if (other.txId != null) { return false; } } else if (!txId.equals(other.txId)) { return false; } if (txQualifier != other.txQualifier) { return false; } if (txThreadId != other.txThreadId) { return false; } return true; } }