/* * 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); } }