/**
* 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;
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.APMAgentHelper;
import org.helios.apmrouter.byteman.APMSocketMonitorHelper;
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.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <p>Title: SocketMonitor</p>
* <p>Description: Byteman socket monitor</p>
* <p><a href="http://apmrouter.blogspot.com/2012/12/socket-monitoring-instrumentation.html">Docs.</a></p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.byteman.sockets.SocketMonitor</code></p>
*/
public class SocketMonitor extends APMAgentHelper {
/** 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 configured accumulator size */
protected static int accumulatorSize = DEFAULT_ACC_SIZE;
/** The configured flush period in ms. */
protected static long flushPeriod = DEFAULT_FLUSH_PERIOD;
/** The configured SocketTracingLevel */
protected static SocketTracingLevel tracingLevel = SocketTracingLevel.CONNECTIONS;
/** 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;
/** 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;
//==========================================================================================
// JMX Ops and Attrs for SocketHelper MBean
//==========================================================================================
/** The JMX MBean ObjectName */
protected static final ObjectName objectName = JMXHelper.objectName(APMSocketMonitorHelper.class.getPackage().getName() + ":helper=" + SocketMonitor.class.getSimpleName());
/**
* <p>Title: SocketMonitorJMX</p>
* <p>Description: JMX management for the SocketMonitor helper.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.byteman.sockets.SocketMonitor.SocketMonitorJMX</code></p>
*/
public static class SocketMonitorJMX implements SocketMonitorMXBean {
/**
* {@inheritDoc}
* @see org.helios.apmrouter.byteman.sockets.SocketMonitorMXBean#getSocketTracingLevel()
*/
@Override
public String getSocketTracingLevel() {
return tracingLevel.name();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.byteman.sockets.SocketMonitorMXBean#setSocketTracingLevel(java.lang.String)
*/
@Override
public void setSocketTracingLevel(String level) {
tracingLevel = SocketTracingLevel.valueOfName(level);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.byteman.sockets.SocketMonitorMXBean#getServerSocketCount()
*/
@Override
public int getServerSocketCount() {
return ServerSocketTracker.serverSideSockets.size();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.byteman.sockets.SocketMonitorMXBean#getAcceptedConnectionCounts()
*/
@Override
public Map<String, Long> getAcceptedConnectionCountsByAddress() {
return ServerSocketTracker.getAcceptedConnectionCountsByAddress();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.byteman.sockets.SocketMonitorMXBean#getClientSocketCount()
*/
@Override
public int getClientSocketCount() {
return SocketTracker.CTRACKERS.size();
}
}
/** The flush runnable */
protected static final Runnable flushProcedure = new Runnable() {
@Override
public void run() {
SystemClock.startTimer();
ServerSocketTracker.flush();
SocketTracker.flush();
//SimpleLogger.info("\n\tSocketMonitor Flushing....");
// flushData();
// cleanClosedSockets();
ElapsedTime et = SystemClock.endTimer();
//SimpleLogger.info("SocketMonitor Flush completed in [", et, "]");
}
};
//================================================================================
// SocketMonitor configuration methods, called by the rule script execution
//================================================================================
/**
* Sets the size of the collections created to track socket activity.
* Defaults to 128.
* @param accSize The size of the collections
*/
public void setAccumulatorSize(int accSize) {
accumulatorSize = accSize;
}
/**
* Sets the flush period in ms.
* Defaults to 5000.
* @param flushPeriod The flush period in ms.
*/
public void setFlushPeriod(long flushPeriod) {
SocketMonitor.flushPeriod = flushPeriod;
}
/**
* Sets the socket tracing level.
* Defaults to CONNECTIONS.
* @param levelName The name of the level to set to, i.e. on the enums from {@link SocketTracingLevel}.
*/
public void setTracingLevel(String levelName) {
SocketTracingLevel level = null;
try {
level = SocketTracingLevel.valueOfName(levelName);
tracingLevel = level;
} catch (IllegalArgumentException ex) {
SimpleLogger.warn("Invalid SocketTracingLevel Name [", levelName, "]. Reverting to default CONNECTIONS");
tracingLevel = SocketTracingLevel.CONNECTIONS;
}
}
//================================================================================
/**
* 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 SocketMonitorJMX(), objectName);
} catch (Exception ex) {
SimpleLogger.warn("Failed to register SocketMonitor MBean", ex);
}
}
initCollections();
if(flushSchedule!=null) {
flushSchedule.cancel(true);
}
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);
flushSchedule = null;
}
SimpleLogger.info("\n\t======================\n\tDeactivated SocketMonitor\n\t======================\n");
}
/**
* Initializes the tracking collections
*/
protected static void initCollections() {
//serverSockets = new NonBlockingHashMap<ServerSocket, Counter>(accumulatorSize);
}
//==============================================================================
// Server Socket Tracking
//==============================================================================
// /** A map of tracked server sockets with a counter tracking accept counts for each */
// protected static NonBlockingHashMap<ServerSocket, Counter> serverSockets = null;
/**
* Creates a new SocketMonitor
* @param rule The rule this helper is being created for
*/
public SocketMonitor(Rule rule) {
super(rule);
}
/**
* Called when a new {@link ServerSocket} is bound and starts listening.
* Triggered by {@link ServerSocket#bind(java.net.SocketAddress)} or {@link ServerSocket#bind(java.net.SocketAddress, int)}.
* Note that server sockets bound before this rule is installed will not be tracked.
* However, once the rule is installed, invocations of {@link ServerSocket#accept()} will start tracking.
* @param serverSocket The bound server socket
*/
public void serverSocketBind(ServerSocket serverSocket) {
ServerSocketTracker.getInstance(serverSocket);
}
/**
* Starts tracking on the socket created by the {@link ServerSocket#accept()} operation.
* Triggered by {@link ServerSocket#accept()}.
* @param serverSocket The tracked {@link ServerSocket}
* @param socket The connected {@link Socket}
*/
public void serverSocketAccept(ServerSocket serverSocket, Socket socket) {
ServerSocketTracker sst = ServerSocketTracker.getInstance(serverSocket).increment();
if(!socket.isConnected()) {
sst.trackAccept(socket);
} else {
sst.trackConnection(socket);
}
}
/**
* Queues the passed server socket for close and cleanup.
* Triggered by {@link ServerSocket#close()}.
* @param serverSocket The server socket to close
*/
public void serverSocketClose(ServerSocket serverSocket) {
ServerSocketTracker.getInstance(serverSocket).closeServerSocket(serverSocket);
}
/**
* Starts tracking on the passed socket.
* At this interception, there's no way to differentiate between a client socket and a server socket.
* The method {@link SocketTracker#getInstance(Socket)} will determine which it is and start appropriate tracking on the socket.
* Triggered by {@link Socket#connect(SocketAddress)} and {@link Socket#connect(SocketAddress, int)}.
* @param socket The connected socket.
*/
public void socketConnect(Socket socket) {
if(!ServerSocketTracker.isTrackedServerSideSocket(socket)) {
SocketTracker.getInstance(socket);
}
}
/**
* Queues the passed socket for close and cleanup.
* Triggered by {@link Socket#close()}.
* @param socket The closed socket.
*/
public void socketClose(Socket socket) {
if(ServerSocketTracker.isTrackedServerSideSocket(socket)) {
ServerSocketTracker.closeServerSideSocket(socket);
} else {
SocketTracker st = SocketTracker.getInstance(socket);
st.closeSocket();
}
}
/**
* Updates the socket tracker with the socket's opened input stream.
* Triggered by {@link Socket#getInputStream()}.
* @param socket The socket
* @param is The socket's opened input stream
*/
public void clientSocketInput(Socket socket, InputStream is) {
if(tracingLevel.ordinal()>SocketTracingLevel.CONNECTIONS.ordinal()) {
SocketTracker.getInstance(socket).setInputStream(is);
} // else ---> remove rule ?
}
/**
* Updates the socket tracker with the socket's opened output stream.
* Triggered by {@link Socket#getOutputStream()}.
* @param socket The socket
* @param os The socket's opened output stream
*/
public void clientSocketOutput(Socket socket, OutputStream os) {
if(tracingLevel.ordinal()>SocketTracingLevel.CONNECTIONS.ordinal()) {
SocketTracker.getInstance(socket).setOutputStream(os);
} // else ---> remove rule ?
}
/**
* <p>Title: ServerSocketTracker</p>
* <p>Description: A container class to track the accept count and created sockets for a ServerSocket.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.byteman.sockets.SocketMonitor.ServerSocketTracker</code></p>
*/
protected static class ServerSocketTracker {
/** A map of the created ServerSocketTracker instances keyed by the tracked {@ServerSocket} */
final static NonBlockingHashMap<ServerSocket, ServerSocketTracker> TRACKERS = new NonBlockingHashMap<ServerSocket, ServerSocketTracker>(accumulatorSize);
/** A map of the created ServerSocketTracker instances keyed by the tracked {@ServerSocket}'s local socket address */
final static NonBlockingHashMap<SocketAddress, ServerSocketTracker> TRACKERS_BY_BIND = new NonBlockingHashMap<SocketAddress, ServerSocketTracker>(accumulatorSize);
/** A set of pending server socket closes */
final static NonBlockingHashSet<ServerSocket> pendingCloses = new NonBlockingHashSet<ServerSocket>();
/** A set of accepted sockets (server side sockets) to distinguish between client and server side socket instances */
final static NonBlockingHashSet<Socket> serverSideSockets = new NonBlockingHashSet<Socket>();
/** The tracked {@link ServerSocket} */
final ServerSocket serverSocket;
/** The total cummulative number of accepts performed by the tracked {@link ServerSocket}. */
final Counter acceptCount = new Counter();
/** The number of curently connected clients to the tracked {@link ServerSocket} */
final Counter connectedClients = new Counter();
/** A map of connected sockets accepted by the tracked {@link ServerSocket} keyed by the remote socket address of the connector */
final NonBlockingHashMap<InetSocketAddress, SocketTracker> connectorsBySocketAddress = new NonBlockingHashMap<InetSocketAddress, SocketTracker>(accumulatorSize);
/** A map of sets of connected sockets accepted by the tracked {@link ServerSocket} keyed by the remote address of the connector */
final NonBlockingHashMap<InetAddress, NonBlockingHashSet<SocketTracker>> connectorsByAddress = new NonBlockingHashMap<InetAddress, NonBlockingHashSet<SocketTracker>>(accumulatorSize);
/**
* Interval flush procedure for server socket tracking
*/
static void flush() {
for(ServerSocket ss: pendingCloses) {
ServerSocketTracker sst = TRACKERS.remove(ss);
//itracer.traceGauge(0, "Bound", "SocketMonitor", "ServerSockets", sst.serverSocket.getInetAddress().getHostAddress(), "" + sst.serverSocket.getLocalPort());
TRACKERS_BY_BIND.remove(ss.getLocalSocketAddress());
sst.connectorsByAddress.clear();
sst.connectorsBySocketAddress.clear();
}
for(ServerSocketTracker sst : TRACKERS.values()) {
sst._flush();
}
}
private void _flush() {
itracer.traceGauge(1, "Bound", "SocketMonitor", "ServerSockets", serverSocket.getInetAddress().getHostAddress(), "" + serverSocket.getLocalPort());
itracer.traceDeltaCounter(acceptCount.get(), "Accepts", "SocketMonitor", "ServerSockets", serverSocket.getInetAddress().getHostAddress(), "" + serverSocket.getLocalPort());
itracer.traceGauge(connectedClients.get(), "ConnectedClients", "SocketMonitor", "ServerSockets", serverSocket.getInetAddress().getHostAddress(), "" + serverSocket.getLocalPort());
// acceptCount.increment();
// connectedClients.increment();
}
/**
* Returns a map of the total cummulative accept counts by address
* @return a map of the total cummulative accept counts by address
*/
public static Map<String, Long> getAcceptedConnectionCountsByAddress() {
Map<String, Long> map = new HashMap<String, Long>(TRACKERS.size());
for(Map.Entry<ServerSocket, ServerSocketTracker> entry: TRACKERS.entrySet()) {
String address = entry.getKey().getInetAddress().getHostName() + ":" + entry.getKey().getLocalPort();
if(!map.containsKey(address)) map.put(address, 0L);
map.put(address, map.get(address)+entry.getValue().acceptCount.get());
}
return map;
}
/**
* Returns the {@link ServerSocketTracker} for the passed {@ServerSocket}
* @param serverSocket The {@ServerSocket}
* @return the {@link ServerSocketTracker}
*/
public static ServerSocketTracker getInstance(ServerSocket serverSocket) {
ServerSocketTracker sst = TRACKERS.get(serverSocket);
if(sst==null) {
synchronized(TRACKERS) {
sst = TRACKERS.get(serverSocket);
if(sst==null) {
sst = new ServerSocketTracker(serverSocket);
TRACKERS.put(serverSocket, sst);
}
}
}
return sst;
}
/**
* Returns the server socket tracker for the server socket bound to the passed address.
* @param boundAddress The address the server socket is bound to
* @return the server socket tracker, or null if one is not found.
*/
public static ServerSocketTracker getInstance(SocketAddress boundAddress) {
return TRACKERS_BY_BIND.get(boundAddress);
}
/**
* Indicates if the passed socket is registered as a server side socket
* @param socket The socket to test
* @return true if the socket is registered as a server side socket,
* false if the socket is null or not registered as a server side socket
*/
public static boolean isTrackedServerSideSocket(Socket socket) {
if(socket==null) return false;
return serverSideSockets.contains(socket);
}
/**
* Handles a closed server connection to a bound server side socket
* @param socket the closed socket
*/
public static void closeServerSideSocket(Socket socket) {
ServerSocketTracker sst = getInstance(socket.getLocalSocketAddress());
sst.closeServerSide(socket);
}
/**
* Creates a new ServerSocketTracker
* @param serverSocket The tracked {@link ServerSocket}
*/
private ServerSocketTracker(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
TRACKERS_BY_BIND.put(this.serverSocket.getLocalSocketAddress(), this);
}
/**
* Closes and cleans up references to a server side socket
* @param socket the closed socket
*/
protected void closeServerSide(Socket socket) {
decrement();
serverSideSockets.remove(socket);
connectorsBySocketAddress.remove(socket.getRemoteSocketAddress());
Set<SocketTracker> socketTrackers = connectorsByAddress.get(socket.getInetAddress());
SocketTracker socketTracker = SocketTracker.getServerInstance(socket);
if(socketTrackers!=null) {
socketTrackers.remove(socketTracker);
}
socketTracker.closeSocket();
}
/**
* Increments the accept count for the tracked {@link ServerSocket}
* @return this tracker
*/
public ServerSocketTracker increment() {
acceptCount.increment();
connectedClients.increment();
return this;
}
/**
* Decrements the connected client count for the tracked {@link ServerSocket}
* @return this tracker
*/
public ServerSocketTracker decrement() {
connectedClients.decrement();
return this;
}
/**
* Registers the passed server socket for close and cleanup in the next flush
* @param serverSocket The server socket to be closed
* @return this tracker
*/
public ServerSocketTracker closeServerSocket(ServerSocket serverSocket) {
if(serverSocket!=null) {
pendingCloses.add(serverSocket);
// terminate and remove all associated sockets
}
return this;
}
/**
* Adds a socket that created during a {@link ServerSocket#accept()} but before the create socket's connection.
* This allows the created socket to be registered as a server side socket before the {@link Socket#connect(SocketAddress)} callback,
* so we know it is a server side socket.
* @param socket The connecting socket
* @return this tracker
*/
public ServerSocketTracker trackAccept(Socket socket) {
if(socket==null) return this;
serverSideSockets.add(socket);
return this;
}
/**
* Adds a socket that connected to this server socket for tracking
* @param socket The connecting socket
* @return this tracker
*/
public ServerSocketTracker trackConnection(Socket socket) {
if(socket==null) return this;
serverSideSockets.add(socket);
if(tracingLevel.isAddressCollecting()) {
NonBlockingHashSet<SocketTracker> socketTrackers = connectorsByAddress.get(socket.getInetAddress());
if(socketTrackers==null) {
socketTrackers = new NonBlockingHashSet<SocketTracker>();
connectorsByAddress.put(socket.getInetAddress(), socketTrackers);
}
socketTrackers.add(SocketTracker.getServerInstance(socket));
} else if(tracingLevel.isPortCollecting()) {
connectorsBySocketAddress.putIfAbsent((InetSocketAddress)socket.getRemoteSocketAddress(), SocketTracker.getServerInstance(socket));
}
return this;
}
}
/**
* <p>Title: SocketTracker</p>
* <p>Description: A container class to track a socket connection and its traffic.
* SocketTracker maintains three types of collections for indexing references and counters:<ul>
* <li>Reference collections for tracking instances related to tracked sockets</li>
* <li>Accumulator references for interval accumulation of tracked socket activity</li>
* <li>Counter references for counting events associated to tracked sockets</li>
* </ul></p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.byteman.sockets.SocketMonitor.SocketTracker</code></p>
*/
protected static class SocketTracker {
/** A map of the created server side SocketTracker instances keyed by the tracked {@Socket} */
final static NonBlockingHashMap<Socket, SocketTracker> STRACKERS = new NonBlockingHashMap<Socket, SocketTracker>(accumulatorSize);
/** A map of the created client side SocketTracker instances keyed by the tracked {@Socket} */
final static NonBlockingHashMap<Socket, SocketTracker> CTRACKERS = new NonBlockingHashMap<Socket, SocketTracker>(accumulatorSize);
/** A map of registered SocketTracker instances keyed by the tracked {@Socket}'s input stream */
final static NonBlockingHashMap<InputStream, SocketTracker> INSTREAMS = new NonBlockingHashMap<InputStream, SocketTracker>(accumulatorSize);
/** A map of registered SocketTracker instances keyed by the tracked {@Socket}'s output stream */
final static NonBlockingHashMap<OutputStream, SocketTracker> OUTSTREAMS = new NonBlockingHashMap<OutputStream, SocketTracker>(accumulatorSize);
/** A map of client side connection counters keyed by the tracked {@Socket}'s remote socket address */
final static NonBlockingHashMap<InetSocketAddress, Counter> CLIENT_CONNECTION_COUNTERS = new NonBlockingHashMap<InetSocketAddress, Counter>(accumulatorSize);
/** The tracked {@link Socket} */
final Socket socket;
/** The socket's local address */
final InetAddress localAddress;
/** The socket's remote address */
final InetAddress remoteAddress;
/** The socket's local socket address */
final InetSocketAddress localSocketAddress;
/** The socket's remote address */
final InetSocketAddress remoteSocketAddress;
/** The socket's local port */
final int localPort;
/** The socket's remote port */
final int remotePort;
/** The socket's input stream */
private InputStream is = null;
/** The socket's output stream */
private OutputStream os = null;
/** The socket's writes */
private ConcurrentLongSlidingWindow socketWrites;
/** The socket's reads */
private ConcurrentLongSlidingWindow socketReads;
/**
* Interval flush procedure for socket tracking
*/
static void flush() {
}
/**
* Closes a socket
*/
public void closeSocket() {
Counter counter = CLIENT_CONNECTION_COUNTERS.get(remoteSocketAddress);
if(counter!=null) counter.decrement();
}
/**
* Sets and indexes the input stream of the tracked socket
* @param is the input stream of the tracked socket
*/
public void setInputStream(InputStream is) {
this.is = is;
INSTREAMS.putIfAbsent(is, this);
socketReads = new ConcurrentLongSlidingWindow(accumulatorSize);
}
/**
* Sets and indexes the output stream of the tracked socket
* @param os the output stream of the tracked socket
*/
public void setOutputStream(OutputStream os) {
this.os = os;
OUTSTREAMS.putIfAbsent(os, this);
socketWrites = new ConcurrentLongSlidingWindow(accumulatorSize);
}
/**
* Records a socket write
* @param bytes the number of bytes written
*/
public void recordWrite(int bytes) {
socketWrites.insert(bytes);
}
/**
* Records a socket read
* @param bytes the number of bytes read
*/
public void recordRead(int bytes) {
socketReads.insert(bytes);
}
/**
* Returns the socket tracker map
* @param serverSide true for server side sockets, false for client side
* @return a socket tracker map
*/
private static NonBlockingHashMap<Socket, SocketTracker> tracker(boolean serverSide) {
return serverSide ? STRACKERS : CTRACKERS;
}
/**
* Returns the {@link SocketTracker} for the passed server side {@Socket}
* @param socket The {@socket}
* @return the {@link SocketTracker}
*/
public static SocketTracker getServerInstance(Socket socket) {
return getInstance(true, socket);
}
/**
* Returns the {@link SocketTracker} for the passed client side {@Socket}
* @param socket The {@socket}
* @return the {@link SocketTracker}
*/
public static SocketTracker getClientInstance(Socket socket) {
return getInstance(false, socket);
}
/**
* Returns the {@link SocketTracker} for the passed unknown side {@Socket}.
* If the socket is not already registered, it will be registered.
* To determine the type, the local socket address will be bounced up against
* the ServerSocketTracker registry to try and find a match. If a match is found,
* the passed socket is a <b>server</b> socket. Otherwise it is a <b>client</b> socket.
* @param socket The socket to get the tracker for
* @return the socket tracker.
*/
public static SocketTracker getInstance(Socket socket) {
SocketTracker tracker = CTRACKERS.get(socket);
if(tracker==null) tracker = STRACKERS.get(socket);
if(tracker==null) tracker = getInstance(ServerSocketTracker.getInstance(socket.getLocalSocketAddress())!=null, socket);
return tracker;
}
/**
* Returns the {@link SocketTracker} for the passed stream's socket
* @param is The input stream
* @return the {@link SocketTracker} for the passed stream
*/
public static SocketTracker getInstance(InputStream is) {
SocketTracker tracker = INSTREAMS.get(is);
if(tracker==null) {
tracker = getInstance(getStreamSocket(is));
}
return tracker;
}
/**
* Returns the {@link SocketTracker} for the passed stream's socket
* @param os The output stream
* @return the {@link SocketTracker} for the passed stream
*/
public static SocketTracker getInstance(OutputStream os) {
SocketTracker tracker = INSTREAMS.get(os);
if(tracker==null) {
tracker = getInstance(getStreamSocket(os));
}
return tracker;
}
/**
* Returns the {@link SocketTracker} for the passed {@Socket}
* @param serverSide true for server side sockets, false for client side
* @param socket The {@socket}
* @return the {@link SocketTracker}
*/
public static SocketTracker getInstance(boolean serverSide, Socket socket) {
final NonBlockingHashMap<Socket, SocketTracker> TRACKERS = tracker(serverSide);
SocketTracker st = TRACKERS.get(socket);
if(st==null) {
synchronized(tracker(serverSide)) {
st = TRACKERS.get(socket);
if(st==null) {
st = new SocketTracker(socket);
TRACKERS.put(socket, st);
}
}
}
return st;
}
/**
* Creates a new SocketTracker for a <i>connected</i> socket.
* @param socket The tracked {@link Socket}
*/
private SocketTracker(Socket socket) {
this.socket = socket;
localAddress = socket.getLocalAddress();
localPort = socket.getLocalPort();
localSocketAddress = (InetSocketAddress)socket.getLocalSocketAddress();
remoteAddress = socket.getInetAddress();
remotePort = socket.getPort();
remoteSocketAddress = (InetSocketAddress)socket.getRemoteSocketAddress();
if(!ServerSocketTracker.isTrackedServerSideSocket(socket)) {
Counter counter = CLIENT_CONNECTION_COUNTERS.get(remoteSocketAddress);
if(counter==null) {
synchronized(CLIENT_CONNECTION_COUNTERS) {
counter = CLIENT_CONNECTION_COUNTERS.get(remoteSocketAddress);
if(counter==null) {
counter = new Counter();
CLIENT_CONNECTION_COUNTERS.put(remoteSocketAddress, counter);
}
}
}
counter.increment();
}
}
/**
* Reflects out the socket from a socket input stream
* @param is The socket input stream to get the socket for
* @return the socket
*/
public static Socket getStreamSocket(InputStream is) {
try {
return (Socket)SOCK_IN_FIELD.get(is);
} catch (Exception ex) {
throw new RuntimeException("Failed to get socket from SocketInputStream", ex);
}
}
/**
* Reflects out the socket from a socket output stream
* @param os The socket output stream to get the socket for
* @return the socket
*/
public static Socket getStreamSocket(OutputStream os) {
try {
return (Socket)SOCK_OUT_FIELD.get(os);
} catch (Exception ex) {
throw new RuntimeException("Failed to get socket from SocketOutputStream", ex);
}
}
/**
* Returns this socket's input stream
* @return this socket's input stream or null if it has not been opened
*/
public InputStream getInputStream() {
return is;
}
/**
* Returns this socket's output stream
* @return this socket's output stream or null if it has not been opened
*/
public OutputStream getOutputStream() {
return os;
}
}
/** 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;
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);
}
}
}