/*
* 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.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.StringTokenizer;
import com.slamd.common.Constants;
import com.slamd.common.SLAMDException;
import com.slamd.stat.StackedValueTracker;
import com.slamd.stat.StatTracker;
/**
* This class defines a SLAMD resource monitor that uses the mpstat utility to
* measure CPU utilization data on a per-CPU basis. Note that the definition of
* "CPU" is controlled by the mpstat utility, so on multi-core systems (e.g.,
* those using UltraSPARC-IV processors) each core will appear as a separate
* CPU, and on CMT systems (e.g., those using UltraSPARC T1 processors), each
* strand will appear as a separate CPU.
* <BR><BR>
* This resource monitor is currently only supported on Solaris systems (using
* either SPARC or x86/x64 processors).
*
*
* @author Neil A. Wilson
*/
public class MPStatResourceMonitor
extends ResourceMonitor
{
/**
* The display name of the stat tracker that will be used to report the CPU
* busy time (user time + system time).
*/
public static final String STAT_TRACKER_CPU_BUSY = "MPStat CPU Busy Time";
/**
* The display name of the stat tracker that will be used to report the CPU
* idle time.
*/
public static final String STAT_TRACKER_CPU_IDLE = "MPStat CPU Idle Time";
/**
* The display name of the stat tracker that will be used to report the CPU
* system time.
*/
public static final String STAT_TRACKER_CPU_SYSTEM = "MPStat CPU System Time";
/**
* The display name of the stat tracker that will be used to report the CPU
* user time.
*/
public static final String STAT_TRACKER_CPU_USER = "MPStat CPU User Time";
/**
* The name of the configuration property that indicates whether the CPU busy
* (user+system) time should be captured.
*/
public static final String PROPERTY_CAPTURE_CPU_BUSY = "capture_cpu_busy";
/**
* The default behavior that will be used with regards to capturing CPU busy
* time.
*/
public static final boolean DEFAULT_CAPTURE_CPU_BUSY = true;
/**
* The name of the configuration property that indicates whether the CPU user
* time should be captured.
*/
public static final String PROPERTY_CAPTURE_CPU_USER = "capture_cpu_user";
/**
* The default behavior that will be used with regards to capturing CPU user
* time.
*/
public static final boolean DEFAULT_CAPTURE_CPU_USER = true;
/**
* The name of the configuration property that indicates whether the CPU
* system time should be captured.
*/
public static final String PROPERTY_CAPTURE_CPU_SYSTEM = "capture_cpu_system";
/**
* The default behavior that will be used with regards to capturing CPU system
* time.
*/
public static final boolean DEFAULT_CAPTURE_CPU_SYSTEM = true;
/**
* The name of the configuration property that indicates whether the CPU idle
* time should be captured.
*/
public static final String PROPERTY_CAPTURE_CPU_IDLE = "capture_cpu_idle";
/**
* The default behavior that will be used with regards to capturing CPU idle
* time.
*/
public static final boolean DEFAULT_CAPTURE_CPU_IDLE = true;
// Stat trackers that will be used by this resource monitor thread.
private StackedValueTracker cpuBusyTime;
private StackedValueTracker cpuIdleTime;
private StackedValueTracker cpuSystemTime;
private StackedValueTracker cpuUserTime;
// Flags that indicate what should be captured
private boolean captureCPUBusy;
private boolean captureCPUIdle;
private boolean captureCPUSystem;
private boolean captureCPUUser;
// The maps that will be used to hold the data collected while mpstat
// is running.
private LinkedHashMap<Integer,ArrayList<Integer>> idleMap;
private LinkedHashMap<Integer,ArrayList<Integer>> systemMap;
private LinkedHashMap<Integer,ArrayList<Integer>> userMap;
// The statistics collection interval that should be used.
private int collectionInterval;
// The data to use when initializing the stat trackers.
private String clientID;
private String threadID;
/**
* Performs any initialization specific to this resource monitor.
*
* @throws SLAMDException If a problem occurs while performing the
* initialization.
*/
@Override()
public void initializeMonitor()
throws SLAMDException
{
captureCPUBusy = getProperty(PROPERTY_CAPTURE_CPU_BUSY,
DEFAULT_CAPTURE_CPU_BUSY);
captureCPUUser = getProperty(PROPERTY_CAPTURE_CPU_USER,
DEFAULT_CAPTURE_CPU_USER);
captureCPUSystem = getProperty(PROPERTY_CAPTURE_CPU_SYSTEM,
DEFAULT_CAPTURE_CPU_SYSTEM);
captureCPUIdle = getProperty(PROPERTY_CAPTURE_CPU_IDLE,
DEFAULT_CAPTURE_CPU_IDLE);
userMap = new LinkedHashMap<Integer,ArrayList<Integer>>();
systemMap = new LinkedHashMap<Integer,ArrayList<Integer>>();
idleMap = new LinkedHashMap<Integer,ArrayList<Integer>>();
}
/**
* 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()
{
int osType = getClientOS();
switch (osType)
{
case OS_TYPE_SOLARIS:
return true;
default:
return false;
}
}
/**
* 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
{
MPStatResourceMonitor monitor = new MPStatResourceMonitor();
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)
{
this.clientID = clientID;
this.threadID = threadID;
this.collectionInterval = collectionInterval;
cpuUserTime = null;
cpuSystemTime = null;
cpuIdleTime = null;
cpuBusyTime = null;
}
/**
* Retrieves the name to use for this resource monitor.
*
* @return The name to use for this resource monitor.
*/
@Override()
public String getMonitorName()
{
return "MPStat";
}
/**
* Retrieves the statistical data collected by this resource monitor.
*
* @return The statistical data collected by this resource monitor.
*/
@Override()
public StatTracker[] getResourceStatistics()
{
ArrayList<StatTracker> statList = new ArrayList<StatTracker>();
if (captureCPUBusy && (cpuBusyTime != null))
{
statList.add(cpuBusyTime);
}
if (captureCPUUser && (cpuUserTime != null))
{
statList.add(cpuUserTime);
}
if (captureCPUSystem && (cpuSystemTime != null))
{
statList.add(cpuSystemTime);
}
if (captureCPUIdle && (cpuIdleTime != null))
{
statList.add(cpuIdleTime);
}
StatTracker[] returnTrackers = new StatTracker[statList.size()];
statList.toArray(returnTrackers);
return returnTrackers;
}
/**
* 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 execute the mpstat command and collect its output.
Process p;
InputStream inputStream;
BufferedReader reader;
try
{
String[] commandArray =
{
"mpstat",
String.valueOf(collectionInterval)
};
p = Runtime.getRuntime().exec(commandArray);
inputStream = p.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream));
}
catch (Exception e)
{
logMessage("Error executing command \"mpstat " + collectionInterval +
"\" -- " + e);
return Constants.JOB_STATE_STOPPED_DUE_TO_ERROR;
}
boolean firstSetSeen = false;
boolean firstSetSkipped = false;
int numIterations = -1;
int stopReason = Constants.JOB_STATE_COMPLETED_SUCCESSFULLY;
while (! shouldStop())
{
try
{
if (inputStream.available() == 0)
{
try
{
Thread.sleep(100);
continue;
} catch (InterruptedException ie) {}
}
// Read the next line of output and figure out what to do with it.
String line = reader.readLine();
if (line.startsWith("CPU"))
{
// This is a header line. If it's the first one, then indicate that
// we've seen it. If it's a subsequent one, then indicate that we
// have skipped the first set.
if (firstSetSeen)
{
firstSetSkipped = true;
numIterations++;
}
else
{
firstSetSeen = true;
}
}
else
{
// This is a data line. If we're in the first set of output, then
// simply parse out the CPU ID and store it in the hash with an empty
// list. Otherwise, also grab the percent user, system, wait, and
// idle times and append them to the lists already in the hash.
StringTokenizer tokenizer = new StringTokenizer(line, " \t");
if (firstSetSkipped)
{
Integer cpuID = new Integer(tokenizer.nextToken());
ArrayList<Integer> userList = userMap.get(cpuID);
ArrayList<Integer> systemList = systemMap.get(cpuID);
ArrayList<Integer> idleList = idleMap.get(cpuID);
if (userList == null)
{
// This is a CPU we haven't seen before. This could happen if the
// CPU was dynamically enabled in the middle of the job. In this
// case, create a new list and fill it with zeros up to this
// point.
userList = new ArrayList<Integer>();
systemList = new ArrayList<Integer>();
idleList = new ArrayList<Integer>();
for (int i=0; i < numIterations; i++)
{
userList.add(0);
systemList.add(0);
idleList.add(0);
}
userMap.put(cpuID, userList);
systemMap.put(cpuID, systemList);
idleMap.put(cpuID, idleList);
}
else if (userList.size() < numIterations)
{
// We must be missing some data for this CPU for one or more
// iterations. This can happen if the CPU is dynamically taken
// offline and then re-enabled in the middle of a job. In this
// case, fill in the gap with zeros.
while (userList.size() < numIterations)
{
userList.add(0);
systemList.add(0);
idleList.add(0);
}
}
// Skip over all the data that we don't care about for this tracker.
tokenizer.nextToken(); // minf
tokenizer.nextToken(); // mjf
tokenizer.nextToken(); // xcal
tokenizer.nextToken(); // intr
tokenizer.nextToken(); // ithr
tokenizer.nextToken(); // csw
tokenizer.nextToken(); // icsw
tokenizer.nextToken(); // migr
tokenizer.nextToken(); // smtx
tokenizer.nextToken(); // srw
tokenizer.nextToken(); // syscl
// Parse out the user, system, wait, and idle times.
Integer userTime = new Integer(tokenizer.nextToken());
Integer systemTime = new Integer(tokenizer.nextToken());
Integer waitTime = new Integer(tokenizer.nextToken());
Integer idleTime = new Integer(tokenizer.nextToken());
if (waitTime > 0)
{
systemTime = (systemTime + waitTime);
}
userList.add(userTime);
systemList.add(systemTime);
idleList.add(idleTime);
}
else
{
Integer cpuID = new Integer(tokenizer.nextToken());
userMap.put(cpuID, new ArrayList<Integer>());
systemMap.put(cpuID, new ArrayList<Integer>());
idleMap.put(cpuID, new ArrayList<Integer>());
}
}
}
catch (Exception e)
{
logMessage("Error while parsing mpstat command output: " + e);
stopReason = Constants.JOB_STATE_COMPLETED_WITH_ERRORS;
}
}
try
{
reader.close();
p.destroy();
} catch (Exception e) {}
// If the number of iterations is negative, then we didn't capture any data.
if (numIterations <= 0)
{
cpuBusyTime = null;
cpuUserTime = null;
cpuSystemTime = null;
cpuIdleTime = null;
return stopReason;
}
// Iterate through the maps and make sure that all the data for each of the
// CPUs has the right number of iterations. This could be off if a CPU was
// dynamically offlined and not re-enabled while the job was running, in
// which case we'll pad out the lists with zeros. It could also occur if
// the monitor was in the middle of parsing a set of output and only some of
// the CPUs had been handled, in which case we'll drop the data for that
// interval for those CPUs that had been captured. Then convert each data
// set to arrays to use when initializing the stat trackers.
int numCPUs = userMap.size();
String[] categoryNames = new String[numCPUs+1];
double[][] userTimeArray = new double[numIterations][numCPUs+1];
double[][] systemTimeArray = new double[numIterations][numCPUs+1];
double[][] idleTimeArray = new double[numIterations][numCPUs+1];
double[][] busyTimeArray = new double[numIterations][numCPUs+1];
int[] categoryCounts = new int[numIterations];
int categorySlot = 0;
Iterator iterator = userMap.keySet().iterator();
while (iterator.hasNext())
{
int slot= categorySlot++;
Integer cpuID = (Integer) iterator.next();
ArrayList<Integer> userList = userMap.get(cpuID);
ArrayList<Integer> systemList = systemMap.get(cpuID);
ArrayList<Integer> idleList = idleMap.get(cpuID);
if (userList.size() != numIterations)
{
while (userList.size() < numIterations)
{
userList.add(0);
systemList.add(0);
idleList.add(0);
}
while (userList.size() > numIterations)
{
userList.remove(numIterations);
systemList.remove(numIterations);
idleList.remove(numIterations);
}
}
categoryNames[slot] = "CPU " + cpuID;
for (int i=0; i < numIterations; i++)
{
Integer userTime = userList.get(i);
Integer systemTime = systemList.get(i);
Integer idleTime = idleList.get(i);
userTimeArray[i][slot] = userTime.doubleValue();
systemTimeArray[i][slot] = systemTime.doubleValue();
idleTimeArray[i][slot] = idleTime.doubleValue();
busyTimeArray[i][slot] =
userTime.doubleValue() + systemTime.doubleValue();
}
}
categoryNames[numCPUs] = "Idle Time";
for (int i=0; i < numIterations; i++)
{
userTimeArray[i][numCPUs] = (100 * numCPUs);
systemTimeArray[i][numCPUs] = (100 * numCPUs);
idleTimeArray[i][numCPUs] = (100 * numCPUs);
busyTimeArray[i][numCPUs] = (100 * numCPUs);
for (int j=0; j < numCPUs; j++)
{
userTimeArray[i][numCPUs] -= userTimeArray[i][j];
systemTimeArray[i][numCPUs] -= systemTimeArray[i][j];
idleTimeArray[i][numCPUs] -= idleTimeArray[i][j];
busyTimeArray[i][numCPUs] -= busyTimeArray[i][j];
}
}
Arrays.fill(categoryCounts, 1);
// Create the stat trackers and populate them with the captured data.
cpuBusyTime = new StackedValueTracker(clientID, threadID,
clientID + ' ' + STAT_TRACKER_CPU_BUSY,
collectionInterval, categoryNames);
cpuBusyTime.setIntervalTotals(busyTimeArray, categoryCounts);
cpuBusyTime.setDrawAsStackedGraph(false);
cpuUserTime = new StackedValueTracker(clientID, threadID,
clientID + ' ' + STAT_TRACKER_CPU_USER,
collectionInterval, categoryNames);
cpuUserTime.setIntervalTotals(userTimeArray, categoryCounts);
cpuUserTime.setDrawAsStackedGraph(false);
cpuSystemTime = new StackedValueTracker(clientID, threadID,
clientID + ' ' + STAT_TRACKER_CPU_SYSTEM,
collectionInterval, categoryNames);
cpuSystemTime.setIntervalTotals(systemTimeArray, categoryCounts);
cpuSystemTime.setDrawAsStackedGraph(false);
cpuIdleTime = new StackedValueTracker(clientID, threadID,
clientID + ' ' + STAT_TRACKER_CPU_IDLE,
collectionInterval, categoryNames);
cpuIdleTime.setIntervalTotals(idleTimeArray, categoryCounts);
cpuIdleTime.setDrawAsStackedGraph(false);
return stopReason;
}
}