/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file 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. */ package org.jboss.ejb.plugins; import java.lang.reflect.Method; import java.security.Principal; import java.util.ArrayList; import java.util.List; import javax.jms.DeliveryMode; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; import javax.jms.Topic; import javax.jms.TopicConnection; import javax.jms.TopicConnectionFactory; import javax.jms.TopicPublisher; import javax.jms.TopicSession; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.transaction.Transaction; import org.jboss.ejb.Container; import org.jboss.invocation.Invocation; import org.jboss.monitor.MetricsConstants; /** * MetricsInterceptor collects data from the bean invocation call and publishes * them on a JMS topic (bound to <tt>topic/metrics</tt> in the name service). * * @author <a href="mailto:jplindfo@helsinki.fi">Juha Lindfors</a> * @author <a href="mailto:dimitris@jboss.org">Dimitris Anreadis</a> * @version $Revision: 81030 $ * * @since v2.0 */ public class MetricsInterceptor extends AbstractInterceptor implements MetricsConstants { // Constants ----------------------------------------------------- // Attributes ---------------------------------------------------- /** Application name this bean belongs to */ private String applicationName = "<undefined>"; /** Bean name in the container */ private String beanName = "<undefined>"; /** Publisher thread */ private Thread publisher = null; /** * Message queue for the outgoing JMS messages. This list is accessed * by the interceptor when adding new messages, and by the publisher * thread when copying and clearing the contents of the queue. The list * must be locked for access and locks should be kept down to minimum * as they degrade the interceptor stack performance. */ private List msgQueue = new ArrayList(2000); // Public -------------------------------------------------------- /** * Stores the container reference and the application and bean JNDI * names. * * @param container set by the container initialization code */ public void setContainer(Container container) { super.setContainer(container); if (container != null) { applicationName = container.getEjbModule().getName(); beanName = container.getBeanMetaData().getJndiName(); } } // Interceptor implementation ------------------------------------ public Object invokeHome(Invocation mi) throws Exception { long begin = System.currentTimeMillis(); try { return super.invokeHome(mi); } finally { if (mi.getMethod() != null && publisher.isAlive()) { addEntry(mi, begin, System.currentTimeMillis()); } } } public Object invoke(Invocation mi) throws Exception { long begin = System.currentTimeMillis(); try { return super.invoke(mi); } finally { if (mi.getMethod() != null && publisher.isAlive()) { addEntry(mi, begin, System.currentTimeMillis()); } } } /** * Starts the JMS publisher thread. */ public void create() { log.warn("\n" + "----------------------------------------------------------------------\n" + "Deprecated MetricsInterceptor activated for bean: '" + beanName + "'\n" + "Invocation metrics will be published in JMS Topic: 'topic/metrics'\n" + "----------------------------------------------------------------------" ); // looks like create() is called after setContainer(). // wonder if container method callback order is documented somewhere, it should be.. publisher = new Thread(new Publisher()); publisher.setName("Metrics Publisher Thread for " + beanName); publisher.setDaemon(true); publisher.start(); } /** * Kills the publisher thread. */ public void destroy() { publisher.interrupt(); } // Private -------------------------------------------------------- /** * Store the required information from this invocation: principal, * transaction, method, time. * * @param begin invocation begin time in ms * @param end invocation end time in ms */ private final void addEntry(Invocation mi, long begin, long end) { /* this gets called by the interceptor */ Transaction tx = mi.getTransaction(); Principal princ = mi.getPrincipal(); Method method = mi.getMethod(); Entry start = new Entry(princ, method, tx, begin, "START"); Entry stop = new Entry(princ, method, tx, end, "STOP"); // add both entries, order is guaranteed, synchronized to prevent // publisher from touching the queue while working on it synchronized (msgQueue) { // Two entries for now, one should suffice but requires changes in // the client. msgQueue.add(start); msgQueue.add(stop); } } private Message createMessage(Session session, String principal, int txID, String method, String checkpoint, long time) { try { Message msg = session.createMessage(); msg.setJMSType(INVOCATION_METRICS); msg.setStringProperty(CHECKPOINT, checkpoint); msg.setStringProperty(BEAN, beanName); msg.setObjectProperty(METHOD, method); msg.setLongProperty(TIME, time); if (txID != -1) msg.setStringProperty("ID", String.valueOf(txID)); if (principal != null) msg.setStringProperty("PRINCIPAL", principal); return msg; } catch (Exception e) { // catch JMSExceptions, tx.SystemExceptions, and NPE's // don't want to bother the container even if the metrics fail. return null; } } /** * JMS Publisher thread implementation. */ private class Publisher implements Runnable { /** Thread keep-alive field. */ private boolean running = true; /** Thread sleep delay. */ private int delay = 2000; /** JMS Connection */ private TopicConnection connection = null; /** * Thread implementation. <p> * * When started, looks up a topic connection factory from the name * service, and attempts to create a publisher to <tt>topic/metrics</tt> * topic. <p> * * While alive, locks the <tt>msgQueue</tt> every two seconds to make a * copy of the contents and then clear it. <p> * * Interrupting this thread will kill it. * * @see #msgQueue * @see java.lang.Thread#interrupt() */ public void run() { try { final boolean IS_TRANSACTED = true; final int ACKNOWLEDGE_MODE = Session.DUPS_OK_ACKNOWLEDGE; // lookup the connection factory and topic and create a JMS session Context namingContext = new InitialContext(); TopicConnectionFactory fact = (TopicConnectionFactory) namingContext.lookup("java:/ConnectionFactory"); connection = fact.createTopicConnection(); Topic topic = (Topic) namingContext.lookup("topic/metrics"); TopicSession session = connection.createTopicSession(IS_TRANSACTED, ACKNOWLEDGE_MODE); TopicPublisher pub = session.createPublisher(topic); pub.setDeliveryMode(DeliveryMode.NON_PERSISTENT); pub.setPriority(Message.DEFAULT_PRIORITY); pub.setTimeToLive(Message.DEFAULT_TIME_TO_LIVE); // start the JMS connection connection.start(); // copy the message queue every x seconds, and publish the messages while (running) { Object[] array; long sleepTime = delay; try { Thread.sleep(sleepTime); // measure message processing cost and try to deal // with congestion long begin = System.currentTimeMillis(); // synchronized during the copy... the interceptor will // have to wait til done synchronized (msgQueue) { array = msgQueue.toArray(); msgQueue.clear(); } // publish the messages for (int i = 0; i < array.length; ++i) { Message msg = createMessage(session, ((Entry) array[i]).principal, ((Entry) array[i]).id, ((Entry) array[i]).method, ((Entry) array[i]).checkpoint, ((Entry) array[i]).time ); pub.publish(msg); } // try to deal with congestion a little better, alot of // small messages fast will kill JBossMQ performance, this is // a temp fix to group many messages into one operation try { session.commit(); } catch (Exception e) { } // stop the clock and reduce the work time from our // resting time long end = System.currentTimeMillis(); sleepTime = delay - (end - begin); } catch (InterruptedException e) { // kill this thread running = false; } } } catch (NamingException e) { log.warn(Thread.currentThread().getName() + " exiting", e); } catch (JMSException e) { log.warn(Thread.currentThread().getName() + " exiting", e); } finally { // thread cleanup synchronized (msgQueue) { msgQueue.clear(); } try { if (connection != null) connection.close(); } catch (JMSException e) { log.warn(e); } } } } /** * Wrapper class for message queue entries. * * @see #msgQueue */ private final class Entry { int id = -1; long time; String principal = null; String checkpoint; String method; Entry(Principal principal, Method method, Transaction tx, long time, String checkpoint) { this.time = time; this.checkpoint = checkpoint; this.method = method.getName(); if (tx != null) this.id = tx.hashCode(); if (principal != null) this.principal = principal.getName(); } } }