org.perf4j.helpers.StatisticsExposingMBean Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of perf4j Show documentation
Show all versions of perf4j Show documentation
Performance statistics logging and monitoring toolkit extension to log4j and the java.util.logging framework.
/* Copyright (c) 2008-2009 HomeAway, Inc.
* All rights reserved. http://www.perf4j.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.perf4j.helpers;
import org.perf4j.GroupedTimingStatistics;
import org.perf4j.TimingStatistics;
import javax.management.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class provides a wrapper around GroupedTimingStatistics data so that this performance information can be
* exposed through JMX.
*
* @author Alex Devine
*/
public class StatisticsExposingMBean extends NotificationBroadcasterSupport implements DynamicMBean {
/**
* Logging classes use this as the default ObjectName of this MBean when registering it with an MBeanServer.
*/
public static final String DEFAULT_MBEAN_NAME = "org.perf4j:type=StatisticsExposingMBean,name=Perf4J";
/**
* The type of the Notifications sent when a statistics value is outside of the acceptable range.
*/
public static final String OUT_OF_RANGE_NOTIFICATION_TYPE = "org.perf4j.threshold.exceeded";
/**
* The name under which this MBean is registered in the MBean server.
*/
protected ObjectName mBeanName;
/**
* This MBeanInfo exposes this MBean's management interface to the MBeanServer.
*/
protected MBeanInfo managementInterface;
/**
* The tags whose statistics values are being exposed.
*/
protected Collection tagsToExpose;
/**
* These AcceptableRangeConfigurations force a notification to be sent if a statistic is updated to a value
* outside the allowable range. This Map maps acceptable ranges to whether or not the LAST check of the attribute
* value was good or bad. This is used to ensure only a single notification is sent when an attribute crosses the
* threshold to go out of range.
*/
protected Map acceptableRanges;
/**
* This single thread pool is used to send notifications if any values are outside of the acceptable ranges
* (this is necessary because the JMX spec states that the sendNotification method may be synchronous). This
* member variable will be null if no acceptable ranges are specified.
*/
protected ExecutorService outOfRangeNotifierThread;
/**
* This sequence number is required by the JMX Notification API.
*/
protected long outOfRangeNotificationSeqNo;
/**
* The current underlying timing statistics whose values are exposed as MBean attributes.
*/
protected GroupedTimingStatistics currentTimingStatistics;
/**
* Pattern used to parse requested attribute names into the tag name and the statistic name
*/
protected Pattern attributeNamePattern = Pattern.compile("(.*)(Mean|StdDev|Min|Max|Count|TPS)");
/**
* Creates a new StatisticsExposingMBean whose management interface exposes performance attributes for the tags
* specified, and that sends notifications if attributes are outside of the acceptable ranges.
*
* @param mBeanName The name under which this MBean is registered in the MBean server
* @param tagsToExpose The names of the tags whose statistics should exposed. For each tag specified there will
* be 6 attributes whose getters are exposed: tagNameMean, tagNameStdDev, tagNameMin,
* tagNameMax, and tagNameCount and tagNameTPS
* @param acceptableRanges These acceptable ranges are used to send notifications if any of the monitored
* attributes go outside of the range.
*/
public StatisticsExposingMBean(String mBeanName,
Collection tagsToExpose,
Collection acceptableRanges) {
//set mBeanName
if (mBeanName == null) {
mBeanName = DEFAULT_MBEAN_NAME;
}
try {
this.mBeanName = new ObjectName(mBeanName);
} catch (MalformedObjectNameException mone) {
throw new IllegalArgumentException(mone);
}
//set acceptableRanges
if (acceptableRanges == null || acceptableRanges.isEmpty()) {
this.acceptableRanges = Collections.emptyMap();
} else {
this.acceptableRanges = new LinkedHashMap();
// initialize the last known value of the attribute as good
for (AcceptableRangeConfiguration acceptableRange : acceptableRanges) {
this.acceptableRanges.put(acceptableRange, Boolean.TRUE);
//ensure the attributeName on the range is valid
if (!attributeNamePattern.matcher(acceptableRange.getAttributeName()).matches()) {
throw new IllegalArgumentException(
"Acceptable range attribute name " + acceptableRange.getAttributeName()
+ " invalid - must match pattern " + attributeNamePattern.pattern()
);
}
}
this.outOfRangeNotifierThread = Executors.newSingleThreadExecutor();
}
this.tagsToExpose = new ArrayList(tagsToExpose);
this.managementInterface = createMBeanInfoFromTagNames(tagsToExpose);
this.currentTimingStatistics = new GroupedTimingStatistics(); //just set empty so it's never null
}
/**
* This method should be called to update the underlying timing statistics, which will correspondingly change the
* values of the exposed attributes.
*
* @param currentTimingStatistics The TimingStatistics to set, may not be null
*/
public synchronized void updateCurrentTimingStatistics(GroupedTimingStatistics currentTimingStatistics) {
if (currentTimingStatistics == null) {
throw new IllegalArgumentException("timing statistics may not be null");
}
this.currentTimingStatistics = currentTimingStatistics;
sendNotificationsIfValuesNotAcceptable();
}
/**
* This MBean operation method allows the caller to add a tag whose statistics should be exposed as attributes
* at runtime.
*
* @param tagName The name of the tag whose statistics should be exposed.
*/
public void exposeTag(String tagName) {
this.tagsToExpose.add(tagName);
this.managementInterface = createMBeanInfoFromTagNames(this.tagsToExpose);
}
/**
* This MBean operation method allows the caller to remove, at runtime, a tag whose statistics are exposed.
*
* @param tagName The name of the tag whose statistics should be removed as attributes from this MBean.
* @return Whether or not the specified tag was previously exposed on this MBean.
*/
public boolean removeTag(String tagName) {
boolean retVal = this.tagsToExpose.remove(tagName);
this.managementInterface = createMBeanInfoFromTagNames(this.tagsToExpose);
return retVal;
}
public synchronized Object getAttribute(String attribute)
throws AttributeNotFoundException, MBeanException, ReflectionException {
Matcher matcher = attributeNamePattern.matcher(attribute);
if (matcher.matches()) {
String tagName = matcher.group(1);
String statisticName = matcher.group(2);
TimingStatistics timingStats = currentTimingStatistics.getStatisticsByTag().get(tagName);
long windowLength = currentTimingStatistics.getStopTime() - currentTimingStatistics.getStartTime();
return getStatsValueRetrievers().get(statisticName).getStatsValue(timingStats, windowLength);
} else {
throw new AttributeNotFoundException("No attribute named " + attribute);
}
}
public void setAttribute(Attribute attribute)
throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
throw new AttributeNotFoundException("Statistics attributes are not writable");
}
public synchronized AttributeList getAttributes(String[] attributeNames) {
AttributeList retVal = new AttributeList();
for (String attributeName : attributeNames) {
try {
retVal.add(new Attribute(attributeName, getAttribute(attributeName)));
} catch (Exception e) {
//ignore - the absence of the attribute in the return list indicates there was an error
}
}
return retVal;
}
public AttributeList setAttributes(AttributeList attributes) {
//we don't support setting, so just return an empty list
return new AttributeList();
}
public Object invoke(String actionName, Object[] params, String[] signature)
throws MBeanException, ReflectionException {
if ("exposeTag".equals(actionName)) {
exposeTag(params[0].toString());
return null;
} else if ("removeTag".equals(actionName)) {
return removeTag(params[0].toString());
} else {
throw new UnsupportedOperationException("Unsupported operation: " + actionName);
}
}
public MBeanInfo getMBeanInfo() {
return managementInterface;
}
public MBeanNotificationInfo[] getNotificationInfo() {
return managementInterface.getNotifications();
}
/**
* Overridable helper method gets the Map of statistic name to StatsValueRetriever.
*
* @return The StatsValueRetriever Map.
*/
protected Map getStatsValueRetrievers() {
return StatsValueRetriever.DEFAULT_RETRIEVERS;
}
/**
* Helper method creates an MBeanInfo object that contains 6 read only attributes for each tag name, each
* attribute representing a different statistic.
*
* @param tagNames The name of the tags whose statistics should be exposed as MBeanAttributes.
* @return The MBeanInfo that represents the management interface for this MBean.
*/
protected MBeanInfo createMBeanInfoFromTagNames(Collection tagNames) {
MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[tagNames.size() * getStatsValueRetrievers().size()];
int i = 0;
for (String tagName : tagNames) {
for (Map.Entry statNameAndValueRetriever :
getStatsValueRetrievers().entrySet()) {
String statName = statNameAndValueRetriever.getKey();
StatsValueRetriever statsValueRetriever = statNameAndValueRetriever.getValue();
attributes[i++] = new MBeanAttributeInfo(tagName + statName,
statsValueRetriever.getValueClass().getName(),
"Returns " + statName + " for tag " + tagName,
true /* readable */,
false /* not writable */,
false /* not "is" getter */);
}
}
MBeanOperationInfo[] operations = new MBeanOperationInfo[2]; //exposeTag and removeTag
operations[0] = new MBeanOperationInfo("exposeTag",
"Allows the caller to add a monitored tag at runtime",
new MBeanParameterInfo[]{
new MBeanParameterInfo("tagName",
String.class.getName(),
"The name of the tag to expose")
},
"void",
MBeanOperationInfo.ACTION);
operations[1] = new MBeanOperationInfo("removeTag",
"Allows the caller to remove a monitored tag at runtime",
new MBeanParameterInfo[]{
new MBeanParameterInfo("tagName",
String.class.getName(),
"The name of the tag to remove")
},
"boolean",
MBeanOperationInfo.ACTION);
MBeanNotificationInfo[] notificationInfos;
if (acceptableRanges.isEmpty()) {
//then we don't send any out-of-range notifications
notificationInfos = new MBeanNotificationInfo[0];
} else {
notificationInfos = new MBeanNotificationInfo[]{
new MBeanNotificationInfo(
new String[]{OUT_OF_RANGE_NOTIFICATION_TYPE},
Notification.class.getName(),
"Notification sent if any statistics move outside of the specified acceptable ranges"
)
};
}
return new MBeanInfo(getClass().getName(),
"Timing Statistics",
attributes,
null /* no constructors */,
operations,
notificationInfos);
}
/**
* This helper method sends notifications if any of the acceptable ranges detects an attribute value that is
* outside of the specified range. This method should only be called when the lock on this object's monitor is held.
*/
protected void sendNotificationsIfValuesNotAcceptable() {
//send notifications if any values are outside the acceptable range, but only if the LAST check was good
for (Map.Entry acceptableRangeAndWasGood : acceptableRanges.entrySet()) {
AcceptableRangeConfiguration acceptableRange = acceptableRangeAndWasGood.getKey();
boolean lastCheckWasGood = acceptableRangeAndWasGood.getValue();
double attributeValue;
try {
attributeValue = ((Number) getAttribute(acceptableRange.getAttributeName())).doubleValue();
} catch (Exception e) {
//shouldn't happen
continue;
}
boolean isValueInRange = acceptableRange.isInRange(attributeValue);
//update the lastCheckGood value and send the notification
acceptableRangeAndWasGood.setValue(isValueInRange);
if (lastCheckWasGood && !isValueInRange) {
sendOutOfRangeNotification(attributeValue, acceptableRange);
}
}
}
/**
* Helper method is used to send the JMX notification because the attribute value doesn't fall within the
* acceptable range. This method should only be called when the lock on this object's monitor is held.
*
* @param attributeValue The attribute value that falls outside the threshold
* @param acceptableRange The AcceptableRangeConfiguration used to constrain the acceptable value
*/
protected void sendOutOfRangeNotification(final double attributeValue,
final AcceptableRangeConfiguration acceptableRange) {
outOfRangeNotifierThread.execute(new Runnable() {
public void run() {
String errorMessage = "Attribute value " + attributeValue + " not in range " + acceptableRange;
sendNotification(new Notification(OUT_OF_RANGE_NOTIFICATION_TYPE,
mBeanName,
++outOfRangeNotificationSeqNo,
System.currentTimeMillis(),
errorMessage));
}
});
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy