package com.slamd.resourcemonitor.netstat;
import com.slamd.job.JobClass;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.regex.Pattern;
/**
* Solaris network interface statistics collector.
*/
public class SolarisNetStatCollector extends NetStatCollector
{
// Pattern used to split the kstat output
private static final Pattern LINE_PATTERN = Pattern.compile("[ \t:]");
// kstat collector script
private Process collector;
// Reader for the kstat output
private BufferedReader outputReader;
// Thread executing the data collection
private Thread collectorThread;
// Exception caught while reading the script input
private volatile IOException exception;
// Flag to indicate that the collector thread should stop
private volatile boolean shouldStop = false;
// Number of lines expected every time kstat outputs statistics for a
// time interval
private int lineCount;
// Number of lines read from kstat for the current interval.
private int currentLineCount = 0;
// Should only be instantiated by the builder
SolarisNetStatCollector()
{
}
/**
* {@inheritDoc}
*/
@Override
public void initialize()
{
try
{
final StringBuilder buf = new StringBuilder("/usr/bin/kstat -p -c net ");
// If all interfaces are monitored or
// more than one interface is monitored, then kstat will collect stats
// from all interfaces. In the latter case, it will ignore stats from
// non-monitored interfaces.
if (isMonitorAllInterfaces() || getInterfaceNames().size() > 1)
{
buf.append("::mac:[or]bytes64");
}
else
{
// Otherwise, just tell kstat to return the stats for a single
// interface.
buf.append("::").
append(getInterfaceNames().iterator().next()).
append(":[or]bytes64");
}
// Find out how many lines kstat would produce without the interval arg.
populateLineCount(buf.toString());
// Append the interval argument
final String kstatCommand =
buf.append(' ').append(getCollectionIntervalSecs()).toString();
collector = Runtime.getRuntime().exec(kstatCommand);
}
catch (IOException e)
{
logMessage("Failed to initialize collector, reason: " +
e.getLocalizedMessage());
throw new RuntimeException(e);
}
outputReader = new BufferedReader(
new InputStreamReader(
this.collector.getInputStream()
)
);
final Thread thread = new Thread(
new Runnable()
{
public void run() {
String line;
try
{
while (!shouldStop()
&& (line = outputReader.readLine()) != null)
{
parseLine(line);
}
}
catch (IOException e)
{
handleException(e);
}
}
}
);
thread.setDaemon(true);
thread.setName("Solaris network statistics collector");
thread.start();
collectorThread = thread;
}
/**
* {@inheritDoc}
*/
public void collect() throws IOException
{
//
// Nothing to do here, since the collection is done in a separate thread.
//
// In case the collector thread exited, the code rethrows the exception
// so that the resource monitor handles this accordingly.
//
if (exception != null && !shouldStop())
{
final IOException e = exception;
exception = null;
throw e;
}
}
/**
* {@inheritDoc}
*/
@Override
public void stop()
{
shouldStop = true;
// Wait for up to 5 seconds for the collector to stop
try
{
collectorThread.join(5000);
}
catch (InterruptedException e)
{
// ignore
}
if (collectorThread.isAlive())
{
collectorThread.interrupt();
}
try
{
outputReader.close();
}
catch (IOException e) {
// ignore
}
collector.destroy();
}
/**
* @return true if the collector thread should stop.
*/
private boolean shouldStop()
{
return shouldStop;
}
/**
* Helper method for the collector thread to save the exception caught
* while reading the collector script output.
*
* @param e exception caught.
*/
private void handleException(final IOException e)
{
exception = e;
}
/**
* Executes the kstat utility before collection starts in order to determine
* the number of lines expected in each iteration.
*
* @param command the kstat command to execute.
*
* @throws IOException if an error occurs while interacting with the kstat
* utility.
*/
private void populateLineCount(final String command) throws IOException
{
final Process p =
Runtime.getRuntime().exec(command);
final BufferedReader reader =
new BufferedReader(
new InputStreamReader(
p.getInputStream()
)
);
int lineCount = 0;
while (reader.readLine() != null)
{
lineCount++;
}
reader.close();
p.destroy();
this.lineCount = lineCount;
}
/**
* Parses the kstat output and saves the collected statistic.
*
* @param line line to parse
*/
private void parseLine(final String line)
{
// kstat puts an empty line between iterations
if (line.length() == 0)
{
return;
}
// each line is expected to be in the form of
//
// moduleName:instanceName:name:statistic value
//
// e.g.
// e1000g:0:e1000g0:obytes64 2070825856
//
final String[] fields = LINE_PATTERN.split(line);
assert fields.length == 5;
final String moduleName = fields[0];
final String instanceName = fields[1];
final String name = fields[2];
final String statistic = fields[3];
final String value = fields[4];
final String interfaceName;
if ("mac".equals(name))
{
interfaceName = moduleName + instanceName;
}
else
{
interfaceName = name;
}
boolean skipParsing = false;
if (!isMonitorAllInterfaces() &&
!getInterfaceNames().contains(interfaceName))
{
// Ignore this line if this interface is not tracked
skipParsing = true;
}
InterfaceStatistics stats = getInterfaceStatistics(interfaceName);
if (stats == null)
{
return;
}
if (!skipParsing)
{
if ("obytes64".equals(statistic))
{
try
{
stats.recordSentValue(Long.parseLong(value));
}
catch (Exception e)
{
// This should never happen.
writeVerbose("Unable to determine obytes64 for interface " +
interfaceName + " from kstat output: " +
JobClass.stackTraceToString(e));
}
}
else if ("rbytes64".equals(statistic))
{
try
{
stats.recordReceivedValue(Long.parseLong(value));
}
catch (Exception e)
{
// This should never happen.
writeVerbose("Unable to determine rbytes64 for interface " +
interfaceName + " from kstat output: " +
JobClass.stackTraceToString(e));
}
}
}
if (++currentLineCount == lineCount)
{
// All output lines for this time interval have been processed
stats.completeIteration();
currentLineCount = 0;
}
}
}