/* * 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.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.mc4j.ems.connection.bean.EmsBean; import org.mc4j.ems.connection.bean.attribute.EmsAttribute; 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.plugins.jmx.MBeanResourceComponent; /** * A plugin component for managing an EJB2 session bean. * * @author Greg Hinkle * @author Ian Springer * @author John Mazzitelli */ public class EJB2BeanComponent extends MBeanResourceComponent<JBossASServerComponent<?>> { private final Log log = LogFactory.getLog(EJB2BeanComponent.class); private Map<Integer, CallTimeData> previousRawCallTimeDatas = new HashMap<Integer, CallTimeData>(); @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 statelessSessionBeanStats; // javax.management.j2ee.statistics.StatelessSessionBeanStats try { statelessSessionBeanStats = getStatelessSessionBeanStats(); } catch (Exception e) { // Unsure if this will ever happen - maybe the JBoss instance doesn't provide this operation? continue; } try { Map<String, Stat> stats = getStats(statelessSessionBeanStats); Date lastResetTime = getLastResetTime(statelessSessionBeanStats); Date now = new Date(System.currentTimeMillis()); if (!stats.isEmpty()) { CallTimeData callTimeData = createCallTimeData(schedule, stats, lastResetTime, now); report.addData(callTimeData); } } catch (Exception e) { log.error("Failed to retrieve EJB2 call-time data.", e); } } } super.getValues(report, numericMetricSchedules); } private CallTimeData createCallTimeData(MeasurementScheduleRequest schedule, Map<String, Stat> 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()) { Stat timeStatistic = stats.get(methodName); long minTime = timeStatistic.min; long maxTime = timeStatistic.max; long totalTime = timeStatistic.total; long count = timeStatistic.count; 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 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; } private Date getLastResetTime(Object statelessSessionBeanStats) throws Exception { // XXX we assume no one will ever execute the Jboss MBean's resetStats // XXX we look at a count stat's start time and use that as the last reset time Method method = statelessSessionBeanStats.getClass().getMethod("getMethodReadyCount"); Object methodReadyCountStat = method.invoke(statelessSessionBeanStats); long time = (Long) methodReadyCountStat.getClass().getMethod("getStartTime").invoke(methodReadyCountStat); return new Date(time); } private Object getStatelessSessionBeanStats() throws Exception { // will actually be a type of: javax.management.j2ee.statistics.StatelessSessionBeanStats Object statelessSessionBeanStats = null; // accesses the remote MBean to get information on the EJB EmsBean emsBean = getEmsBean(); // use the connection's classloader so we use the appropriate class definitions ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(emsBean.getClass().getClassLoader()); try { EmsAttribute attribute = emsBean.getAttribute("stats"); statelessSessionBeanStats = attribute.refresh(); } catch (RuntimeException e) { String msg = "Failed to retrieve EJB2 invocation stats."; if (log.isDebugEnabled()) { log.debug(msg, e); } else { log.info(msg + " Enable DEBUG logging to see cause."); } throw new Exception(msg, e); } finally { Thread.currentThread().setContextClassLoader(cl); } return statelessSessionBeanStats; } private Map<String, Stat> getStats(Object statelessSessionBeanStats) throws Exception { Map<String, Stat> stats = new HashMap<String, Stat>(); Method method = statelessSessionBeanStats.getClass().getMethod("getStatistics"); Object[] jbossStats = (Object[]) method.invoke(statelessSessionBeanStats); // javax.management.j2ee.statistics.Statistic[] if (jbossStats != null) { for (Object jbossStat : jbossStats) { Class<? extends Object> clazz = jbossStat.getClass(); if (clazz.getSimpleName().contains("TimeStatistic")) { // there appears to be a bug in Jboss stats - startTime always changes when you get // the values and lastSampleTime is always 0. Stat newStat = new Stat(); newStat.name = (String) clazz.getMethod("getName").invoke(jbossStat); newStat.count = (Long) clazz.getMethod("getCount").invoke(jbossStat); newStat.min = (Long) clazz.getMethod("getMinTime").invoke(jbossStat); newStat.max = (Long) clazz.getMethod("getMaxTime").invoke(jbossStat); newStat.total = (Long) clazz.getMethod("getTotalTime").invoke(jbossStat); newStat.startTime = (Long) clazz.getMethod("getStartTime").invoke(jbossStat); newStat.lastSampleTime = (Long) clazz.getMethod("getLastSampleTime").invoke(jbossStat); stats.put(newStat.name, newStat); } } } return stats; } class Stat { String name; long count; long min; long max; long total; long startTime; long lastSampleTime; } }