package com.ibm.nmon;
import java.io.IOException;
import java.io.File;
import java.io.PrintWriter;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Set;
import com.ibm.nmon.data.DataSetListener;
import com.ibm.nmon.data.DataSet;
import com.ibm.nmon.data.ProcessDataSet;
import com.ibm.nmon.data.DataRecord;
import com.ibm.nmon.data.DataType;
import com.ibm.nmon.data.Process;
import com.ibm.nmon.file.CombinedFileFilter;
import com.ibm.nmon.util.FileHelper;
public final class NMONVisualizerCmdLine extends NMONVisualizerApp implements DataSetListener {
private static final SimpleDateFormat OUTPUT_FORMAT = new SimpleDateFormat("MM/dd/yy,HH:mm:ss,");
public static void main(String[] args) throws Exception {
// initialize logging from the classpath properties file
java.util.logging.LogManager.getLogManager().readConfiguration(
NMONVisualizerCmdLine.class.getResourceAsStream("/cmdline.logging.properties"));
NMONVisualizerCmdLine app = new NMONVisualizerCmdLine();
app.addDataSetListener(app);
File pathToParse = getPathToParse(args);
List<String> toParse = new java.util.ArrayList<String>();
FileHelper.recurseDirectories(java.util.Collections.singletonList(pathToParse),
CombinedFileFilter.getInstance(false), toParse);
if (toParse.size() == 0) {
if (pathToParse.isDirectory()) {
System.err.println("no files to parse in " + pathToParse);
}
else {
System.err.println(pathToParse + " is not a recognized file");
}
return;
}
for (String file : toParse) {
System.out.print("Parsing file " + file + "... ");
System.out.flush();
try {
app.parse(file, java.util.TimeZone.getDefault());
System.out.println("Complete");
}
catch (Exception e) {
System.out.println("Failed parsing " + file);
e.printStackTrace();
}
}
System.out.println();
String outputDir = "";
String baseName = "";
if (pathToParse.isDirectory()) {
outputDir = pathToParse.getAbsolutePath();
baseName = "consolidated";
}
else {
outputDir = pathToParse.getParentFile().getAbsolutePath();
baseName = pathToParse.getName();
int idx = baseName.lastIndexOf('.');
if (idx != -1) {
baseName = baseName.substring(0, idx);
}
}
createMasterDetailFile(outputDir, baseName, app);
// if any data set has process data, output the process data files
for (DataSet data : app.getDataSets()) {
if ((data instanceof ProcessDataSet) && (((ProcessDataSet) data).getProcessCount() > 0)) {
createMasterDetailProcessFile(outputDir, baseName, app);
createCommandsFile(outputDir, baseName, app);
break;
}
}
}
private static File getPathToParse(String[] args) throws IOException {
String pathname;
if (args.length < 1) {
System.out.print("What is the path and/or base name you would like to analyze?: ");
System.out.flush();
BufferedReader sysin = new BufferedReader(new java.io.InputStreamReader(System.in));
pathname = sysin.readLine();
sysin.close();
}
else {
pathname = args[0];
}
pathname = pathname.replace("\\", "/");
return new File(pathname);
}
// Output a CSV file with 1 row for each timestamp / hostname combination. Each row will have a
// column for each metric being output. This allows LoadRunner to read in a single file with all
// hosts in it, at the expense of being able to sort by a specific metric since the metric name
// will include the hostname. An alternative, which requires no code change, is to parse each
// file individually them import each CSV separately into LoadRunner.
private static void createMasterDetailFile(String outputDir, String baseName, NMONVisualizerCmdLine app)
throws IOException {
String outputFile = outputDir + '/' + baseName + "_nmon.csv";
List<DataType> typesToOutput = getTypesToOutput(app);
System.out.print("Writing NMON data to master-detail CSV file to " + outputFile + "... ");
System.out.flush();
PrintWriter out = new PrintWriter(new FileWriter(outputFile));
StringBuilder builder = new StringBuilder(512);
// output master-detail header
// with 1 column for each output field
builder.append("Date,Time,System,");
for (DataType type : typesToOutput) {
for (String field : type.getFields()) {
builder.append(type.getId());
builder.append('-');
builder.append(field);
builder.append(',');
}
}
// remove trailing comma
builder.deleteCharAt(builder.length() - 1);
out.println(builder);
// for each time monitored, output the data for each file, field by field
for (long time : app.timesMonitored) {
String dateTime = OUTPUT_FORMAT.format(new java.util.Date(time));
for (DataSet data : app.getDataSets()) {
DataRecord record = data.getRecord(time);
// no record for the given time, continue to the next host
if (record == null) {
continue;
}
builder.setLength(0);
builder.append(dateTime);
builder.append(data.getHostname());
builder.append(',');
for (DataType type : typesToOutput) {
DataType hostType = data.getType(type.getId());
for (String field : type.getFields()) {
if (hostType != null) {
// note lookup by host type here
// since this host may not have all the values being searched
if (hostType.hasField(field)) {
builder.append(record.getData(hostType, field));
}
}
// note if host does not even have the DataType, still output the correct
// number of commas
builder.append(',');
}
}
// remove trailing comma
builder.deleteCharAt(builder.length() - 1);
out.println(builder);
}
}
out.close();
System.out.println("Complete");
}
// build a hardcoded set of data to output
private static List<DataType> getTypesToOutput(NMONVisualizerCmdLine app) {
Set<String> networks = new java.util.HashSet<String>();
Set<String> disks = new java.util.HashSet<String>();
for (DataSet data : app.getDataSets()) {
// all network data for ethernet devices
DataType parsedNetwork = data.getType("NET");
for (int i = 0; i < parsedNetwork.getFieldCount(); i++) {
String field = parsedNetwork.getField(i);
if (field.startsWith("eth") || field.startsWith("en")) {
networks.add(field);
}
}
// add all the disks from each NMON file
// adding for each metric is probably unnecessary since the disks reported on _should_
// be the same for each, but it does not hurt here to be sure
disks.addAll(data.getType("DISKBUSY").getFields());
disks.addAll(data.getType("DISKREAD").getFields());
disks.addAll(data.getType("DISKWRITE").getFields());
disks.addAll(data.getType("DISKXFER").getFields());
}
// add generated total value for all disk metrics
disks.add("Total");
List<DataType> types = new java.util.ArrayList<DataType>(5);
types.add(new DataType("CPU_ALL", "CPU Total", "User%", "Sys%", "Wait%", "CPU%"));
types.add(new DataType("NET", "Network I/O", networks.toArray(new String[0])));
String[] diskNames = disks.toArray(new String[0]);
types.add(new DataType("DISKBUSY", "Disk %Busy", diskNames));
types.add(new DataType("DISKREAD", "Disk Read KB/s", diskNames));
types.add(new DataType("DISKWRITE", "Disk Write KB/s", diskNames));
types.add(new DataType("DISKXFER", "Disk transfers per second", diskNames));
// Linux
types.add(new DataType("VM", "Paging and Virtual Memory", "pgpgin", "pgpgout", "pswpin", "pswpout"));
// AIX
types.add(new DataType("PAGE", "Paging and Virtual Memory", "pgin", "pgout", "pgsin", "pgsout"));
types.add(new DataType("MEM", "Memory MB", "memfree", "Real free(MB)"));
types.add(new DataType("PROC", "Processes", "Runnable", "Swap-in", "pswitch", "syscall"));
return types;
}
// Output a CSV file with 1 row for each timestamp / hostname combination. Each row will have a
// column with the value 'host-command-pid' followed by a column with the CPU for that command
private static void createMasterDetailProcessFile(String outputDir, String baseName, NMONVisualizerCmdLine app)
throws IOException {
List<ProcessDataSet> dataSets = new java.util.ArrayList<ProcessDataSet>(app.getDataSetCount());
for (DataSet dataSet : app.getDataSets()) {
if (dataSet instanceof ProcessDataSet) {
dataSets.add((ProcessDataSet) dataSet);
}
}
String outputFile = outputDir + '/' + baseName + "_top.csv";
System.out.print("Writing TOP data to master-detail CSV file to " + outputFile + "... ");
System.out.flush();
PrintWriter out = new PrintWriter(new FileWriter(outputFile));
StringBuilder builder = new StringBuilder(512);
builder.append("Date,Time,Command,CPU%");
out.println(builder);
for (long time : app.timesMonitored) {
for (DataSet data : dataSets) {
DataRecord record = data.getRecord(time);
if (record == null) {
continue;
}
for (Process p : ((ProcessDataSet) data).getProcesses()) {
DataType type = ((ProcessDataSet) data).getType(p);
if (!record.hasData(type)) {
continue;
}
builder.setLength(0);
builder.append(OUTPUT_FORMAT.format(new java.util.Date(time)));
builder.append(data.getHostname());
builder.append('-');
builder.append(p.getName());
builder.append('-');
if (p.getId() == -1) {
builder.append("ALL");
}
else {
builder.append(Integer.toString(p.getId()));
}
builder.append(',');
builder.append(record.getData(type, "%CPU"));
out.println(builder);
}
}
}
out.close();
System.out.println("Complete");
}
// Output a CSV file with 1 row for each timestamp / hostname combination. Each row will have a
// column with the value 'host-command-pid' followed by a column with the name of the command
// then final column with the full command line
private static void createCommandsFile(String outputDir, String baseName, NMONVisualizerCmdLine app)
throws IOException {
List<ProcessDataSet> dataSets = new java.util.ArrayList<ProcessDataSet>(app.getDataSetCount());
for (DataSet dataSet : app.getDataSets()) {
if (dataSet instanceof ProcessDataSet) {
dataSets.add((ProcessDataSet) dataSet);
}
}
String outputFile = outputDir + '/' + baseName + "_commands.csv";
System.out.print("Writing command data to CSV file to " + outputFile + "... ");
System.out.flush();
PrintWriter out = new PrintWriter(new FileWriter(outputFile));
StringBuilder builder = new StringBuilder(512);
builder.append("Host,PID,Command,Command Line");
out.println(builder);
for (ProcessDataSet data : dataSets) {
for (Process p : data.getProcesses()) {
if (p.getId() == -1) {
continue;
}
builder.setLength(0);
builder.append(data.getHostname());
builder.append(',');
builder.append(p.getId());
builder.append(',');
builder.append(p.getName());
builder.append(',');
builder.append(p.getCommandLine());
out.println(builder);
}
}
out.close();
System.out.println("Complete");
}
private final Set<Long> timesMonitored = new java.util.TreeSet<Long>();
public NMONVisualizerCmdLine() throws Exception {
super();
}
@Override
public void dataAdded(DataSet data) {
timesMonitored.addAll(data.getTimes());
}
@Override
public void dataRemoved(DataSet data) {}
@Override
public void dataChanged(DataSet data) {}
@Override
public void dataCleared() {}
}