/** * 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.NonBlockingHashMap; import org.helios.apmrouter.byteman.sockets.impl.ISocketImpl; import org.helios.apmrouter.collections.ConcurrentLongSlidingWindow; import org.helios.apmrouter.nativex.APMSigar; import org.helios.apmrouter.nativex.TCPSocketState; import org.helios.apmrouter.util.SimpleLogger; import org.hyperic.sigar.NetStat; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * <p>Title: ServerConnection</p> * <p>Description: A container representing an incoming connection to a tracked server socket.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.byteman.sockets.ServerConnection</code></p> */ public class ServerConnection implements NetStatConst { /** The time the connection was established */ protected final long connectTime = System.currentTimeMillis(); /** The server socket interface */ protected final ISocketImpl serverSocket; /** The server socket reference key */ protected final String key; /** A netstat instance for capturing the state of the remote side of this connection */ protected NetStat remoteNetStat; /** A netstat instance for capturing the state of the local (listener server socket) side of this connection */ protected NetStat localNetStat; /** Metric accumulator for the local side */ protected final NonBlockingHashMap<String, ConcurrentLongSlidingWindow> localStats = newNetStatMap(); /** Metric accumulator for the remote side */ protected final NonBlockingHashMap<String, ConcurrentLongSlidingWindow> remoteStats = newNetStatMap(); /** The socket state of the local side as a {@link TCPSocketState} bit mask */ protected final AtomicInteger localSocketState = new AtomicInteger(0); /** The socket state of the remote side as a {@link TCPSocketState} bit mask */ protected final AtomicInteger remoteSocketState = new AtomicInteger(0); /** A sigar reference to acquire netstats */ protected static final APMSigar sigar; /** A map of server connections keyed by the compond of the local and remote address:port */ protected static final NonBlockingHashMap<String, ServerConnection> isockets = new NonBlockingHashMap<String, ServerConnection>(); static { APMSigar tmp = null; try { tmp = APMSigar.getInstance(); } catch (Exception ex) { tmp = null; } sigar = tmp; Executors.newScheduledThreadPool(1, new ThreadFactory(){ private final AtomicInteger serial = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "ServerConnectionHarvester#" + serial.incrementAndGet()); return t; } }).scheduleWithFixedDelay(new Runnable(){ @Override public void run() { if(isockets.isEmpty()) return; long start = System.currentTimeMillis(); for(ServerConnection sc: isockets.values()) { sc.collect(); } long elapsed = System.currentTimeMillis()-start; SimpleLogger.info("\n\t===================\n\tServerConnection Harvest in [" ,elapsed, "] ms.\n\t===================\n"); } }, 5, 5, TimeUnit.SECONDS); } /** * Acquires the ServerConnection instance for the passed ISocketImpl * @param serverSocket the ISocketImpl to get the ServerConnection for. */ public static void registerInstance(ISocketImpl serverSocket) { if(serverSocket==null) throw new IllegalArgumentException("The passed server socket was null", new Throwable()); String key = key(serverSocket); isockets.putIfAbsent(key, new ServerConnection(key, serverSocket)); } /** * Generates a unique reference key for the passed server connection * @param serverSocket the server connection to generate a key for * @return the key */ public static String key(ISocketImpl serverSocket) { if(serverSocket==null) throw new IllegalArgumentException("The passed server socket was null", new Throwable()); return "" + System.identityHashCode(serverSocket); // Socket sock = serverSocket.getSocket(); // InetSocketAddress remoteSockAddr = (InetSocketAddress)sock.getRemoteSocketAddress(); // return new StringBuilder(sock.getLocalAddress().getHostAddress()) // .append("/").append(sock.getLocalPort()) // .append("/").append( // remoteSockAddr // .getHostString()).append("/") // .append(remoteSockAddr.getPort()) // .toString(); } /** * Creates a new ServerConnection * @param serverSocket The tracked server connection socket * @param key The designated key */ private ServerConnection(String key, ISocketImpl serverSocket) { if(isockets.containsKey(key)) { this.serverSocket = null; this.key = null; } else { this.serverSocket = serverSocket; this.key = key; } } /** * Creates a new stat map * @return a new stat map */ public static NonBlockingHashMap<String, ConcurrentLongSlidingWindow> newNetStatMap() { NonBlockingHashMap<String, ConcurrentLongSlidingWindow> stats = new NonBlockingHashMap<String, ConcurrentLongSlidingWindow>(STAT_ALL.length); for(String name: STAT_ALL) { stats.put(name, new ConcurrentLongSlidingWindow(20)); } return stats; } /** * Updates the local socket state, firing state change listeners if the state has changed * @param newSocketState The new socket state */ protected void setLocalSocketState(int newSocketState) { int priorSocketState = localSocketState.getAndSet(newSocketState); if(priorSocketState!=newSocketState) { SimpleLogger.info("Server LocalSide Socket [", serverSocket.getSocket().getLocalSocketAddress() , "] Changed State from [", TCPSocketState.getEnabledStatesName(priorSocketState) , "] to [", TCPSocketState.getEnabledStatesName(newSocketState), "]" ); } } /** * Updates the remote socket state, firing state change listeners if the state has changed * @param newSocketState The new socket state */ protected void setRemoteSocketState(int newSocketState) { int priorSocketState = remoteSocketState.getAndSet(newSocketState); if(priorSocketState!=newSocketState) { SimpleLogger.info("Server RemoteSide Socket [", serverSocket.getSocket().getLocalSocketAddress() , "] Changed State from [", TCPSocketState.getEnabledStatesName(priorSocketState) , "] to [", TCPSocketState.getEnabledStatesName(newSocketState), "]" ); } } /** * Collects stats and states for the local and remote side of this connection */ protected void collect() { refreshNetStat(false); if(localNetStat!=null) { int socketState = TCPSocketState.getMaskedArray(localNetStat.getTcpStates()); setLocalSocketState(socketState); SimpleLogger.info("Local TCP Socket State: [" , TCPSocketState.getEnabledStatesName(socketState), "]"); // localStats.get(STAT_TCPINBOUNDTOTAL).insert(localNetStat.getTcpInboundTotal()); // localStats.get(STAT_TCPOUTBOUNDTOTAL).insert(localNetStat.getTcpOutboundTotal()); // localStats.get(STAT_ALLINBOUNDTOTAL).insert(localNetStat.getAllInboundTotal()); // localStats.get(STAT_ALLOUTBOUNDTOTAL).insert(localNetStat.getAllOutboundTotal()); // localStats.get(STAT_TCPESTABLISHED).insert(localNetStat.getTcpEstablished()); // localStats.get(STAT_TCPSYNSENT).insert(localNetStat.getTcpSynSent()); // localStats.get(STAT_TCPSYNRECV).insert(localNetStat.getTcpSynRecv()); // localStats.get(STAT_TCPFINWAIT1).insert(localNetStat.getTcpFinWait1()); // localStats.get(STAT_TCPFINWAIT2).insert(localNetStat.getTcpFinWait2()); // localStats.get(STAT_TCPTIMEWAIT).insert(localNetStat.getTcpTimeWait()); // localStats.get(STAT_TCPCLOSE).insert(localNetStat.getTcpClose()); // localStats.get(STAT_TCPCLOSEWAIT).insert(localNetStat.getTcpCloseWait()); // localStats.get(STAT_TCPLASTACK).insert(localNetStat.getTcpLastAck()); // localStats.get(STAT_TCPLISTEN).insert(localNetStat.getTcpListen()); // localStats.get(STAT_TCPCLOSING).insert(localNetStat.getTcpClosing()); // localStats.get(STAT_TCPIDLE).insert(localNetStat.getTcpIdle()); // localStats.get(STAT_TCPBOUND).insert(localNetStat.getTcpBound()); // SimpleLogger.info("Local ServerSide [", localSocketState.get(), "] [" ,serverSocket.getSocket().getLocalSocketAddress() , "]" , renderStats(localStats)); } refreshNetStat(true); if(remoteNetStat!=null) { int socketState = TCPSocketState.getMaskedArray(remoteNetStat.getTcpStates()); setRemoteSocketState(socketState); SimpleLogger.info("Remote TCP Socket State: [" , TCPSocketState.getEnabledStatesName(socketState), "]"); // remoteStats.get(STAT_TCPINBOUNDTOTAL).insert(remoteNetStat.getTcpInboundTotal()); // remoteStats.get(STAT_TCPOUTBOUNDTOTAL).insert(remoteNetStat.getTcpOutboundTotal()); // remoteStats.get(STAT_ALLINBOUNDTOTAL).insert(remoteNetStat.getAllInboundTotal()); // remoteStats.get(STAT_ALLOUTBOUNDTOTAL).insert(remoteNetStat.getAllOutboundTotal()); // remoteStats.get(STAT_TCPESTABLISHED).insert(remoteNetStat.getTcpEstablished()); // remoteStats.get(STAT_TCPSYNSENT).insert(remoteNetStat.getTcpSynSent()); // remoteStats.get(STAT_TCPSYNRECV).insert(remoteNetStat.getTcpSynRecv()); // remoteStats.get(STAT_TCPFINWAIT1).insert(remoteNetStat.getTcpFinWait1()); // remoteStats.get(STAT_TCPFINWAIT2).insert(remoteNetStat.getTcpFinWait2()); // remoteStats.get(STAT_TCPTIMEWAIT).insert(remoteNetStat.getTcpTimeWait()); // remoteStats.get(STAT_TCPCLOSE).insert(remoteNetStat.getTcpClose()); // remoteStats.get(STAT_TCPCLOSEWAIT).insert(remoteNetStat.getTcpCloseWait()); // remoteStats.get(STAT_TCPLASTACK).insert(remoteNetStat.getTcpLastAck()); // remoteStats.get(STAT_TCPLISTEN).insert(remoteNetStat.getTcpListen()); // remoteStats.get(STAT_TCPCLOSING).insert(remoteNetStat.getTcpClosing()); // remoteStats.get(STAT_TCPIDLE).insert(remoteNetStat.getTcpIdle()); // remoteStats.get(STAT_TCPBOUND).insert(remoteNetStat.getTcpBound()); // SimpleLogger.info("Remote ServerSide [", remoteSocketState, "] [" , serverSocket.getSocket().getRemoteSocketAddress() , "]" , renderStats(remoteStats)); } } /* ServerSocket[addr=/0.0.0.0,port=0,localport=9384 Accepted Socket: [1212866551] [Socket[addr=/0:0:0:0:0:0:0:1,port=35812,localport=9384]] Available:0 INFO [ServerThread#1]OutputStream Accessed [1212866551] Local Address:/0:0:0:0:0:0:0:1:9384 Remote Address:/0:0:0:0:0:0:0:1:35812 tcp6 0 0 :::9384 :::* LISTEN 18277/java off (0.00/0/0) tcp6 0 0 ::1:35461 ::1:9384 ESTABLISHED 18295/nc off (0.00/0/0) tcp6 0 0 ::1:9384 ::1:35461 ESTABLISHED 18277/java off (0.00/0/0) ===== tcp6 0 0 :::9384 :::* LISTEN 18277/java off (0.00/0/0) tcp6 0 0 ::1:35461 ::1:9384 FIN_WAIT2 - timewait (56.35/0/0) tcp6 1 0 ::1:9384 ::1:35461 CLOSE_WAIT 18277/java off (0.00/0/0) -Djava.net.preferIPv4Stack=true addr=/0.0.0.0,port=0,localport=9384 INFO [ServerThread#1]OutputStream Accessed [1807104969] Local Address:/127.0.0.1:9384 Remote Address:/127.0.0.1:57136 tcp 0 0 0.0.0.0:9384 0.0.0.0:* LISTEN 18367/java off (0.00/0/0) tcp 0 0 127.0.0.1:9384 127.0.0.1:56931 ESTABLISHED 18367/java off (0.00/0/0) tcp 0 0 127.0.0.1:56931 127.0.0.1:9384 ESTABLISHED 18385/nc off (0.00/0/0) ==== tcp 0 0 0.0.0.0:9384 0.0.0.0:* LISTEN 18367/java off (0.00/0/0) tcp 1 0 127.0.0.1:9384 127.0.0.1:56931 CLOSE_WAIT 18367/java off (0.00/0/0) tcp 0 0 127.0.0.1:56931 127.0.0.1:9384 FIN_WAIT2 - timewait (56.93/0/0) */ /** * Formats a stats map into printable string * @param stats The map to render * @return a string */ protected String renderStats(Map<String, ConcurrentLongSlidingWindow> stats) { StringBuilder b = new StringBuilder(); for(Map.Entry<String, ConcurrentLongSlidingWindow> entry: stats.entrySet()) { ConcurrentLongSlidingWindow lsw = entry.getValue(); b.append("\n\t").append(entry.getKey()).append(": ").append(lsw.isEmpty() ? 0 : lsw.get(0)); } return b.toString(); } /** * Refreshes the netstats, creating them if they're null * @param remoteSide true for the remote side, false for the local side * @return true if the netstat was updated successfully, false otherwise */ protected boolean refreshNetStat(boolean remoteSide) { if(remoteSide) { if(remoteNetStat==null) remoteNetStat = getNetStatOrNull(remoteSide); if(remoteNetStat!=null) try { remoteNetStat.stat(sigar.getSigar(), serverSocket.getSocket().getInetAddress().getAddress(), serverSocket.getSocket().getPort()); return true; } catch (Exception ex) { return false; } } else if(!remoteSide) { if(localNetStat==null) localNetStat = getNetStatOrNull(remoteSide); if(localNetStat!=null) try { localNetStat.stat(sigar.getSigar(), serverSocket.getSocket().getLocalAddress().getAddress(), serverSocket.getSocket().getLocalPort()); return true; } catch (Exception ex) { return false; } } return false; } protected void dumpSocketSides() { StringBuilder b = new StringBuilder("Socket Sides Dump:"); b.append("\n\tLocal").append("\n\t\tAddress:").append(serverSocket.getSocket().getLocalAddress()).append("\n\t\tPort:").append(serverSocket.getSocket().getLocalPort()).append("\n\t\tNetStat:").append(localNetStat); b.append("\n\tRemote").append("\n\t\tAddress:").append(serverSocket.getSocket().getInetAddress()).append("\n\t\tPort:").append(serverSocket.getSocket().getPort()).append("\n\t\tNetStat:").append(remoteNetStat); InetSocketAddress isa = (InetSocketAddress)serverSocket.getSocket().getRemoteSocketAddress(); b.append("\n\tRSA").append("\n\t\tAddress:").append(isa.getAddress()).append("\n\t\tPort:").append(isa.getPort()); SimpleLogger.info(b); } /** * Returns a netstat for the remote of the passed socket impl * @param remoteSide true for the remote side, false for the local side * @return a netstat or null if one could not be acquired */ protected NetStat getNetStatOrNull(boolean remoteSide) { try { if(remoteSide) { if(remoteNetStat!=null) return remoteNetStat; return sigar.getNetStat(serverSocket.getSocket().getInetAddress().getAddress(), serverSocket.getSocket().getPort()); } if(localNetStat!=null) return localNetStat; return sigar.getNetStat(serverSocket.getSocket().getLocalAddress().getAddress(), serverSocket.getSocket().getLocalPort()); } catch (Exception ex) { SimpleLogger.error("NetStat fail on [", (remoteSide ? "Remote" : "Local"), ":", ex); return null; } } /** * Tests a socket's input and output streams to see if the socket is active. * @param so The socket to test * @return true if the socket is active, false otherwise */ protected boolean testSocketStreams(Socket so) { if(so==null) return false; try { if(!so.isConnected() || so.isClosed()) return false; boolean ok = testSocketInput(so) && testSocketOutput(so); if(!ok) return false; //so.sendUrgentData(0); NetStat ns = sigar.getNetStat(so.getLocalAddress().getAddress(), so.getLocalPort()); SimpleLogger.info("Local CloseWaits:", ns.getTcpCloseWait()); try { int cw = sigar.getNetStat(so.getInetAddress().getAddress(), so.getPort()).getTcpCloseWait(); SimpleLogger.info("Remote CloseWaits on [:" + so.getRemoteSocketAddress() + "]", cw); if(cw>0) return false; } catch (Exception ex) { SimpleLogger.warn("Failed to get Remote CloseWaits"); } // if(ns.getTcpCloseWait()>0) { // return false; // } return ok; } catch (Exception ex) { if(so.isConnected()) { try { so.close(); } catch (Exception e) {} } return false; } } /** * Tests the socket's input stream to determine if input is closed * @param so the socket to test * @return true if the input is still active, false otherwise */ protected boolean testSocketInput(Socket so) { if(so==null) return false; try { if(so.isInputShutdown()) return false; so.getInputStream().available(); return true; } catch (Exception ex) { return false; } } /** An empty byte array buffer constant */ public static final byte[] EMPTY_BYTE_ARR = {}; /** An one byte array buffer constant */ public static final byte[] ONE_BYTE_ARR = {0}; /** * Tests the socket's output stream to determine if output is closed * @param so the socket to test * @return true if the output is still active, false otherwise */ protected boolean testSocketOutput(Socket so) { if(so==null) return false; try { if(so.isOutputShutdown()) return false; OutputStream os = so.getOutputStream(); os.write(ONE_BYTE_ARR, 0, 0); return true; } catch (Exception ex) { SimpleLogger.info("Got exception writing zero bytes of ONE_BYTE_ARR"); return false; } } }