/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2012, 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;
import org.cliffc.high_scale_lib.Counter;
import org.cliffc.high_scale_lib.NonBlockingHashMap;
import org.cliffc.high_scale_lib.NonBlockingHashSet;
import org.helios.apmrouter.byteman.sockets.SocketTracingLevel;
import org.helios.apmrouter.byteman.sockets.SocketTracingLevel.SocketTracingLevelListener;
import org.helios.apmrouter.byteman.sockets.SocketTracingLevel.SocketTracingLevelWatcher;
import org.helios.apmrouter.collections.ConcurrentLongSlidingWindow;
import org.helios.apmrouter.jmx.JMXHelper;
import org.helios.apmrouter.trace.TracerFactory;
import org.helios.apmrouter.util.SimpleLogger;
import org.helios.apmrouter.util.SystemClock;
import org.helios.apmrouter.util.SystemClock.ElapsedTime;
import org.jboss.byteman.rule.Rule;
import javax.management.ObjectName;
import java.io.Closeable;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <p>Title: APMSocketMonitorHelper</p>
* <p>Description: A byteman helper class for monitoring socket connections and throughput</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.byteman.APMSocketMonitorHelper</code></p>
*/
public class APMSocketMonitorHelper extends APMAgentHelper implements APMSocketMonitorHelperMXBean {
/** The java.net.SocketOutputStream class */
protected static final Class<?> SOCK_OUT_CLASS;
/** The java.net.SocketInputStream class */
protected static final Class<?> SOCK_IN_CLASS;
/** The java.net.SocketOutputStream Stream socket field */
protected static final Field SOCK_OUT_FIELD;
/** The java.net.SocketInputStream Stream socket field */
protected static final Field SOCK_IN_FIELD;
/** The default size of collections holding socket constructs */
public static final int DEFAULT_ACC_SIZE = 128;
/** The default flush period in ms. */
public static final int DEFAULT_FLUSH_PERIOD = 5000;
/** The flush scheduler */
protected static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory(){
private final AtomicInteger serial = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "SocketMonitorFlushThread#" + serial.incrementAndGet());
t.setDaemon(true);
return t;
}
});
/** The current flush schedule handle */
protected static ScheduledFuture<?> flushSchedule = null;
static {
try {
SOCK_OUT_CLASS = Class.forName("java.net.SocketOutputStream");
SOCK_IN_CLASS = Class.forName("java.net.SocketInputStream");
SOCK_OUT_FIELD = SOCK_OUT_CLASS.getDeclaredField("socket");
SOCK_OUT_FIELD.setAccessible(true);
SOCK_IN_FIELD = SOCK_IN_CLASS.getDeclaredField("socket");
SOCK_IN_FIELD.setAccessible(true);
} catch (Exception ex) {
throw new RuntimeException("Failed to load SocketStream classes", ex);
}
}
/** The level listener for this helper's socket tracing level */
protected static final SocketTracingLevelListener levelListener = new SocketTracingLevelListener() {
@Override
public void fromNullTo(SocketTracingLevel newLevel) {
/* No Op */
}
@Override
public void toNull(SocketTracingLevel oldLevel) {
/* No Op */
}
@Override
public void change(SocketTracingLevel oldLevel, SocketTracingLevel newLevel) {
/* No Op */
}
};
/** The socket tracing level */
protected static final SocketTracingLevelWatcher tracingLevel = new SocketTracingLevelWatcher(SocketTracingLevel.CONNECTIONS, levelListener);
/** The array index for socket input (reads) */
public static final int INPUT = 0;
/** The array index for socket output (writes) */
public static final int OUTPUT = 1;
/** The array index for the local socket */
public static final int LOCAL = 0;
/** The array index for the remote socket */
public static final int REMOTE = 0;
/**
* Events to track:
* New Connections:
* ================
* ServerSocket.implAccept (on entry) --> returned socket is an INCOMING (server) connection
*
*
* Socket.connect() --> unless registered as an incoming, this socket is an OUTGOING (client) connection
* CONNECTIONS: Increment counter in serverConnections or clientConnections
* ADDRESS_TRAFFIC: [Check that socket is registered in serverAddressIO or clientAddressIO]
* PORT_TRAFFIC: [Check that socket is registered in serverSocketIO or clientSocketIO]
* ADDRESS_PORT_TRAFFIC: [Check that socket is registered in serverSocketIO or clientSocketIO]
*
* I/O Activity:
* =============
*
* Socket.getInputStream()
* Socket.getOutputStream()
* CONNECTIONS: None
* ADDRESS_TRAFFIC: Add stream and local/remote InetAddresses to outputAddresses or inputAddresses
* PORT_TRAFFIC: Add stream and local/remote InetSocketAddresses to outputPortAddresses or inputPortAddresses
* ADDRESS_PORT_TRAFFIC: Add stream and local/remote InetSocketAddresses to outputPortAddresses or inputPortAddresses
*
* SocketOutputStream.write(...)
* SocketInputStream.read(...)
* CONNECTIONS: None
* ADDRESS_TRAFFIC: Add byte count to serverAddressIO or clientAddressIO
* PORT_TRAFFIC: Add byte count to serverSocketIO or clientSocketIO
* ADDRESS_PORT_TRAFFIC: Add byte count to serverSocketIO or clientSocketIO
*
* Closed Connections:
* ===================
* Socket.close()
* CONNECTIONS: Decrement counter in serverConnections or clientConnections
* ADDRESS_TRAFFIC: [Check that socket is de-registered from serverAddressIO or clientAddressIO & clear outputAddresses or inputAddresses]
* PORT_TRAFFIC: [Check that socket is de-registered from serverSocketIO or clientSocketIO & clear outputPortAddresses or inputPortAddresses]
* ADDRESS_PORT_TRAFFIC: [Check that socket is de-registered from serverSocketIO or clientSocketIO & clear outputPortAddresses or inputPortAddresses]
*
* NOTE: No collection diff between PORT_TRAFFIC and ADDRESS_PORT_TRAFFIC. Flush thread will accumulate address level I/O on flush.
*/
//===============================================================================
// Tracking of socket streams per socket so we can clean them when the socket closes.
//===============================================================================
/** A map of of opaque socket streams keyed by socket output stream */
protected static final NonBlockingHashMap<Socket, Closeable[]> socketStreams = new NonBlockingHashMap<Socket, Closeable[]>(DEFAULT_ACC_SIZE);
//===============================================================================
// Tracking of sockets for socket streams used when using ADDRESS_TRAFFIC
// This saves us from having to reflect out the socket on every call.
//===============================================================================
/** A map of of remote addresses keyed by an opaque socket output stream */
protected static final NonBlockingHashMap<OutputStream, InetAddress> outputAddresses = new NonBlockingHashMap<OutputStream, InetAddress>(DEFAULT_ACC_SIZE);
/** A map of of remote addresses keyed by an opaque socket input stream */
protected static final NonBlockingHashMap<InputStream, InetAddress> inputAddresses = new NonBlockingHashMap<InputStream, InetAddress>(DEFAULT_ACC_SIZE);
//===============================================================================
// Tracking of sockets for socket streams used when using >= PORT_TRAFFIC
// This saves us from having to reflect out the socket on every call.
//===============================================================================
/** A map of of remote addresses keyed by an opaque socket output stream */
protected static final NonBlockingHashMap<OutputStream, InetSocketAddress> outputPortAddresses = new NonBlockingHashMap<OutputStream, InetSocketAddress>(DEFAULT_ACC_SIZE);
/** A map of of remote addresses keyed by an opaque socket input stream */
protected static final NonBlockingHashMap<InputStream, InetSocketAddress> inputPortAddresses = new NonBlockingHashMap<InputStream, InetSocketAddress>(DEFAULT_ACC_SIZE);
//===================================================================
// Accumulators used when we're tracing at at least PORT_TRAFFICconnection
//===================================================================
/** Two member arrays of interval accumulators for IN/OUT traffic marking, keyed by the local server socket the i/o occurs on. */
protected static final NonBlockingHashMap<Socket, ConcurrentLongSlidingWindow[]> serverSocketIO = new NonBlockingHashMap<Socket, ConcurrentLongSlidingWindow[]>(DEFAULT_ACC_SIZE);
/** Two member arrays of interval accumulators for IN/OUT traffic marking, keyed by the local client socket the i/o occurs on. */
protected static final NonBlockingHashMap<Socket, ConcurrentLongSlidingWindow[]> clientSocketIO = new NonBlockingHashMap<Socket, ConcurrentLongSlidingWindow[]>(DEFAULT_ACC_SIZE);
//===================================================================
// Accumulators used when we're tracing at ADDRESS_TRAFFIC
//===================================================================
/** Two member arrays of interval accumulators for IN/OUT traffic marking, keyed by the local server socket the i/o occurs on. */
protected static final NonBlockingHashMap<InetAddress, ConcurrentLongSlidingWindow[]> serverAddressIO = new NonBlockingHashMap<InetAddress, ConcurrentLongSlidingWindow[]>(DEFAULT_ACC_SIZE);
/** Two member arrays of interval accumulators for IN/OUT traffic marking, keyed by the local client socket the i/o occurs on. */
protected static final NonBlockingHashMap<InetAddress, ConcurrentLongSlidingWindow[]> clientAddressIO = new NonBlockingHashMap<InetAddress, ConcurrentLongSlidingWindow[]>(DEFAULT_ACC_SIZE);
//===================================================================
// Accumulators used when we're tracing at CONNECTIONS
//===================================================================
/** Counters for active connections INTO this JVM */
protected static final NonBlockingHashMap<InetAddress, Counter> serverConnections = new NonBlockingHashMap<InetAddress, Counter>(DEFAULT_ACC_SIZE);
/** Counters for active connections INTO this JVM */
protected static final NonBlockingHashMap<InetAddress, Counter> clientConnections = new NonBlockingHashMap<InetAddress, Counter>(DEFAULT_ACC_SIZE);
//===================================================================
// Basic server and client socket tracking combined with possible
// read and write counts.
//===================================================================
/** Counters for active connections INTO this JVM */
protected static final NonBlockingHashMap<Socket, Counter[]> serverSockets = new NonBlockingHashMap<Socket, Counter[]>(DEFAULT_ACC_SIZE);
/** Counters for active connections INTO this JVM */
protected static final NonBlockingHashMap<Socket, Counter[]> clientSockets = new NonBlockingHashMap<Socket, Counter[]>(DEFAULT_ACC_SIZE);
/** A map of bound server sockets and the count of accepted connections */
protected static final NonBlockingHashMap<ServerSocket, Counter> boundServerSockets = new NonBlockingHashMap<ServerSocket, Counter>();
/** The flush runnable */
protected static final Runnable flushProcedure = new Runnable() {
@Override
public void run() {
SystemClock.startTimer();
//SimpleLogger.info("\n\tSocketMonitor Flushing....");
flushData();
cleanClosedSockets();
ElapsedTime et = SystemClock.endTimer();
//SimpleLogger.info("SocketMonitor Flush completed in [", et, "]");
}
};
/**
* Called when the first instance of this helper class is instantiated for an active rule
*/
public static void activated() {
itracer = TracerFactory.getTracer();
if(!JMXHelper.getHeliosMBeanServer().isRegistered(objectName)) {
try {
JMXHelper.getHeliosMBeanServer().registerMBean(new APMSocketMonitorHelper(null), objectName);
} catch (Exception ex) {
SimpleLogger.warn("Failed to register SocketMonitor MBean", ex);
}
}
flushSchedule = scheduler.scheduleWithFixedDelay(flushProcedure, DEFAULT_FLUSH_PERIOD, DEFAULT_FLUSH_PERIOD, TimeUnit.MILLISECONDS);
SimpleLogger.info("\n\t======================\n\tActivated SocketMonitor\n\t======================\n");
}
/**
* Called when the last rule using this helper class is uninstalled
*/
public static void deactivated() {
if(JMXHelper.getHeliosMBeanServer().isRegistered(objectName)) {
try {
JMXHelper.getHeliosMBeanServer().unregisterMBean(objectName);
} catch (Exception ex) {
/* No Op */
}
}
if(flushSchedule!=null) flushSchedule.cancel(false);
SimpleLogger.info("\n\t======================\n\tDeactivated SocketMonitor\n\t======================\n");
}
/** A set of pending closed sockets that will cleared after the next flush */
protected static final Set<Socket> closedSockets = new NonBlockingHashSet<Socket>();
/** A set of pending closed server sockets that will cleared after the next flush */
protected static final Set<ServerSocket> closedServerSockets = new NonBlockingHashSet<ServerSocket>();
//==========================================================================================
// JMX Ops and Attrs for SocketHelper MBean
//==========================================================================================
/** The JMX MBean ObjectName */
protected static final ObjectName objectName = JMXHelper.objectName(APMSocketMonitorHelper.class.getPackage().getName() + ":helper=" + APMSocketMonitorHelper.class.getSimpleName());
/**
* Returns the current SocketTracingLevel name
* @return the current SocketTracingLevel name
*/
@Override
public String getSocketTracingLevel() {
return tracingLevel.get().name();
}
/**
* Sets the SocketTracingLevel
* @param level the SocketTracingLevel name to set
*/
@Override
public void setSocketTracingLevel(String level) {
try {
tracingLevel.set(SocketTracingLevel.valueOfName(level));
} catch (IllegalArgumentException ex) {
throw new RuntimeException("Invalid level. Valid levels are " + Arrays.toString(SocketTracingLevel.values()));
}
}
/**
* Returns the number of open server sockets listening on client requests
* @return the number of open server sockets listening on client requests
*/
@Override
public int getServerSocketCount() {
return boundServerSockets.size();
}
/**
* Returns a map of connection listening ports and the number of accepted connections on each
* @return a map of connection listening ports and the number of accepted connections on each
*/
@Override
public Map<String, Long> getAcceptedConnectionCounts() {
Map<String, Long> map = new HashMap<String, Long>(boundServerSockets.size());
for(Map.Entry<ServerSocket, Counter> entry: boundServerSockets.entrySet()) {
ServerSocket ss = entry.getKey();
map.put(ss.getInetAddress().getHostName() + ":" + ss.getLocalPort(), entry.getValue().get());
}
return map;
}
/**
* Returns the number of client sockets connected from this JVM to remotes
* @return the number of client sockets connected from this JVM to remotes
*/
@Override
public int getClientSocketCount() {
return clientSockets.size();
}
/**
* Creates a new APMSocketMonitorHelper
* @param rule The rule that triggers the helper load
*/
public APMSocketMonitorHelper(Rule rule) {
super(rule);
}
/**
* Starts tracking on a bound server socket.
* Should be triggered by {@link ServerSocket#bind(java.net.SocketAddress)} and {@link ServerSocket#bind(java.net.SocketAddress, int)}.
* @param serverSocket The bound server socket
* @param socketAddres The socket address that the server socket is bound to
*/
public void trackServerSocket(ServerSocket serverSocket, SocketAddress socketAddres) {
boundServerSockets.putIfAbsent(serverSocket, new Counter());
SimpleLogger.info("Bound Server Socket ", render(serverSocket));
}
/**
* Starts tracking on the connection created on a server socket accept.
* Should be triggered by {@link ServerSocket#accept()}.
* @param serverSocket The server socket that accepted a connection
* @param socket The socket created by the accepted connection
*/
public void serverSocketAccept(ServerSocket serverSocket, Socket socket) {
boundServerSockets.putIfAbsent(serverSocket, new Counter());
boundServerSockets.get(serverSocket).increment();
serverSockets.putIfAbsent(socket, new Counter[]{new Counter(), new Counter()});
}
/**
* Adds the closed server socket to the closed pending queue.
* Should be triggered by {@link ServerSocket#close()}.
* @param serverSocket The closed server socket
*/
public void serverSocketClosed(ServerSocket serverSocket) {
closedServerSockets.add(serverSocket);
}
/**
* <p>Starts tracking on the passed socket. Called when:<ol>
* <li><b><code>ServerSocket.implAccept (entry)</code></b>: Passed socket is not connected, but we track it so we know it is a server accepted.</li>
* <li><b><code>Socket.connect (exit)</code></b>: Passed socket is connected so we can get the remote/local addresses. If the socket is not registered as a server socket, it is a client socket.</li>
* </ol></p>
* <p>Note that server accepted sockets will be passed here twice, once on implAccept and once on connect.
* @param socket The socket to track
* NOTE: An important distinction, INCOMING sockets are not connected yet so do not have local/remote addresses.
*/
public void trackSocket(Socket socket) {
if(socket==null) return;
// ==========================================================================
// Tracked Socket Registration
// ==========================================================================
Counter[] counters = new Counter[2];
if(!socket.isConnected()) {
// Add the socket to the serverSocket tracker,
// since only server sockets will come here unconnected
if(serverSockets.putIfAbsent(socket, counters)==null) {
counters[0] = new Counter(); counters[1] = new Counter();
}
} else {
boolean addedClientSocket = false;
// If the socket is not a server socket, it's a client socket
if(!serverSockets.containsKey(socket)) {
if(clientSockets.putIfAbsent(socket, counters)==null) {
counters[0] = new Counter(); counters[1] = new Counter();
addedClientSocket = true;
}
} else {
// This means the socket is a server socket and is now connected
initConnectedServerSocket(socket);
}
// ==========================================================================
// Tracked Socket Address Registration
// ==========================================================================
// if the socket is new and connected, we can register the local and remote addresses.
if(addedClientSocket) {
initConnectedClientSocket(socket);
}
}
}
/**
* Determines if the passed socket is tracked at all
* @param socket The socket to test
* @return true if the socket is tracked, false otherwise
*/
public static boolean isSocketTracked(Socket socket) {
if(socket==null) return false;
return clientSockets.containsKey(socket) || serverSockets.containsKey(socket);
}
/**
* Determines if the passed socket is a tracked client socket
* @param socket The socket to test
* @return true if the socket is a tracked client socket, false otherwise
*/
public static boolean isSocketClientTracked(Socket socket) {
if(socket==null) return false;
return clientSockets.containsKey(socket);
}
/**
* Determines if the passed socket is a tracked server side socket
* @param socket The socket to test
* @return true if the socket is a tracked server side socket, false otherwise
*/
public static boolean isSocketServerTracked(Socket socket) {
if(socket==null) return false;
return serverSockets.containsKey(socket);
}
/**
* Initializes the tracking counters for the passed connected socket
* @param socket The socket to initialize for
*/
protected void initConnectedClientSocket(Socket socket) {
if(socket==null || !socket.isConnected()) return;
final SocketTracingLevel level = tracingLevel.get(); // the address we're connected to
final InetAddress remoteAddress = socket.getInetAddress();
if(level.isActiveConnections()) {
// ==============================================================================
// Increment the counter for clientConnections for the sockets remote address
// ==============================================================================
clientConnections.putIfAbsent(remoteAddress, new Counter());
clientConnections.get(remoteAddress).increment();
} else if(level.isAddressCollecting()) {
socketStreams.putIfAbsent(socket, new Closeable[2]);
// ==============================================================================
// Initialize clientAddressIO with the traffic counters
// ==============================================================================
ConcurrentLongSlidingWindow[] cls = new ConcurrentLongSlidingWindow[2];
if(clientAddressIO.putIfAbsent(remoteAddress, cls)==null) {
cls[0] = new ConcurrentLongSlidingWindow(DEFAULT_ACC_SIZE);
cls[1] = new ConcurrentLongSlidingWindow(DEFAULT_ACC_SIZE);
}
} else if(level.isPortCollecting()) {
socketStreams.putIfAbsent(socket, new Closeable[2]);
// ==============================================================================
// Initialize clientSocketIO with the traffic counters
// ==============================================================================
ConcurrentLongSlidingWindow[] cls = new ConcurrentLongSlidingWindow[2];
if(clientSocketIO.putIfAbsent(socket, cls)==null) {
cls[0] = new ConcurrentLongSlidingWindow(DEFAULT_ACC_SIZE);
cls[1] = new ConcurrentLongSlidingWindow(DEFAULT_ACC_SIZE);
}
}
}
/**
* Initializes the tracking counters for the passed connected socket
* @param socket The socket to initialize for
*/
protected void initConnectedServerSocket(Socket socket) {
if(socket==null || !socket.isConnected()) return;
final SocketTracingLevel level = tracingLevel.get();
final InetAddress remoteAddress = socket.getInetAddress(); // the address connecting to us
if(level.isActiveConnections()) {
// ==============================================================================
// Increment the counter for serverConnections for the sockets remote address
// ==============================================================================
serverConnections.putIfAbsent(remoteAddress, new Counter());
serverConnections.get(remoteAddress).increment();
} else if(level.isAddressCollecting()) {
// ==============================================================================
// Initialize serverAddressIO with the traffic counters
// ==============================================================================
ConcurrentLongSlidingWindow[] cls = new ConcurrentLongSlidingWindow[2];
if(serverAddressIO.putIfAbsent(remoteAddress, cls)==null) {
cls[0] = new ConcurrentLongSlidingWindow(DEFAULT_ACC_SIZE);
cls[1] = new ConcurrentLongSlidingWindow(DEFAULT_ACC_SIZE);
}
} else if(level.isPortCollecting()) {
// ==============================================================================
// Initialize serverSocketIO with the traffic counters
// ==============================================================================
ConcurrentLongSlidingWindow[] cls = new ConcurrentLongSlidingWindow[2];
if(serverSocketIO.putIfAbsent(socket, cls)==null) {
cls[0] = new ConcurrentLongSlidingWindow(DEFAULT_ACC_SIZE);
cls[1] = new ConcurrentLongSlidingWindow(DEFAULT_ACC_SIZE);
}
}
}
/**
* Initializes counters for input stream (read) counters
* Should be bound to {@link Socket#getInputStream()} (exit).
* @param socket The socket for which the input stream is being acquired
* @param is The socket input stream
*/
public void trackInputStream(Socket socket, InputStream is) {
if(is==null) return;
final SocketTracingLevel level = tracingLevel.get();
if(level.isActiveConnections()) {
// disable rule ?
return;
}
if(!isSocketTracked(socket)) {
trackSocket(socket);
}
// Store the association of the socketinput stream to the socket,
// so we can clear the streams references after the socket closes.
socketStreams.get(socket)[INPUT] = is;
if(level.isAddressCollecting()) {
// Store the association of the input stream to the remote address
inputAddresses.put(is, socket.getInetAddress());
} else if(level.isPortCollecting()) {
// Store the association of the input stream to the remote port
inputPortAddresses.put(is, (InetSocketAddress)socket.getRemoteSocketAddress());
}
}
/**
* Initializes counters for output stream (write) counters
* Should be bound to {@link Socket#getOutputStream()} (exit).
* @param socket The socket for which the output stream is being acquired
* @param os The socket output stream
*
*/
public void trackOutputStream(Socket socket, OutputStream os) {
if(os==null) return;
final SocketTracingLevel level = tracingLevel.get();
if(level.isActiveConnections()) {
// disable rule ?
return;
}
if(!isSocketTracked(socket)) {
trackSocket(socket);
}
// Store the association of the socketoutput stream to the socket,
// so we can clear the streams references after the socket closes.
socketStreams.get(socket)[OUTPUT] = os;
if(level.isAddressCollecting()) {
// Store the association of the output stream to the remote address
outputAddresses.put(os, socket.getInetAddress());
} else if(level.isPortCollecting()) {
// Store the association of the output stream to the remote port
outputPortAddresses.put(os, (InetSocketAddress)socket.getRemoteSocketAddress());
}
}
/**
* Records the number of bytes written to the passed socket
* @param socket the socket written to
* @param bytes The number of bytes written
*/
public void socketWrite(Socket socket, int bytes) {
if(socket==null) return;
final SocketTracingLevel level = tracingLevel.get();
if(level.isActiveConnections()) {
// disable rule ?
return;
}
if(!isSocketTracked(socket)) {
trackSocket(socket);
}
if(level.isAddressCollecting()) {
// Add the byte count to the sliding window associated to the socket's remote address
if(isSocketServerTracked(socket)) {
serverAddressIO.get(socket)[INPUT].insert(bytes);
} else {
clientAddressIO.get(socket)[INPUT].insert(bytes);
}
} else if(level.isPortCollecting()) {
// Add the byte count to the sliding window associated to the socket's remote port
if(isSocketServerTracked(socket)) {
serverSocketIO.get(socket)[INPUT].insert(bytes);
} else {
clientSocketIO.get(socket)[INPUT].insert(bytes);
}
}
}
/**
* Records the number of bytes read from the passed socket
* @param socket the socket read from
* @param bytes The number of bytes read
*/
public void socketRead(Socket socket, int bytes) {
if(socket==null) return;
final SocketTracingLevel level = tracingLevel.get();
if(level.isActiveConnections()) {
// disable rule ?
return;
}
if(!isSocketTracked(socket)) {
trackSocket(socket);
}
if(level.isAddressCollecting()) {
// Add the byte count to the sliding window associated to the socket's remote address
if(isSocketServerTracked(socket)) {
serverAddressIO.get(socket)[OUTPUT].insert(bytes);
} else {
clientAddressIO.get(socket)[OUTPUT].insert(bytes);
}
} else if(level.isPortCollecting()) {
// Add the byte count to the sliding window associated to the socket's remote port
if(isSocketServerTracked(socket)) {
serverSocketIO.get(socket)[OUTPUT].insert(bytes);
} else {
clientSocketIO.get(socket)[OUTPUT].insert(bytes);
}
}
}
/**
* Fired when a socket closes.
* Should be bound to {@link Socket#close()}.
* @param socket The closing socket
*/
public void socketClosed(Socket socket) {
if(socket==null) return;
closedSockets.add(socket);
SimpleLogger.info("Closing ", render(socket));
}
/**
* Renders the details of a socket in the returned string
* @param socket The socket to render
* @return the details of the socket as a string
*/
public static String render(Socket socket) {
if(socket==null) return "NULL";
StringBuilder b = new StringBuilder("\nSocket [");
b.append("\n\tLocalPort:").append(socket.getLocalPort());
b.append("\n\tLocalAddress:").append(socket.getLocalAddress());
b.append("\n\tLocalSocketAddress:").append(socket.getLocalSocketAddress());
b.append("\n\tRemotePort:").append(socket.getPort());
b.append("\n\tRemoteAddress:").append(socket.getInetAddress());
b.append("\n\tRemoteSocketAddress:").append(socket.getRemoteSocketAddress());
b.append("\n\tChannel:").append(socket.getChannel());
b.append("\n\tHashCode:").append(socket.hashCode());
b.append("\n]");
return b.toString();
}
/**
* Renders the details of a server socket in the returned string
* @param socket The server socket to render
* @return the details of the server socket as a string
*/
public static String render(ServerSocket socket) {
if(socket==null) return "NULL";
StringBuilder b = new StringBuilder("\nSocket [");
b.append("\n\tLocalPort:").append(socket.getLocalPort());
b.append("\n\tLocalAddress:").append(socket.getInetAddress());
b.append("\n\tLocalSocketAddress:").append(socket.getLocalSocketAddress());
b.append("\n\tChannel:").append(socket.getChannel());
b.append("\n\tHashCode:").append(socket.hashCode());
b.append("\n]");
return b.toString();
}
//===================================================================
// Flush and Clean Procedures
//===================================================================
/**
* Flushes the data to the itracer
*/
protected static void flushData() {
//===============================================================
// Trace the number of client and server connections
//===============================================================
for(Socket socket: serverSockets.keySet()) {
}
}
/**
* Cleans up all closed sockets
*/
protected static void cleanClosedSockets() {
for(Iterator<ServerSocket> closedServerIterator = closedServerSockets.iterator(); closedServerIterator.hasNext();) {
// clean up connected sockets here ?
closedServerIterator.remove();
}
//boundServerSockets
for(Iterator<Socket> closedIterator = closedSockets.iterator(); closedIterator.hasNext();) {
Socket sock = closedIterator.next();
if(isSocketServerTracked(sock)) {
serverAddressIO.remove(sock.getRemoteSocketAddress());
serverSocketIO.remove(sock.getRemoteSocketAddress());
serverSockets.remove(sock);
} else {
clientAddressIO.remove(sock.getRemoteSocketAddress());
clientSocketIO.remove(sock.getRemoteSocketAddress());
clientSockets.remove(sock);
}
Closeable[] streams = socketStreams.get(sock);
if(streams!=null) {
if(streams[INPUT]!=null) {
inputAddresses.remove(streams[INPUT]);
inputPortAddresses.remove(streams[INPUT]);
}
if(streams[OUTPUT]!=null) {
outputAddresses.remove(streams[OUTPUT]);
outputPortAddresses.remove(streams[OUTPUT]);
}
}
closedIterator.remove();
}
}
}
///**
//public void traceSocketIO(Object[] args) {
// Socket as = (Socket)getFieldValue(args[0], "socket");
// InetSocketAddress localSocket = (InetSocketAddress)as.getLocalSocketAddress();
// InetSocketAddress remoteSocket = (InetSocketAddress)as.getRemoteSocketAddress();
// int bytesMoved = 0;
// if(args.length==4) {
// bytesMoved = (Integer)args[3];
// } else {
// if(args[1].getClass()==byte[].class) {
// bytesMoved = ((byte[])args[1]).length;
// } else {
// bytesMoved = 1;
// }
// }
// //SimpleLogger.info("\n\tSocket Write [", bytesWritten, "] bytes\n\t Socket Local:", localSocket.getAddress().getHostAddress(), ":", localSocket.getPort(), "\n\t Socket Remote:", remoteSocket.getAddress().getHostAddress(), ":", remoteSocket.getPort());
//}
//* Called when the first instance of this helper class is instantiated for an active rule
//*/
//public static void activated() {
// APMAgentHelper.activated();
// Class<?>[] publified = null;
// try {
// publified = ClassPublifier.getInstance().publify(true, Class.forName("java.net.SocketOutputStream"), Class.forName("java.net.SocketInputStream"));
// SimpleLogger.info("Publified SocketMonitor Classes:" + publified.length);
// StringBuilder b = new StringBuilder("\nPublified SocketMonitor Classes:");
// for(Class<?> clazz: publifiedClasses) {
// b.append("\n\t[").append(clazz.getName()).append("] Public:").append(Modifier.isPublic(clazz.getModifiers()));
// }
// b.append("\n");
// SimpleLogger.info(b);
// Collections.addAll(publifiedClasses, publified);
// } catch (Exception ex) {
// SimpleLogger.error("Failed to publify socket streams", ex);
// throw new RuntimeException("Failed to publify socket streams", ex);
// }
//}
//
///**
//* Called when the last rule using this helper class is uninstalled
//*/
//public static void deactivated() {
// APMAgentHelper.deactivated();
// Class<?>[] reverted = null;
// try {
// reverted = ClassPublifier.getInstance().revert(true, Class.forName("java.net.SocketOutputStream"), Class.forName("java.net.SocketInputStream"));
// SimpleLogger.info("Reverted SocketMonitor Classes:" + reverted.length);
// for(Class<?> clazz: reverted) {
// publifiedClasses.remove(clazz);
// }
// if(!publifiedClasses.isEmpty()) {
// StringBuilder b = new StringBuilder("\nUnexpected SocketMonitor Publified Classes after Helper Deactivation:");
// for(Class<?> clazz: publifiedClasses) {
// b.append("\n\t[").append(clazz.getName()).append("]-->").append(clazz.getClassLoader());
// }
// b.append("\n");
// SimpleLogger.warn(b);
// }
// } catch (Exception ex) {
// SimpleLogger.error("Failed to revert publified socket streams", ex);
// throw new RuntimeException("Failed to revert publify socket streams", ex);
// }
//}