/* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2004-2010. * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): Neil A. Wilson */ package com.slamd.resourcemonitor; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketTimeoutException; import com.slamd.common.Constants; import com.slamd.common.SLAMDException; import com.slamd.job.JobClass; import com.slamd.stat.IncrementalTracker; import com.slamd.stat.RealTimeStatReporter; import com.slamd.stat.StatTracker; import com.slamd.stat.TimeTracker; /** * This class defines a SLAMD resource monitor that uses UDP packets to obtain * basic characteristics of the network connection between two systems, * including response time and packet loss. The function that it performs is * very similar to the ping utility, but the transport is UDP rather than ICMP. * Communication can be with either the standard echo service (UDP port 7) if * available, or using a custom UDP ping server listening on any port. * <BR><BR> * Note that this monitor can achieve at best millisecond accuracy because there * is no pure Java way to obtain more accurate timings in Java 1.4.x. Java 1.5 * introduces a new <CODE>System.nanoTime()</CODE> method that can offer up to * nanosecond accuracy, but this monitor does not take advantage of that. As * such, this monitor is best suited for WAN connections with multi-millisecond * response times between systems. * * * @author Neil A. Wilson */ public class UDPPingResourceMonitor extends ResourceMonitor { /** * The display name of the stat tracker that will be used to report the * total number of requests sent. */ public static final String STAT_TRACKER_REQUESTS_SENT = "Requests Sent"; /** * The display name of the stat tracker that will be used to report the * response time in milliseconds. */ public static final String STAT_TRACKER_RESPONSE_TIME = "Response Time (ms)"; /** * The display name of the stat tracker that will be used to measure the * number of packets that were lost (i.e., cases in which no response was * obtained before a timeout occurred). */ public static final String STAT_TRACKER_PACKETS_LOST = "Packets Lost"; /** * The display name of the stat tracker that will be used to measure the * number of duplicate packets received. */ public static final String STAT_TRACKER_DUPLICATE_PACKETS = "Duplicate Packets Received"; /** * The name of the configuration property that specifies the address of the * system to ping. */ public static final String PROPERTY_TARGET_ADDRESS = "target_address"; /** * The default target address that will be used if none is specified. */ public static final String DEFAULT_TARGET_ADDRESS = null; /** * The name of the configuration property that specifies the UDP port to which * the "ping" requests should be sent. */ public static final String PROPERTY_TARGET_PORT = "target_port"; /** * The default target port that will be used if none is specified. */ public static final int DEFAULT_TARGET_PORT = 7; /** * The name of the configuration property that specifies the interval in * milliseconds between pings. */ public static final String PROPERTY_PING_INTERVAL = "ping_interval"; /** * The default ping interval that will be used if none is specified. */ public static final int DEFAULT_PING_INTERVAL = 1000; /** * The name of the configuration property that specifies the maximum length of * time in milliseconds that the client will wait for a response before * considering a packet "lost". */ public static final String PROPERTY_PING_TIMEOUT = "ping_timeout"; /** * The default ping timeout that will be used if none is specified. */ public static final int DEFAULT_PING_TIMEOUT = 1000; /** * The name of the configuration property that indicates the size in bytes of * the UDP packet to send to the remote system. */ public static final String PROPERTY_PACKET_SIZE = "packet_size"; /** * The default packet size that will be used if none is specified. */ public static final int DEFAULT_PACKET_SIZE = 64; /** * The minimum packet size that can be used for the pings. It is necessary to * have at least this many bytes to hold the sequence number and packet * creation timestamp. */ public static final int MINIMUM_PACKET_SIZE = 12; // Stat trackers that will be used by this resource monitor thread. private IncrementalTracker packetsLost; private IncrementalTracker duplicatePackets; private TimeTracker responseTimer; // The packet size to use for the UDP packets that will be sent. private int packetSize; // The interval to use when pinging the remote system. private int pingInterval; // The timeout to use when detecting lost packets. private int pingTimeout; // The UDP port to which the request packets will be sent. private int targetPort; // The address of the local system on which the client is running. private String localAddress; // The user-specified address of the target system to ping. private String targetAddress; // The IP address of the target system to ping. private String targetIP; /** * Performs any initialization specific to this resource monitor. * * @throws SLAMDException If a problem occurs while performing the * initialization. */ @Override() public void initializeMonitor() throws SLAMDException { targetAddress = getProperty(PROPERTY_TARGET_ADDRESS, DEFAULT_TARGET_ADDRESS); targetPort = getProperty(PROPERTY_TARGET_PORT, DEFAULT_TARGET_PORT); pingInterval = getProperty(PROPERTY_PING_INTERVAL, DEFAULT_PING_INTERVAL); pingTimeout = getProperty(PROPERTY_PING_TIMEOUT, DEFAULT_PING_TIMEOUT); packetSize = getProperty(PROPERTY_PACKET_SIZE, DEFAULT_PACKET_SIZE); // Make sure that a target address was specified. if (targetAddress == null) { throw new SLAMDException("No target address specified for the system " + "to ping."); } // Make sure that the packet size is not smaller than the minimum allowed. if (packetSize < MINIMUM_PACKET_SIZE) { logMessage("UDP ping packet size may not be smaller than " + MINIMUM_PACKET_SIZE + " -- using minimum allowable value."); packetSize = MINIMUM_PACKET_SIZE; } // Resolve the target address to an IP address so we don't have to worry // about name resolution later. try { targetIP = InetAddress.getByName(targetAddress).getHostAddress(); } catch (Exception e) { throw new SLAMDException("Unable to resolve target address \"" + targetAddress + " to an IP address -- " + e, e); } try { localAddress = InetAddress.getLocalHost().getHostName(); } catch (Exception e) { try { localAddress = InetAddress.getLocalHost().getHostAddress(); } catch (Exception e2) { throw new SLAMDException("Unable to obtain local address -- " + e2, e2); } } } /** * Indicates whether the current client system is supported for this resource * monitor. * * @return <CODE>true</CODE> if the current client system is supported for * this resource monitor, or <CODE>false</CODE> if not. */ @Override() public boolean clientSupported() { return true; } /** * Creates a new instance of this resource monitor thread. Note that the * <CODE>initialize()</CODE> method should have been called on the new * instance before it is returned. * * @return A new instance of this resource monitor thread. * * @throws SLAMDException If a problem occurs while creating or initializing * the resource monitor. */ @Override() public ResourceMonitor newInstance() throws SLAMDException { UDPPingResourceMonitor monitor = new UDPPingResourceMonitor(); monitor.initialize(getMonitorClient(), getMonitorProperties()); return monitor; } /** * Initializes the stat trackers maintained by this resource monitor. * * @param clientID The client ID to use for the stubs. * @param threadID The thread ID to use for the stubs. * @param collectionInterval The collection interval to use for the stubs. */ @Override() public void initializeStatistics(String clientID, String threadID, int collectionInterval) { String displayNamePrefix = localAddress + " -> " + targetAddress + " UDP Ping "; responseTimer = new TimeTracker(clientID, threadID, displayNamePrefix + STAT_TRACKER_RESPONSE_TIME, collectionInterval); packetsLost = new IncrementalTracker(clientID, threadID, displayNamePrefix + STAT_TRACKER_PACKETS_LOST, collectionInterval); duplicatePackets = new IncrementalTracker(clientID, threadID, displayNamePrefix + STAT_TRACKER_DUPLICATE_PACKETS, collectionInterval); ResourceMonitorJob monitorJob = getMonitorJob(); if (monitorJob.enableRealTimeStats()) { String jobID = monitorJob.getJobID(); RealTimeStatReporter statReporter = monitorJob.getStatReporter(); responseTimer.enableRealTimeStats(statReporter, jobID); packetsLost.enableRealTimeStats(statReporter, jobID); duplicatePackets.enableRealTimeStats(statReporter, jobID); } } /** * Retrieves the name to use for this resource monitor. * * @return The name to use for this resource monitor. */ @Override() public String getMonitorName() { return "UDP Ping"; } /** * Retrieves the statistical data collected by this resource monitor. * * @return The statistical data collected by this resource monitor. */ @Override() public StatTracker[] getResourceStatistics() { return new StatTracker[] { responseTimer, packetsLost, duplicatePackets }; } /** * Performs the work of actually collecting resource statistics. This method * should periodically call the <CODE>shouldStop()</CODE> method to determine * whether to stop collecting statistics. * * @return A value that indicates the status of the monitor when it * completed. */ @Override() public int runMonitor() { // First, establish a "connection" to the remote system. DatagramSocket pingSocket; try { pingSocket = new DatagramSocket(); pingSocket.setSoTimeout(pingTimeout); pingSocket.connect(new InetSocketAddress(targetIP, targetPort)); } catch (Exception e) { logMessage("UDP ping unable to establish a connection to " + targetIP + ':' + targetPort + " -- " + JobClass.stackTraceToString(e)); return Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; } // Create the packets that we will use to send and receive the pings. byte[] rxBytes = new byte[packetSize]; byte[] txBytes = new byte[packetSize]; DatagramPacket rxPacket = new DatagramPacket(rxBytes, packetSize); DatagramPacket txPacket = new DatagramPacket(txBytes, packetSize); // Start the stat trackers. responseTimer.startTracker(); packetsLost.startTracker(); duplicatePackets.startTracker(); // Operate in a loop that will run until it needs to stop. int sequenceNumber = 0; int returnCode = Constants.JOB_STATE_COMPLETED_SUCCESSFULLY; while (! shouldStop()) { // Get the current time, since we will need it to construct the request. // The request will contain at least 12 bytes (four for the sequence // number and eight for the start time). long startTime = System.currentTimeMillis(); txBytes[0] = (byte) ((sequenceNumber >>> 24) & 0x000000FF); txBytes[1] = (byte) ((sequenceNumber >>> 16) & 0x000000FF); txBytes[2] = (byte) ((sequenceNumber >>> 8) & 0x000000FF); txBytes[3] = (byte) (sequenceNumber & 0x000000FF); txBytes[4] = (byte) ((startTime >>> 56) & 0x000000FF); txBytes[5] = (byte) ((startTime >>> 48) & 0x000000FF); txBytes[6] = (byte) ((startTime >>> 40) & 0x000000FF); txBytes[7] = (byte) ((startTime >>> 32) & 0x000000FF); txBytes[8] = (byte) ((startTime >>> 24) & 0x000000FF); txBytes[9] = (byte) ((startTime >>> 16) & 0x000000FF); txBytes[10] = (byte) ((startTime >>> 8) & 0x000000FF); txBytes[11] = (byte) (startTime & 0x000000FF); // Actually send the packet to the remote system. txPacket.setData(txBytes); responseTimer.startTimer(); try { pingSocket.send(txPacket); } catch (Exception e) { logMessage("UDP ping unable to send packet with sequence number " + sequenceNumber + " -- " + JobClass.stackTraceToString(e)); returnCode = Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; break; } // Wait for the response packet. try { pingSocket.receive(rxPacket); rxBytes = rxPacket.getData(); int sequence = (((rxBytes[0] & 0x000000FF) << 24) | ((rxBytes[1] & 0x000000FF) << 16) | ((rxBytes[2] & 0x000000FF) << 8) | (rxBytes[3] & 0x000000FF)); if (sequence == sequenceNumber) { responseTimer.stopTimer(); } else if (sequence < sequenceNumber) { duplicatePackets.increment(); } } catch (SocketTimeoutException ste) { packetsLost.increment(); } catch (Exception e) { logMessage("UDP ping caught an unexpected exception while waiting " + "for response with sequence number " + sequenceNumber + " -- " + JobClass.stackTraceToString(e)); returnCode = Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; break; } // Increment the sequence number and see if we need to sleep before the // next request. sequenceNumber++; long sleepTime = (startTime + pingInterval) - System.currentTimeMillis(); if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (Exception e) {} } } // Close the "connection" to the remote system. try { pingSocket.close(); } catch (Exception e) {} // Stop the stat trackers. responseTimer.stopTracker(); packetsLost.stopTracker(); duplicatePackets.stopTracker(); return returnCode; } }