
org.jboss.byteman.sample.helper.JMXHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of byteman-sample Show documentation
Show all versions of byteman-sample Show documentation
The Byteman sample jar contains some example helper classes and auxiliary classes used by the]
sample rule scripts contained in the distribution
/*
* JBoss, Home of Professional Open Source
* Copyright 2010 Red Hat and individual contributors
* by the @authors tag. See the copyright.txt 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.
*
* @authors Andrew Dinn
*/
package org.jboss.byteman.sample.helper;
import org.jboss.byteman.rule.Rule;
import org.jboss.byteman.rule.helper.Helper;
import javax.management.*;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
/**
* A variant of PeriodicHelper which publishes its stats via JMX
*/
public class JMXHelper extends Helper implements DynamicMBean
{
/************************************************************************/
/* public API */
/************************************************************************/
/**
* This is a system property whose value will determine which MBean Server the
* MBeans should be registered in. If the system property is not defined, the default
* will be the platform MBeanServer itself. If this system property is set, it will
* be assumed to be a default domain name of an existing MBeanServer. The existing
* MBeanServer with that default domain name will be used to house the MBeans. If there
* is no existing MBeanServer with the given default domain name, one will be created.
* Note that if this sysprop has the special value "*platform*", then the platform
* MBeanServer will be used (i.e. it will be as if this sysprop was not set).
*/
public final static String SYSPROP_MBEAN_SERVER = "org.jboss.byteman.jmx.mbeanserver";
/**
* the default period which the helper will wait for between calls to periodicUpdate in milliseconds. this
* can be redefined either by overriding defaultPeriod
*/
public final static long DEFAULT_PERIOD = 10000L;
/**
* default number of samples we are willing to store in order to maintain a running count of all previous
*/
public final static int DEFAULT_SAMPLE_SET_SIZE = 5;
/**
* default value for the rmi server host address used by the JMX onnector server
*
* used only if an rmi server is required for the JMXConnector
*/
public final static String DEFAULT_RMI_HOST = "localhost";
/**
* default value for the rmi server port used by the JMX connector server
*
* used only if an rmi server is required for the JMXConnector
*/
public final static int DEFAULT_RMI_PORT = 1099;
/**
* JMX Url pattern for use when creating the connector server
*/
public final static String JMX_URL = "service:jmx:rmi:///jndi/rmi://%1$s:%2$d/jmxrmi" ;
/**
* constructor allowing this helper to be used as a helper
* @param rule the rule this helper is attached to
*/
public JMXHelper(Rule rule) {
super(rule);
shutDown = false;
}
/************************************************************************/
/* Helper lifecycle implementation methods used to install a background */
/* thread when rules employing the helper are installed and to remove */
/* the background thread when they are uninstalled */
/************************************************************************/
/**
* helper activation method which creates a periodic helper thread to perform periodic calls to the trigger
* method. should only be called when synchronized on PeriodicHelper.class.
*/
public static void activated()
{
if (theHelper == null) {
theHelper = new JMXHelper(null);
theHelper.start();
}
}
/**
* helper deactivation method which shuts down the periodic helper thread. will only be called when
* synchronized on PeriodicHelper.class
*/
public static void deactivated()
{
if (theHelper != null) {
theHelper.shutdown();
theHelper = null;
}
}
/**
* Byteman does not guarantee to deactivate the helper at shutdown. However, we need to be sure that
* a deactivate happens if we are using the connector server to avoid RMI problems. So, we register
* a shutdown hook when this helper class gets loaded. We don't bother about deregistering it inside
* deactivate and then reregistering it next time we activate and so on. We can safely do so because
* deactivated is idempotent.
*/
static {
Thread thread = new Thread("JMXHelper Shutdown Hook Deactivation Thread") {
public void run()
{
synchronized(JMXHelper.class)
{
JMXHelper.deactivated();
}
}
};
Runtime.getRuntime().addShutdownHook(thread);
}
/************************************************************************/
/* methods exposed by the helper class for the benefit of a rule set, */
/* allowing it to specify: */
/* the sampling period */
/* the keys and value types of the counters used for sampling */
/* the number of sample sets to combine when computing RATE and MEAN */
/* whether to create a JMX RMI Connector server to expose MBean info */
/* if so what host and port name to use for the connection */
/************************************************************************/
/**
* method called by the helper thread when it is activated to obtain the initial sample period.
* this is provided so that a rule set can inject an initial value to be used a the sample
* period. it should return a time interval in milliseconds.
* @return
*/
private long samplePeriod()
{
return DEFAULT_PERIOD;
}
/**
* method called by the helper thread when it is activated to obtain the number of samples
* over which counter rates or counter means should be averaged. this is provided so that
* a rule set can inject an initial value to be used a the sample period. it should return
* a positive integer ibetween 1 and 10.
* @return
*/
private int sampleSetSize()
{
return DEFAULT_SAMPLE_SET_SIZE;
}
/**
* method called once by the helper thread when it is activated to obtain the list of keys identifying
* counters which are to be displayed by the helper mbean. this is provided so that a rule set
* inject a rule which creates and returns a value identifying the counters the rule set is
* collecting.
* @return
*/
private KeyInfo keyInfo()
{
// if the rule set does not generate a keyinfo value then we return this one to indicate
// that something is missing.
String[] keyNames = new String[1];
keyNames[0] = "No counters defined";
return new KeyInfo("Byteman Periodic Statistics", keyNames);
}
/**
* method called once by the helper thread when it is activated to decide whether to start up a
* JMX RMI Connector Service. this is provided so that a rule set can inject a rule which returns
* true to enable startup. this method returns false which means it is disabled by default.
* @return
*/
private boolean rmiServerRequired()
{
return false;
}
/**
* method called once by the helper thread when it is activated if rmiServerrequired returns true.
* this is provided so that a rule set can inject a rule which returns a host name to use for
* the connection. this method returns localhost as the default value.
* @return
*/
private String rmiHost()
{
return DEFAULT_RMI_HOST;
}
/**
* method called once by the helper thread when it is activated if rmiServerrequired returns true.
* this is provided so that a rule set can inject a rule which returns a port to use for
* the connection. this method returns "1234" as the default value.
* @return
*/
private int rmiPort()
{
return DEFAULT_RMI_PORT;
}
/************************************************************************/
/* DynamicMBean implementation */
/************************************************************************/
public Object getAttribute(String attribute)
throws AttributeNotFoundException, MBeanException, ReflectionException
{
if (attribute.equals("period")) {
return getPeriodSecs();
}
if (attribute.equals("samples")) {
return getPeriodSecs();
}
int pos = attribute.lastIndexOf(" : ");
String keyname = attribute.substring(0,pos);
int keyType;
if (attribute.endsWith(" : rate")) {
keyType = KeyInfo.KEY_TYPE_RATE;
} else if (attribute.endsWith(" : mean")) {
keyType = KeyInfo.KEY_TYPE_MEAN;
} else {
keyType = KeyInfo.KEY_TYPE_CUMULATIVE;
}
String[] keyNames = keyInfo.getKeyNames();
int[] keyTypes = keyInfo().getKeyTypes();
int keyCount = keyInfo.getKeyCount();
for (int i = 0; i < keyCount; i++) {
if (keyNames[i].equals(keyname) && keyTypes[i] == keyType) {
return getValue(i);
}
}
throw new AttributeNotFoundException("JMXHelper : not expecting get for attribute " + attribute);
}
public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
if (attribute.getName().equals("period")) {
int value = ((Integer)attribute.getValue()).intValue();
setPeriodSecs(value);
} else {
throw new InvalidAttributeValueException("JMXHelper : not expecting set call for attribute " + attribute.getName());
}
}
public AttributeList getAttributes(String[] attributes) {
AttributeList list = new AttributeList();
for (String name : attributes) {
try {
Object value = getAttribute(name);
if (value != null) {
list.add(new Attribute(name, value));
}
} catch (AttributeNotFoundException e) {
// ignore
} catch (MBeanException e) {
// ignore
} catch (ReflectionException e) {
// ignore
}
}
return list;
}
public AttributeList setAttributes(AttributeList attributes) {
AttributeList list = new AttributeList();
for (Attribute attribute : attributes.asList()) {
try {
setAttribute(attribute);
list.add(getAttribute(attribute.getName()));
} catch (AttributeNotFoundException e) {
// ignore
} catch (InvalidAttributeValueException e) {
// ignore
} catch (MBeanException e) {
// ignore
} catch (ReflectionException e) {
// ignore
}
}
return list;
}
public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException {
if (!"reset".equals(actionName)) {
throw new MBeanException(null, "JMXHelper : not expecting invoke call for action " + actionName);
}
if (params.length != 0) {
throw new MBeanException(null, "JMXHelper : not expecting arguments for action " + actionName);
}
synchronized (this) {
String[] keyNames = keyInfo.getKeyNames();
int keyCount = keyInfo.getKeyCount();
// reset time stamps
for (int i = 0; i < sampleSetSizePlusOne; i++) {
timeStamps[i][START_TIME] = timeStamps[i][END_TIME] = 0;
}
timeStamps[0][START_TIME] = System.currentTimeMillis();
ringIndex = 0;
for (int i = 0; i < keyCount; i++) {
String keyName = keyNames[i];
readCounter(keyName, true);
for (int j = 0; j < sampleSetSizePlusOne; j++) {
seriesValues[j][i] = 0;
}
counterValues[i] = 0;
}
}
return null;
}
public MBeanInfo getMBeanInfo() {
String label = keyInfo().getLabel();
int keyCount = keyInfo.getKeyCount();
String[] keyNames = keyInfo.getKeyNames();
int[] keyTypes = keyInfo.getKeyTypes();
String[] keyLabels = keyInfo.getKeyLabels();
String className = getClass().getName();
MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[keyInfo.getKeyCount() + 2];
MBeanConstructorInfo[] constructors = null;
MBeanOperationInfo[] operations = new MBeanOperationInfo[1];
MBeanNotificationInfo[] notifications = null;
attributes[0] = new MBeanAttributeInfo("period",
"java.lang.Integer",
"Sample Period",
true,
true,
false);
attributes[1] = new MBeanAttributeInfo("samples",
"java.lang.Integer",
"Sample Set Size",
true,
false,
false);
for (int i = 0; i < keyCount; i++) {
String type;
String descriptor;
if (keyTypes[i] == KeyInfo.KEY_TYPE_CUMULATIVE) {
type = "java.lang.Integer";
descriptor = " : total";
} else if (keyTypes[i] == KeyInfo.KEY_TYPE_RATE) {
type = "java.lang.Float";
descriptor = " : rate";
} else {
type = "java.lang.Float";
descriptor = " : mean";
}
attributes[i + 2] = new MBeanAttributeInfo(keyNames[i] + descriptor,
type,
keyLabels[i],
true,
false,
false);
}
operations[0] = new MBeanOperationInfo("reset", "zero all counters", null, "void", MBeanOperationInfo.ACTION);
return new MBeanInfo(className, label, attributes, constructors, operations, notifications);
}
/************************************************************************/
/* private implementation */
/************************************************************************/
/**
* singleton instance holding the current periodic helper
*/
private static JMXHelper theHelper = null;
/**
* handle on the current helper thread
*/
private static PeriodicHelperThread theHelperThread = null;
/**
* an mbean server for registering the mbean
*/
private static MBeanServer mbeanServer = null;
/**
* a connector server providing RMI access to the mbean server
*/
private static JMXConnectorServer connectorServer = null;
/**
* flag used to control shutdown
*/
private boolean shutDown;
/**
* the interval between wakeups for the helper thread
*/
private long period;
/**
* the number of samples collected plus one. this allows for the fact that one of the sample
* slots will be live and so will not contain a valid end time.
*/
private int sampleSetSizePlusOne;
/**
* the key information identifying the counters being sampled and the type of information they provide
*/
private KeyInfo keyInfo;
/**
* array storing sampled counter values dimension 1 is keyCount
*/
private int[] counterValues;
/**
* array storing previously sampled counter values dimension 1 is RING_SIZE dimension 2 is keyCount
*/
private int[][] seriesValues;
/**
* array storing timestamps for previously sampled values dimension 1 is RING_SIZE dimension 2 is 2
*/
private long[][] timeStamps;
/**
* index into first dimension of timestamp array containing start time
*/
private final static int START_TIME = 0;
/**
* index into second dimension of timestamp array containing end time
*/
private final static int END_TIME = 1;
/**
* ring buffer index identifying next
*/
private int ringIndex;
/**
* fetch the current sample period in milliseconds
*/
private long getPeriodMillisecs()
{
return period;
}
/**
* update the current sample period
* @param period the new period in milliseconds which must be greater than one second
*/
private void setPeriodMillisecs(long period)
{
if (period < 1000L) {
throw new IllegalArgumentException("Period must be greater than 1 second!");
}
synchronized (this) {
this.period = period;
notify();
}
}
/**
* update the current sample set size
* @param period the new period in milliseconds which must be greater than one second
*/
private void setSampleSetSize(int sampleSetSize)
{
if (sampleSetSize < 1) {
throw new IllegalArgumentException("Sample set size must be greater than 1!");
}
if (sampleSetSize > 10) {
throw new IllegalArgumentException("Sample set size must be no greater than 10!");
}
this.sampleSetSizePlusOne = sampleSetSize;
}
/**
* fetch the info describing the counter keys and types
* @return
*/
private KeyInfo getKeyInfo()
{
return keyInfo;
}
/**
* assign the info describing the counter keys and types
* @param keyInfo
*/
private void setKeyInfo(KeyInfo keyInfo)
{
this.keyInfo = keyInfo;
// initialise the counter arrays according to the size
int keyCount = keyInfo.getKeyCount();
ringIndex = 0;
counterValues = new int[keyCount];
seriesValues = new int[sampleSetSizePlusOne][];
for (int i = 0; i < sampleSetSizePlusOne; i++) {
seriesValues[i] = new int[keyCount];
}
timeStamps = new long[sampleSetSizePlusOne][];
for (int i = 0; i < sampleSetSizePlusOne; i++) {
timeStamps[i] = new long[2];
timeStamps[i] = new long[2];
}
timeStamps[ringIndex][START_TIME] = System.currentTimeMillis();
}
/**
* method called in activate to create and run the shutdown thread. will only be called when synchronized
* on PeriodicHelper.class
*/
private void start()
{
theHelperThread = new PeriodicHelperThread();
theHelperThread.start();
}
/**
* method called in deactivate to shutdown the helper thread. will only be called when synchronized on
* PeriodicHelper.class
*/
private void shutdown()
{
synchronized (this) {
shutDown = true;
this.notify();
}
try {
theHelperThread.join();
} catch (InterruptedException e) {
// ignore -- should never happen
}
theHelperThread = null;
}
/**
* a getter called when the helper is activated which computes the mbean server to use
*/
private static MBeanServer getMBeanServer()
{
// If the sysprop is not defined, default to the platform MBeanServer.
// If it is defined, it is the name of the MBS' default domain name so
// look for an MBeanServer that already has that default domain name and
// if exists, use it, otherwise create a new one.
// Note that if the sysprop is defined and has the special value "*platform*"
// the platform MBeanServer will be used.
MBeanServer mbsToReturn = null;
String mbeanServerDomainToLookFor = System.getProperty(SYSPROP_MBEAN_SERVER);
if (mbeanServerDomainToLookFor == null || "*platform*".equals(mbeanServerDomainToLookFor)) {
mbsToReturn = ManagementFactory.getPlatformMBeanServer();
} else {
ArrayList mbeanServers = MBeanServerFactory.findMBeanServer(null);
if (mbeanServers != null) {
for (MBeanServer mbs : mbeanServers ) {
if (mbeanServerDomainToLookFor.equals(mbs.getDefaultDomain())) {
mbsToReturn = mbs;
break;
}
}
}
if (mbsToReturn == null) {
mbsToReturn = MBeanServerFactory.createMBeanServer(mbeanServerDomainToLookFor);
}
}
return mbsToReturn;
}
/**
* called by the helper thread when at startup. this calls the trigger methods which the rule set
* should short circuit to return information used to parameterise operation of the dynamic MBean
*/
private void initialise()
{
// enable triggering and then install the MBean
setTriggering(true);
setPeriodMillisecs(samplePeriod());
setSampleSetSize(sampleSetSize());
setKeyInfo(keyInfo());
mbeanServer = getMBeanServer();
try {
mbeanServer.registerMBean(this, ObjectName.getInstance("org.jboss.byteman.sample.jmx:type=PeriodicStats"));
} catch (InstanceAlreadyExistsException e) {
e.printStackTrace(System.out);
} catch (MBeanRegistrationException e) {
e.printStackTrace(System.out);
} catch (NotCompliantMBeanException e) {
e.printStackTrace(System.out);
} catch (MalformedObjectNameException e) {
e.printStackTrace(System.out);
}
// start a JMX RMI server if it is needed
boolean isRmiRequired = rmiServerRequired();
if (isRmiRequired) {
String host = rmiHost();
int port = rmiPort();
if (port > 0) {
try {
JMXServiceURL url = new JMXServiceURL(String.format( JMX_URL, host, port));
connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer);
connectorServer.start();
} catch (IOException e) {
e.printStackTrace(System.out);
}
}
}
setTriggering(false);
}
/**
* this gets called by the helper thread when it starts and calls the trigger methods which
* the rule set can use to provide the information which parameterises operation of the MBean
*/
private void cleanup()
{
if (connectorServer != null) {
try {
connectorServer.stop();
} catch (IOException e) {
e.printStackTrace(System.out);
}
connectorServer = null;
}
try {
mbeanServer.unregisterMBean(ObjectName.getInstance("org.jboss.byteman.sample.jmx:type=PeriodicStats"));
} catch (InstanceNotFoundException e) {
e.printStackTrace(System.out);
} catch (MBeanRegistrationException e) {
e.printStackTrace(System.out);
} catch (MalformedObjectNameException e) {
e.printStackTrace(System.out);
}
mbeanServer = null;
}
/**
* method called by the periodic helper thread to wait between calls to the trigger method
*/
private boolean doWait()
{
synchronized(this) {
if (!shutDown) {
try {
wait(period);
} catch (InterruptedException e) {
// ignore -- should never happen
}
}
return !shutDown;
}
}
/**
* getter for period used by the MBean code
*
* @return
*/
private int getPeriodSecs()
{
return (int) (period / 1000L);
}
/**
* setter for period used by the MBean code
* @param period
*/
private void setPeriodSecs(int period)
{
this.period = ((long)period) * 1000L;
}
/**
* method called at regular intervals by the periodic helper thread to trigger sampling and
* publishing of counters.
*/
private synchronized void periodicUpdate()
{
int keyCount = keyInfo.getKeyCount();
String[] keyNames = keyInfo.getKeyNames();
int[] keyTypes = keyInfo.getKeyTypes();
long timestamp = System.currentTimeMillis();
// collect the sample data and then update the DynamicMBean
for (int i = 0; i < keyCount; i++) {
int counterValue = readCounter(keyNames[i], false);
if (keyTypes[i] != KeyInfo.KEY_TYPE_CUMULATIVE) {
// copy difference in value into next free slot in ring
seriesValues[ringIndex][i] = counterValue - counterValues[i];
}
counterValues[i] = counterValue;
}
// update timestamp slots and increment index
timeStamps[ringIndex][END_TIME] = timestamp;
ringIndex = (ringIndex + 1) % sampleSetSizePlusOne;
timeStamps[ringIndex][START_TIME] = timestamp;
timeStamps[ringIndex][END_TIME] = 0;
}
/**
* getter for counter values used by MBean code
* @param idx the index into the key list
* @return the associated counter value as an Object
*/
public synchronized Object getValue(int idx)
{
int keyType = keyInfo.getKeyTypes()[idx];
switch (keyType) {
case KeyInfo.KEY_TYPE_CUMULATIVE:
default:
return counterValues[idx];
case KeyInfo.KEY_TYPE_RATE:
{
long start = Long.MAX_VALUE;
long end = 0;
float sum = 0;
// sum as many values as we have time intervals for
for (int i = 0; i < sampleSetSizePlusOne; i++) {
long nextStart = timeStamps[i][START_TIME];
long nextEnd = timeStamps[i][END_TIME];
if (nextStart != 0 && nextEnd != 0) {
sum += seriesValues[i][idx];
if (start > nextStart) {
start = nextStart;
}
if (end < nextEnd) {
end = nextEnd;
}
}
}
if (end == 0) {
// no complete samples yet
return 0.0;
} else {
// result is number per second
return ((sum * 1000) / (end - start));
}
}
case KeyInfo.KEY_TYPE_MEAN:
{
long start = Long.MAX_VALUE;
long end = 0;
float sum = 0;
long totalTime = 0;
// sum as many values as we have time intervals for
for (int i = 0; i < sampleSetSizePlusOne; i++) {
long nextStart = timeStamps[i][START_TIME];
long nextEnd = timeStamps[i][END_TIME];
if (nextStart != 0 && nextEnd != 0) {
// the intervals may not all be equal so we
// weight the amount added to the sum by the time interval
long interval = (nextEnd - nextStart);
sum += (seriesValues[i][idx] * interval);
totalTime += interval;
}
}
if (totalTime == 0) {
// no complete samples yet
return 0.0;
} else {
// now divide the weighted sum by the total time and the
// result is a running mean value per sample
return (sum / totalTime);
}
}
}
}
// auxiliary classes
/**
* background thread which regularly samples the counters updated by the rule set and
* updates the counters accordingly
*/
private class PeriodicHelperThread extends Thread
{
public PeriodicHelperThread()
{
super("Periodic Helper Thread");
}
public void run()
{
// we only want this thread to be able to trigger rules at very specific points
setTriggering(false);
// allow the rule set to initialise theJMX helper sample period and key info
// and register am MX bean
initialise();
// if we got through that with no exceptions then we must ensure we clean up
try {
while (doWait()) {
periodicUpdate();
}
} finally {
cleanup();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy