/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group 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.helios.apmrouter.byteman.sockets.impl; import org.helios.apmrouter.util.SimpleLogger; import javax.management.MBeanServerInvocationHandler; import javax.management.ObjectName; import java.io.InputStream; import java.io.OutputStream; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.SocketAddress; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * <p>Title: SocketTrackingAdapter</p> * <p>Description: A static template class acting as a receiver for events emitted from instrumented socket implementations</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter</code></p> * TODO: Options for detecting closed remote sockets: * Define interface and impls for disconnected remote detector * sendUrgentData supression (default is false) * Sigar NetStat */ public class SocketTrackingAdapter implements SocketTrackingAdapterMBean { /** The registered socket tracker */ protected static ISocketTracker socketTracker = null; /** The socket tracking adapters for socket impls */ public static final Map<String, String> SOCKET_IMPL_ADAPTERS; /** The socket tracking adapters for socket output stream */ public static final Map<String, String> SOCKET_OS_ADAPTERS; /** The socket tracking adapters for socket input stream */ public static final Map<String, String> SOCKET_IS_ADAPTERS; /** A map of instantiated socket trackers keyed by the class name */ protected static final Map<String, ISocketTracker> socketTrackerInstances = new ConcurrentHashMap<String, ISocketTracker>(); /** The system property name for the socket tracker to install at init time */ public static final String SOCK_TRACKER_PROP = "org.helios.apmrouter.socket.tracker"; /** The template for the JMX ObjectName for installed socket trackers */ public static final String SOCK_OBJECT_NAME = "org.helios.apmrouter.sockets:service=SocketTracker,name=%s"; /** The JMX ObjectName for the SocketTrackingAdapter */ public static final String SOCK_ADAPTER_OBJECTNAME = "org.helios.apmrouter.sockets:service=SocketTrackingAdapter"; /** * Creates a new SocketTrackingAdapter */ private SocketTrackingAdapter() { } static { Map<String, String> methodMap = new HashMap<String, String>(); methodMap.put("connect.(Ljava/lang/String;I)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onConnect($0, $$);"); methodMap.put("connect.(Ljava/net/InetAddress;I)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onConnect($0, $$);"); methodMap.put("connect.(Ljava/net/SocketAddress;I)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onConnect($0, $$);"); methodMap.put("bind.(Ljava/net/InetAddress;I)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onBind($0, $$);"); methodMap.put("listen.(I)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onListen($0, $$);"); methodMap.put("accept.(Ljava/net/SocketImpl;)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onAccept($0, $$);"); methodMap.put("getInputStream.()Ljava/io/InputStream;","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onGetInputStream($0, $_);"); methodMap.put("getOutputStream.()Ljava/io/OutputStream;","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onGetOutputStream($0, $_);"); methodMap.put("available.()I","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onAvailable($0, $_);"); methodMap.put("close.()V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onClose($0);"); methodMap.put("shutdownInput.()V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onShutdownInput($0);"); methodMap.put("shutdownOutput.()V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onShutdownOutput($0);"); methodMap.put("sendUrgentData.(I)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onSendUrgentData($0, $$);"); methodMap.put("setSocket.(Ljava/net/Socket;)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onSetSocket($0, $$);"); methodMap.put("setServerSocket.(Ljava/net/ServerSocket;)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onSetServerSocket($0, $$);"); methodMap.put("reset.()V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onReset($0);"); methodMap.put("setPerformancePreferences.(III)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onSetPerformancePreferences($0, $$);"); SOCKET_IMPL_ADAPTERS = Collections.unmodifiableMap(methodMap); methodMap = new HashMap<String, String>(); methodMap.put("socketWrite.([BII)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onSocketWrite($0, socket, $$);"); SOCKET_OS_ADAPTERS = Collections.unmodifiableMap(methodMap); methodMap = new HashMap<String, String>(); methodMap.put("read.([B)I","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onRead($0, $_, socket, $$);"); methodMap.put("read.([BII)I","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onRead($0, $_, socket, $$);"); methodMap.put("read.()I","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onRead($0, $_, socket);"); methodMap.put("setEOF.(Z)V","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onSetEOF($0, socket, $$);"); methodMap.put("skip.(J)J","org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapter.onSkip($0, $_, socket, $$);"); SOCKET_IS_ADAPTERS = Collections.unmodifiableMap(methodMap); setISocketTracker(getISocketTracker(System.getProperty(SOCK_TRACKER_PROP, null))); try { ManagementFactory.getPlatformMBeanServer().registerMBean(new SocketTrackingAdapter(), new ObjectName(SOCK_ADAPTER_OBJECTNAME)); } catch (Exception ex) { SimpleLogger.warn("Failed to register SocketTrackingAdapter JMX interface. Continuing without.", ex); } SimpleLogger.info("Initialized SocketTrackingAdapter [", SocketTrackingAdapter.class.getClassLoader(), "]" ); } public static final SocketTrackingAdapterMBean getInvoker() { return MBeanServerInvocationHandler.newProxyInstance(ManagementFactory.getPlatformMBeanServer(), objectName(SOCK_ADAPTER_OBJECTNAME), SocketTrackingAdapterMBean.class, false); } public static final ObjectName objectName(CharSequence name) { try { return new ObjectName(name.toString()); } catch (Exception ex) { throw new RuntimeException(ex); } } /** * {@inheritDoc} * @see org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapterMBean#getInstalledTrackerName() */ @Override public String getInstalledTrackerName() { if(socketTracker==null) return null; return socketTracker.getClass().getName(); } /** * {@inheritDoc} * @see org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapterMBean#setInstalledTrackerName(java.lang.String) */ @Override public void setInstalledTrackerName(String trackerClassName) { if(trackerClassName==null || trackerClassName.trim().isEmpty()) { setISocketTracker(null); } else { setISocketTracker(getISocketTracker(trackerClassName)); } } /** * {@inheritDoc} * @see org.helios.apmrouter.byteman.sockets.impl.SocketTrackingAdapterMBean#setInstalledTrackerName(java.lang.String, java.lang.ClassLoader) */ public void setInstalledTrackerName(String trackerClassName, ClassLoader cl) { if(trackerClassName==null || trackerClassName.trim().isEmpty()) { setISocketTracker(null); } else { setISocketTracker(getISocketTracker(trackerClassName, cl)); } } /** * Returns an instance of the named socket tracker * @param trackerClassName The socket tracker class name to load * @param cl An optional classloader. Ignored if null * @return the loaded socket tracker */ @SuppressWarnings("unchecked") protected static ISocketTracker getISocketTracker(String trackerClassName, ClassLoader cl) { if(trackerClassName==null || trackerClassName.trim().isEmpty()) return null; ISocketTracker socketTracker = socketTrackerInstances.get(trackerClassName.trim()); if(socketTracker==null) { synchronized(socketTrackerInstances) { socketTracker = socketTrackerInstances.get(trackerClassName.trim()); if(socketTracker==null) { try { Class<ISocketTracker> clazz = null; if(cl==null) { clazz = (Class<ISocketTracker>) Class.forName(trackerClassName.trim()); } else { clazz = (Class<ISocketTracker>) Class.forName(trackerClassName.trim(), true, cl); } socketTracker = clazz.newInstance(); socketTrackerInstances.put(socketTracker.getClass().getName(), socketTracker); } catch (Exception ex) { SimpleLogger.error("Failed to load socket tracker [", trackerClassName, "]", ex); socketTracker = null; } } } } return socketTracker; } /** * Returns an instance of the named socket tracker * @param trackerClassName The socket tracker class name to load * @return the loaded socket tracker */ protected static ISocketTracker getISocketTracker(String trackerClassName) { return getISocketTracker(trackerClassName, null); } /** * Sets the socket tracker * @param tracker the socket tracker or null to clear the tracker */ public static void setISocketTracker(ISocketTracker tracker) { if(socketTracker!=null && tracker!=null && socketTracker==tracker) return; if(socketTracker!=null) { socketTracker.stop(); socketTracker.setActiveTracker(tracker==null ? null : tracker.getClass().getSimpleName()); } socketTracker = tracker; if(socketTracker!=null && !socketTracker.isStarted()) { socketTracker.setActiveTracker(socketTracker.getClass().getSimpleName()); socketTracker.start(); } } /** * Called when EOF is set on a socket * @param is the input stream * @param socket the socket * @param eof the eof value */ public static void onSetEOF(InputStream is, Object socket, boolean eof) { if(socketTracker!=null) socketTracker.onSetEOF(is, socket, eof); } /** * Called when bytes are skipped on the input stream of a socket * @param is The input stream * @param skipped the actual number of bytes to skip * @param socket the socket * @param skip the number of bytes to skip */ public static void onSkip(InputStream is, long skipped, Object socket, long skip) { if(socketTracker!=null) socketTracker.onSkip(is, skipped, socket, skip); } /** * Called when a write completes to a socket * @param os The output stream * @param socket The socket * @param b the data that was written * @param off the start offset in the data * @param len the number of bytes that were written */ public static void onSocketWrite(OutputStream os, Object socket, byte b[], int off, int len) { if(socketTracker!=null) socketTracker.onSocketWrite(os, socket, b, off, len); } /** * Called when data is read from a socket * @param is the input stream * @param actualBytesRead the actual number of bytes read, -1 is returned when the end of the stream is reached * @param socket the socket * @param buffer the buffer into which the data is read */ public static void onRead(InputStream is, int actualBytesRead, Object socket, byte[] buffer) { if(socketTracker!=null) socketTracker.onRead(is, actualBytesRead, socket, buffer); } /** * Called when data is read from a socket * @param is the input stream * @param actualBytesRead the actual number of bytes read, -1 is returned when the end of the stream is reached * @param socket the socket * @param buffer the buffer into which the data is read * @param off the start offset of the data * @param length the maximum number of bytes read */ public static void onRead(InputStream is, int actualBytesRead, Object socket, byte[] buffer, int off, int length) { if(socketTracker!=null) socketTracker.onRead(is, actualBytesRead, socket, buffer, off, length); } /** * Called when a byte is read from a socket * @param is the input stream * @param value the value read * @param socket the socket */ public static void onRead(InputStream is, int value, Object socket) { if(socketTracker!=null) socketTracker.onRead(is, value, socket); } /** * Called on a socket impl connect. This is the only <i>actual</i> connect execution. The overloads are redirected here. * @param socketImpl the socket impl that connected * @param address the socket address that the socket impl connected to * @param timeout the timeout used for connect */ public static void onConnect(Object socketImpl, SocketAddress address, int timeout) { if(socketTracker!=null) socketTracker.onConnect((ISocketImpl)socketImpl, address, timeout); } /** * Called on a socket impl connect * @param socketImpl the socket impl that connected * @param address the inet address that the socket impl connected to * @param timeout the timeout used for connect */ public static void onConnect(Object socketImpl, InetAddress address, int timeout) { if(socketTracker!=null) socketTracker.onConnect((ISocketImpl)socketImpl, address, timeout); } /** * Called on a socket impl connect * @param socketImpl the socket impl that connected * @param host the host name that the socket impl connected to * @param port the port that the socket impl connected to */ public static void onConnect(Object socketImpl, String host, int port) { if(socketTracker!=null) socketTracker.onConnect((ISocketImpl)socketImpl, host, port); } /** * Called when a socket impl binds to a socket * @param socketImpl the socket impl that was bound * @param host the host of the bound socket * @param port the port of the bound socket */ public static void onBind(Object socketImpl, InetAddress host, int port) { if(socketTracker!=null) socketTracker.onBind((ISocketImpl)socketImpl, host, port); } /** * Called when a socket impl has its backlog queue set * @param socketImpl the socket impl that had its backlog queue set * @param backlog the connection backlog */ public static void onListen(Object socketImpl, int backlog) { if(socketTracker!=null) socketTracker.onListen((ISocketImpl)socketImpl, backlog); } /** * Called when a server socket impl accepts a new connection * @param socketImpl the socket impl that accepted * @param acceptedSocketImpl the accepted client socket impl */ public static void onAccept(Object socketImpl, Object acceptedSocketImpl) { if(socketTracker!=null) socketTracker.onAccept((ISocketImpl)socketImpl, (ISocketImpl)acceptedSocketImpl); } /** * Called when the input stream is requested from a socket impl * @param socketImpl the socket impl * @param inputStream the returned input stream */ public static void onGetInputStream(Object socketImpl, InputStream inputStream) { if(socketTracker!=null) socketTracker.onGetInputStream((ISocketImpl)socketImpl, inputStream); } /** * Called when the output stream is requested from a socket impl * @param socketImpl the socket impl * @param outputStream the returned output stream */ public static void onGetOutputStream(Object socketImpl, OutputStream outputStream) { if(socketTracker!=null) socketTracker.onGetOutputStream((ISocketImpl)socketImpl, outputStream); } /** * Called when the available bytes is requested from a socket impl * @param socketImpl the socket impl * @param available the available bytes returned */ public static void onAvailable(Object socketImpl, int available) { if(socketTracker!=null) socketTracker.onAvailable((ISocketImpl)socketImpl, available); } /** * Called when a socket impl is closed * @param socketImpl the socket impl */ public static void onClose(Object socketImpl) { if(socketTracker!=null) socketTracker.onClose((ISocketImpl)socketImpl); } /** * Called when input is shutdown on a socket impl * @param socketImpl the socket impl */ public static void onShutdownInput(Object socketImpl) { if(socketTracker!=null) socketTracker.onShutdownInput((ISocketImpl)socketImpl); } /** * Called when output is shutdown on a socket impl * @param socketImpl the socket impl */ public static void onShutdownOutput(Object socketImpl) { if(socketTracker!=null) socketTracker.onShutdownOutput((ISocketImpl)socketImpl); } /** * Called when urgent data is sent through a socket impl * @param socketImpl the socket impl * @param data the data that was sent */ public static void onSendUrgentData(Object socketImpl, int data) { if(socketTracker!=null) socketTracker.onSendUrgentData((ISocketImpl)socketImpl, data); } /** * Called when the client socket is set on a socket impl * @param socketImpl the socket impl * @param socket the set socket */ public static void onSetSocket(Object socketImpl, Object socket) { if(socketTracker!=null) socketTracker.onSetSocket((ISocketImpl)socketImpl, socket); } /** * Called when the server socket is set on a socket impl * @param socketImpl the socket impl * @param serverSocket The set server socket */ public static void onSetServerSocket(Object socketImpl, Object serverSocket) { if(socketTracker!=null) socketTracker.onSetServerSocket((ISocketImpl)socketImpl, serverSocket); } /** * Called when a socket impl is reset * @param socketImpl the socket impl */ public static void onReset(Object socketImpl) { if(socketTracker!=null) socketTracker.onReset((ISocketImpl)socketImpl); } /** * Called when performance preferences are set on a socket impl * @param socketImpl the socket impl * @param connectionTime An <tt>int</tt> expressing the relative importance of a short connection time * @param latency An <tt>int</tt> expressing the relative importance of low latency * @param bandwidth An <tt>int</tt> expressing the relative importance of highbandwidth */ public static void onSetPerformancePreferences(Object socketImpl, int connectionTime, int latency, int bandwidth) { if(socketTracker!=null) socketTracker.onSetPerformancePreferences((ISocketImpl)socketImpl, connectionTime, latency, bandwidth); } }