/*
* Jopr Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program 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 General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.plugins.jbossas;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.domain.measurement.calltime.CallTimeData;
import org.rhq.core.domain.measurement.calltime.CallTimeDataValue;
import org.rhq.core.pluginapi.operation.OperationResult;
import org.rhq.plugins.jbossas.util.JBossMBeanUtility;
import org.rhq.plugins.jmx.MBeanResourceComponent;
/**
* A plugin component for managing an EJB3 session bean.
*
* @author Greg Hinkle
* @author Ian Springer
*/
public class EJB3BeanComponent extends MBeanResourceComponent<JBossASServerComponent<?>> {
private final Log log = LogFactory.getLog(EJB3BeanComponent.class);
private Map<Integer, CallTimeData> previousRawCallTimeDatas = new HashMap<Integer, CallTimeData>();
@Override
public AvailabilityType getAvailability() {
return (JBossMBeanUtility.isStarted(getEmsBean(), getResourceContext())) ? AvailabilityType.UP
: AvailabilityType.DOWN;
}
@Override
public OperationResult invokeOperation(String name, Configuration parameters) throws Exception {
if ("viewMethodStats".equals(name)) {
// TODO GH: Ems should be managing the context classloader for the refresh call (also check the operations 1 more time)
Object invocationStatistics = getInvocationStatistics();
OperationResult result = new OperationResult();
PropertyList methodList = new PropertyList("methods");
result.getComplexResults().put(methodList);
Map<String, Object> stats = getStats(invocationStatistics);
for (String methodName : stats.keySet()) {
Object timeStatistic = stats.get(methodName);
Long count = (Long) timeStatistic.getClass().getField("count").get(timeStatistic);
Long minTime = (Long) timeStatistic.getClass().getField("minTime").get(timeStatistic);
Long maxTime = (Long) timeStatistic.getClass().getField("maxTime").get(timeStatistic);
Long totalTime = (Long) timeStatistic.getClass().getField("totalTime").get(timeStatistic);
PropertyMap method = new PropertyMap("method", new PropertySimple("methodName", methodName),
new PropertySimple("count", count), new PropertySimple("minTime", minTime), new PropertySimple(
"maxTime", maxTime), new PropertySimple("totalTime", totalTime));
methodList.add(method);
}
return result;
}
return super.invokeOperation(name, parameters);
}
@Override
public void getValues(MeasurementReport report, Set<MeasurementScheduleRequest> schedules) {
Set<MeasurementScheduleRequest> numericMetricSchedules = new LinkedHashSet<MeasurementScheduleRequest>();
for (MeasurementScheduleRequest schedule : schedules) {
if (schedule.getDataType() == DataType.MEASUREMENT) {
numericMetricSchedules.add(schedule);
} else if (schedule.getName().equals("MethodInvocationTime")) {
Object invocationStatistics;
try {
invocationStatistics = getInvocationStatistics();
} catch (Exception e) {
// This will be fairly common, since only JBossAS 4.2.x provides this operation, so don't log an
// error.
continue;
}
try {
long lastResetTime = getLastResetTime(invocationStatistics);
Map<String, Object> stats = getStats(invocationStatistics);
long collectionTime = System.currentTimeMillis();
if (!stats.isEmpty()) {
CallTimeData callTimeData = createCallTimeData(schedule, stats, new Date(lastResetTime),
new Date(collectionTime));
report.addData(callTimeData);
}
} catch (Exception e) {
log.error("Failed to retrieve EJB3 call-time data.", e);
}
}
}
super.getValues(report, numericMetricSchedules);
}
private CallTimeData createCallTimeData(MeasurementScheduleRequest schedule, Map<String, Object> stats,
Date lastResetTime, Date collectionTime) throws Exception {
CallTimeData previousRawCallTimeData = this.previousRawCallTimeDatas.get(schedule.getScheduleId());
CallTimeData rawCallTimeData = new CallTimeData(schedule);
this.previousRawCallTimeDatas.put(schedule.getScheduleId(), rawCallTimeData);
CallTimeData callTimeData = new CallTimeData(schedule);
for (String methodName : stats.keySet()) {
Object timeStatistic = stats.get(methodName);
long minTime = (Long) timeStatistic.getClass().getField("minTime").get(timeStatistic);
long maxTime = (Long) timeStatistic.getClass().getField("maxTime").get(timeStatistic);
long totalTime = (Long) timeStatistic.getClass().getField("totalTime").get(timeStatistic);
long count = (Long) timeStatistic.getClass().getField("count").get(timeStatistic);
try {
rawCallTimeData.addAggregatedCallData(methodName, lastResetTime, collectionTime, minTime, maxTime,
totalTime, count);
} catch (IllegalArgumentException iae) {
// if any issue with the data, log them and continue processing the rest of the report
log.error(iae);
continue;
}
// Now compute the adjusted data, which is what we will report back to the server.
CallTimeDataValue previousValue = (previousRawCallTimeData != null) ? previousRawCallTimeData.getValues()
.get(methodName) : null;
boolean supercedesPrevious = ((previousValue != null) && (previousValue.getBeginTime() == lastResetTime
.getTime()));
Date beginTime = lastResetTime;
if (supercedesPrevious) {
// The data for this method hasn't been reset since the last time we collected it.
long countSincePrevious = count - previousValue.getCount();
if (countSincePrevious > 0) {
// There have been new calls since the last time we collected data
// for this method. Adjust the time span to begin at the end of the
// time span from the previous collection.
beginTime = new Date(previousValue.getEndTime());
// Adjust the total and count to reflect the adjusted time span;
// do so by subtracting the previous values from the current values.
// NOTE: It isn't possible to figure out the minimum and maximum for
// the adjusted time span, so just leave them be. If they happen
// to have changed since the previous collection, they will be
// accurate; otherwise they will not.
count = countSincePrevious;
totalTime = totalTime - (long) previousValue.getTotal();
}
// else, the count hasn't changed, so don't bother adjusting the data;
// when the JON server sees the data has the same begin time as
// previously persisted data, it will replace the previous data with the
// updated data (which will basically have a later end time)
}
callTimeData.addAggregatedCallData(methodName, beginTime, collectionTime, minTime, maxTime, totalTime,
count);
}
return callTimeData;
}
// NOTE: Invocation stats were not exposed by EJB3 MBeans in versions of JBoss EJB3 prior to RC9 Patch 1
// (see http://jira.jboss.com/jira/browse/EJBTHREE-742 and
// http://viewvc.jboss.org/cgi-bin/viewvc.cgi/jbossas?view=rev&revision=57901). The EJB3 builds
// bundled with JBossAS 4.2.x are newer than RC9 Patch 1, so invocation stats should always be
// available from AS 4.2.x instances.
private Object getInvocationStatistics() throws Exception {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getEmsBean().getClass().getClassLoader());
// Value is an InvocationStatistics object, but to avoid classloader issues, we don't cast it as such.
// (see http://anonsvn.jboss.org/repos/jbossas/branches/Branch_4_2/ejb3/src/main/org/jboss/ejb3/statistics/InvocationStatistics.java)
Object invocationStatistics = null;
try {
invocationStatistics = getEmsBean().getAttribute("InvokeStats").refresh();
} catch (RuntimeException e) {
String msg = "Failed to retrieve EJB3 invocation stats - perhaps JBoss EJB3 impl version is less than RC9 Patch 1.";
log.info(msg + " Enable DEBUG logging to see cause.");
if (log.isDebugEnabled())
log.debug(msg, e);
throw new Exception(msg, e);
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
return invocationStatistics;
}
private long getLastResetTime(Object invocationStatistics) throws Exception {
Field field = invocationStatistics.getClass().getField("lastResetTime");
return (Long) field.get(invocationStatistics);
}
private Map<String, Object> getStats(Object invocationStatistics) throws Exception {
Method method = invocationStatistics.getClass().getMethod("getStats");
return (Map<String, Object>) method.invoke(invocationStatistics);
}
}