/**
* 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.destination.wily;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <p>Title: WilyIntroscopeTracer</p>
* <p>Description: A reflection based wrapper for dispatching metrics to Wily Introscope</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.destination.wily.WilyIntroscopeTracer</code></p>
*/
public class WilyIntroscopeTracer {
/** Singleton instance */
protected static volatile WilyIntroscopeTracer instance = null;
/** Singleton instance ctor lock */
protected static final Object lock = new Object();
/** The data recorder factory class */
protected Class<?> drfClazz = null;
/** A cache of data recorder creating methods indexed by the data recorder simple type name */
protected final Map<String, Method> recorderCreateMethods = new ConcurrentHashMap<String, Method>();
/** A cache of data recorder recording methods indexed by the data recorder simple type name and method name*/
protected final Map<String, Map<String, Method>> recordingMethods = new ConcurrentHashMap<String, Map<String, Method>>();
/** Regex pattern to match data recorder creation methods */
public static final Pattern CREATE_PATTERN = Pattern.compile("create(.*)DataRecorder");
/** A cache of data recorders keyed by metric name */
protected final Map<String, Object> dataRecorders = new ConcurrentHashMap<String, Object>(128);
/** A null argument object array */
public static final Object[] NULL_ARGS = new Object[0];
/** Iscope Resource Delimeter */
public static final String RES_DELIM = "|";
/** Iscope Metric Delimeter */
public static final String MET_DELIM = ":";
/** Iscope Metric Patterns */
public static final String[] MET_PATTERNS = new String[50];
/** The metric type name constant for LongAverages */
public static final String LONGAVERAGE_TYPE = "LongAverage";
/** The metric type name constant for IntAverages */
public static final String INTAVERAGE_TYPE = "IntAverage";
/** The metric type name constant for LongCounters */
public static final String LONGCOUNTER_TYPE = "LongCounter";
/** The metric type name constant for IntCounters */
public static final String INTCOUNTER_TYPE = "IntCounter";
/** The metric type name constant for IntRates */
public static final String INTRATE_TYPE = "IntRate";
/** The metric type name constant for StringEvents */
public static final String STRINGEVENT_TYPE = "StringEvent";
/** The metric type name constant for Timestamps */
public static final String TIMESTAMP_TYPE = "Timestamp";
/** The metric type name constant for PerIntervalCounters */
public static final String PERINTERVALCOUNTER_TYPE = "PerIntervalCounter";
/** Recorder op Name */
public static final String RECORDMULTIPLEINCIDENTS_OP = "recordMultipleIncidents";
/** Recorder op Name */
public static final String SUBTRACT_OP = "subtract";
/** Recorder op Name */
public static final String RECORDDATAPOINT_OP = "recordDataPoint";
/** Recorder op Name */
public static final String ADD_OP = "add";
/** Recorder op Name */
public static final String RECORDTIMESTAMP_OP = "recordTimestamp";
/** Recorder op Name */
public static final String RECORDCURRENTVALUE_OP = "recordCurrentValue";
/** Recorder op Name */
public static final String RECORDINCIDENT_OP = "recordIncident";
/** The system property that defines the wily agent name */
public static final String AGENT_NAME_SYSPROP = "com.wily.introscope.agent.agentName";
/** The system property that defines the wily agent configuration properties file name */
public static final String AGENT_CONFIG_SYSPROP = "com.wily.introscope.agentProfile";
static {
MET_PATTERNS[1] = "%s";
MET_PATTERNS[2] = "%s" + MET_DELIM + "%s";
for(int i = 3; i < 50; i++) {
StringBuilder b = new StringBuilder(MET_PATTERNS[2]);
int rez = i-2;
for(int x = 0; x < rez; x++) {
b.insert(0, "%s" + RES_DELIM);
}
MET_PATTERNS[i] = b.toString();
}
}
/**
* Acquires the IScopeOneOffTracer singleton instance
* @return the IScopeOneOffTracer singleton instance
*/
public static WilyIntroscopeTracer getInstance() {
if(instance==null) {
synchronized(lock) {
if(instance==null) {
instance = new WilyIntroscopeTracer();
}
}
}
return instance;
}
/**
* Creates a new WilyIntroscopeTracer
*/
private WilyIntroscopeTracer() {
try {
Class<?> drfClazz = Class.forName("com.wily.introscope.agent.api.DataRecorderFactory", true, Thread.currentThread().getContextClassLoader());
log("DataRecorderFactory Class:" + drfClazz.getName());
for(Method m: drfClazz.getDeclaredMethods()) {
String methodName = m.getName();
Matcher matcher = CREATE_PATTERN.matcher(methodName);
if(matcher.matches()) {
String type = matcher.group(1);
if(type!=null && !type.isEmpty()) {
m.setAccessible(true);
recorderCreateMethods.put(type, m);
Class<?> recorderClass = m.getReturnType();
Map<String, Method> rMethods = new HashMap<String, Method>();
recordingMethods.put(type, rMethods);
for(Method rm: recorderClass.getDeclaredMethods()) {
rMethods.put(rm.getName(), rm);
String gs = rm.toGenericString();
if(gs.contains(" throws ")) {
gs = gs.split(" throws ")[0];
}
StringBuilder b = new StringBuilder("\n\tpublic void ");
String[] frags = gs.split("\\.");
b.append(frags[frags.length-1]);
b.append(" {\n\t\t\n\t}");
}
}
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to initialize DataRecorderFactory", e);
}
}
/**
* @param args
*/
public static void main(String[] args) {
log("IScope Test");
try {
Blocker blocker = new Blocker();
WilyIntroscopeTracer iscope = WilyIntroscopeTracer.getInstance();
CountDownLatch latch = new CountDownLatch(1);
for(int i = 0; i < 30; i++) {
log("Loop #" + i);
ThreadInfo ti = ManagementFactory.getThreadMXBean().getThreadInfo(Thread.currentThread().getId());
if(i%2==0) {
try { Thread.sleep(12000); } catch (Exception e) {}
} else {
blocker.enter(12000);
}
ThreadInfo ti2 = ManagementFactory.getThreadMXBean().getThreadInfo(Thread.currentThread().getId());
iscope.recordCurrentValue(ti2.getBlockedCount() - ti.getBlockedCount(), "JVM", "Thread Contention", "Blocked Count");
iscope.recordCurrentValue(ti2.getBlockedTime() - ti.getBlockedTime(), "JVM", "Thread Contention", "Blocked Time");
iscope.recordCurrentValue(ti2.getWaitedCount() - ti.getWaitedCount(), "JVM", "Thread Contention", "Waited Count");
iscope.recordCurrentValue(ti2.getWaitedTime() - ti.getWaitedTime(), "JVM", "Thread Contention", "Waited Time");
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
public static class Blocker {
public void enter(final long blockTime) {
final CountDownLatch latch = new CountDownLatch(1);
new Thread() {
public void run() {
waitForMe(latch, blockTime);
}
}.start();
try { latch.await(); } catch (Exception e) {}
waitForMe(null, 1);
}
public synchronized void waitForMe(CountDownLatch latch, long blockTime) {
if(latch!=null) latch.countDown();
try { Thread.currentThread().join(blockTime); } catch (Exception e) {}
}
}
public static void log(Object msg) {
System.out.println(msg);
}
/**
* Records a single data point and rolls it into the current average
* @param value The value to record
* @param args The metric name fragments
*/
public void recordDataPoint(long value, Object...args) {
try {
invokeRecording(LONGAVERAGE_TYPE, RECORDDATAPOINT_OP, value, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Records a single data point and rolls it into the current average
* @param value The value to record
* @param metricName The metric name
*/
public void recordDataPoint(long value, String metricName) {
try {
invokeRecording(LONGAVERAGE_TYPE, RECORDDATAPOINT_OP, value, metricName);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Records a single data point and rolls it into the current average
* @param value The value to record
* @param args The metric name fragments
*/
public void recordDataPoint(int value, Object...args) {
try {
invokeRecording(INTAVERAGE_TYPE, RECORDDATAPOINT_OP, value, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Adds the given delta from the current value.
* @param value The value to add from the counter
* @param args The metric name fragments
*/
public void add(long value, Object...args) {
try {
invokeRecording(LONGCOUNTER_TYPE, ADD_OP, value, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Records the current counter value
* @param value The value to record
* @param args The metric name fragments
*/
public void recordCurrentValue(long value, Object...args) {
try {
invokeRecording(LONGCOUNTER_TYPE, RECORDCURRENTVALUE_OP, value, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Records the current counter value
* @param value The value to record
* @param metricName The metric name
*/
public void recordCurrentValue(long value, String metricName) {
try {
invokeRecording(LONGCOUNTER_TYPE, RECORDCURRENTVALUE_OP, value, metricName);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Subtracts the given delta from the current value.
* @param value The value to subtract from the counter
* @param args The metric name fragments
*/
public void subtract(long value, Object...args) {
try {
invokeRecording(LONGCOUNTER_TYPE, SUBTRACT_OP, value, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Adds the given delta from the current value.
* @param value The value to add from the counter
* @param args The metric name fragments
*/
public void add(int value, Object...args) {
try {
invokeRecording(INTCOUNTER_TYPE, ADD_OP, value, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Records the current counter value
* @param value The value to record
* @param args The metric name fragments
*/
public void recordCurrentValue(int value, Object...args) {
try {
invokeRecording(INTCOUNTER_TYPE, RECORDCURRENTVALUE_OP, value, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Subtracts the given delta from the current value.
* @param value The value to subtract from the counter
* @param args The metric name fragments
*/
public void subtract(int value, Object...args) {
try {
invokeRecording(INTCOUNTER_TYPE, SUBTRACT_OP, value, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Records a single string type point
* @param value The value to record
* @param args The metric name fragments
*/
public void recordDataPoint(CharSequence value, Object...args) {
try {
invokeRecording(STRINGEVENT_TYPE, RECORDDATAPOINT_OP, value, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Records a single string type point
* @param value The value to record
* @param metricName THe formatted metric name
*/
public void recordDataPoint(CharSequence value, String metricName) {
try {
invokeRecording(STRINGEVENT_TYPE, RECORDDATAPOINT_OP, value, metricName);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Records a timestamp
* @param timestamp The timestamp in the form of a UTC long
* @param args The metric name fragments
*/
public void recordTimestamp(long timestamp, Object...args) {
try {
invokeRecording(TIMESTAMP_TYPE, RECORDTIMESTAMP_OP, timestamp, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Records the current timestamp
* @param args The metric name fragments
*/
public void recordTimestamp(Object...args) {
try {
invokeRecording(TIMESTAMP_TYPE, RECORDTIMESTAMP_OP, System.currentTimeMillis(), args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Records a single interval incident
* @param args The metric name fragments
*/
public void recordIntervalIncident(Object...args) {
try {
invokeRecording(PERINTERVALCOUNTER_TYPE, RECORDINCIDENT_OP, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Record multiple interval incidents. The number of incidents must be positive.
* @param incidentCount The number of incidents
* @param args The metric name fragments
*/
public void recordMultipleIntervalIncidents(int incidentCount, Object...args) {
try {
invokeRecording(PERINTERVALCOUNTER_TYPE, RECORDMULTIPLEINCIDENTS_OP, incidentCount, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Records a single rate incident
* @param args The metric name fragments
*/
public void recordRateIncident(Object...args) {
try {
invokeRecording(INTRATE_TYPE, RECORDINCIDENT_OP, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Record multiple rate incidents. The number of incidents must be positive.
* @param value The number of incidents
* @param args The metric name fragments
*/
public void recordMultipleRateIncidents(int value, Object...args) {
try {
invokeRecording(INTRATE_TYPE, RECORDMULTIPLEINCIDENTS_OP, value, args);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
/**
* Invokes a recorder call
* @param type The metric type
* @param op The recorder op
* @param args The metric name fragments
*/
public void invokeRecording(String type, String op, Object...args) {
invokeRecording(type, op, null, args);
}
/**
* Invokes a recorder call
* @param type The metric type
* @param op The recorder op
* @param args The formatted metric name
*/
public void invokeRecording(String type, String op, String metricName) {
invokeRecording(type, op, null, metricName);
}
/**
* Invokes a recorder call
* @param type The metric type
* @param op The recorder op
* @param value The value to pass to the recording. Ignored if null
* @param args The metric name fragments
*/
public void invokeRecording(String type, String op, Object value, Object...args) {
Object recorder = getRecorder(type, args);
try {
recordingMethods.get(type).get(op).invoke(recorder, value==null ? NULL_ARGS : value );
} catch (Exception e) {
throw new RuntimeException("Failed to invoke [" + type + "/" + op + "]", e);
}
}
/**
* Invokes a recorder call
* @param type The metric type
* @param op The recorder op
* @param value The value to pass to the recording. Ignored if null
* @param metricName The formatted metric name
*/
public void invokeRecording(String type, String op, Object value, String metricName) {
Object recorder = getRecorder(type, metricName);
try {
recordingMethods.get(type).get(op).invoke(recorder, value==null ? NULL_ARGS : value );
} catch (Exception e) {
throw new RuntimeException("Failed to invoke [" + type + "/" + op + "]", e);
}
}
/**
* Returns a data recorder for the passed type and metric name fragments
* @param type The data recorder type
* @param args the metric name fragments
* @return a data recorder
*/
public Object getRecorder(String type, Object...args) {
return getRecorder(type, buildMetricName(args));
}
/**
* Returns a data recorder for the passed type and metric name fragments
* @param type The data recorder type
* @param metricName The formatted metric name
* @return a data recorder
*/
public Object getRecorder(String type, String metricName) {
Object recorder = dataRecorders.get(metricName);
if(recorder==null) {
synchronized(dataRecorders) {
recorder = dataRecorders.get(metricName);
if(recorder==null) {
Method m = recorderCreateMethods.get(type);
if(m==null) throw new IllegalArgumentException("The passed type [" + type + "] is invalid", new Throwable());
try {
recorder = m.invoke(null, metricName);
} catch (Exception e) {
throw new RuntimeException("Failed to create recorder of type [" + type + "]", e);
}
}
}
}
return recorder;
}
/**
* Builds an iscope metric name
* @param args The components of each resource and then metric name
* @return an Iscope metric name
*/
public String buildMetricName(Object...args) {
if(args==null || args.length<1) throw new IllegalArgumentException("The passed char sequence array was null or zero length", new Throwable());
return String.format(MET_PATTERNS[args.length], args);
}
}