package com.ibm.nmon.util;

import org.slf4j.Logger;

import java.util.List;
import java.util.Map;
import java.util.SortedSet;

import com.ibm.nmon.data.DataRecord;
import com.ibm.nmon.data.DataType;
import com.ibm.nmon.data.ProcessDataSet;
import com.ibm.nmon.data.Process;
import com.ibm.nmon.data.ProcessDataType;

/**
 * Utility methods for working with DataSets and DataTypes.
 */
public final class DataHelper {
    public static Map<String, List<Process>> getProcessesByName(ProcessDataSet data, boolean sorted) {
        /**
         * Collect all the processes in a file and group them by name.
         * 
         * @return a Map of process names to a list of Processes. The List will never be empty or
         *         <code>null</code>.
         */
        Map<String, List<Process>> processNameToProcesses = null;

        if (sorted) {
            processNameToProcesses = new java.util.TreeMap<String, List<Process>>();
        }
        else {
            processNameToProcesses = new java.util.HashMap<String, List<Process>>();
        }

        // collect all the process names and map them to a Process
        for (Process process : data.getProcesses()) {
            List<Process> processes = processNameToProcesses.get(process.getName());

            if (processes == null) {
                processes = new java.util.ArrayList<Process>();
                processNameToProcesses.put(process.getName(), processes);
            }

            processes.add(process);
        }

        if (sorted) {
            for (List<Process> processes : processNameToProcesses.values()) {
                // note that this puts the aggregated process data (with pid -1) at the beginning of
                // the list
                java.util.Collections.sort(processes);
            }
        }

        return processNameToProcesses;
    }

    public static void aggregateProcessData(ProcessDataSet data, Logger logger) {
        long start = System.nanoTime();
        Map<String, List<Process>> processNameToProcesses = DataHelper.getProcessesByName(data, false);

        for (List<Process> processes : processNameToProcesses.values()) {
            if (processes.size() > 1) {
                aggregateProcesses(data, processes, logger);
            }
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Aggregated process data for {} in {}ms ", data, (System.nanoTime() - start) / 1000000.0d);
        }
    }

    private static void aggregateProcesses(ProcessDataSet data, List<Process> processes, Logger logger) {
        long start = System.nanoTime();
        long earliestStart = Long.MAX_VALUE;

        for (Process process : processes) {
            if (process.getStartTime() < earliestStart) {
                earliestStart = process.getStartTime();
            }
        }

        // assume first process is representative and fields do not change
        // note that this aggregates processes with the same name but different commands lines,
        // i.e. different Java web servers
        ProcessDataType processType = data.getType(processes.get(0));

        int fieldCount = processType.getFieldCount() + 1;

        // copy the fields from the Process
        // add a Count field to count the number of processes running at each time period
        String[] fields = new String[fieldCount];

        for (int i = 0; i < fieldCount - 1; i++) {
            fields[i] = processType.getField(i);
        }

        fields[fieldCount - 1] = "Count";

        String name = processes.get(0).getName();

        Process aggregate = new Process(-1, earliestStart, name);
        aggregate.setCommandLine("all " + name + " processes");
        ProcessDataType aggregateType = new ProcessDataType(aggregate, fields);

        data.addProcess(aggregate);
        data.addType(aggregateType);

        // for every record in the file, sum up all the data for each process and add the aggregated
        // data to the record
        for (DataRecord record : data.getRecords()) {
            double[] totals = new double[fieldCount];

            java.util.Arrays.fill(totals, 0);

            // does any process have data at this time?
            boolean valid = false;

            for (Process process : processes) {
                processType = data.getType(process);

                if (record.hasData(processType)) {
                    valid = true;

                    int n = 0;

                    for (String field : processType.getFields()) {
                        totals[n++] += record.getData(processType, field);
                    }

                    // process count
                    ++totals[n];
                }
            }

            if (valid) {
                record.addData(aggregateType, totals);
            }
        }

        if (logger.isTraceEnabled()) {
            logger.trace("Aggregated process data for {} in {}ms ", name, (System.nanoTime() - start) / 1000000.0d);
        }
    }

    /**
     * Does the given disk name denote a disk or a partition?
     * 
     * @return true if the name is a disk
     */
    public static boolean isNotPartition(String diskName) {
        // assume disk names are like sdx, sdx1, sdx2, etc on Linux
        // disks do not have digits; paritions do
        boolean hdisk = diskName.startsWith("hdisk");

        if ((diskName.startsWith("sd") || (diskName.startsWith("hd") && !hdisk) || diskName.startsWith("vd") || diskName
                .startsWith("dasd")) && !Character.isDigit(diskName.charAt(diskName.length() - 1))) {
            return true;

        }
        // hdisks on AIX are physical
        else if (hdisk) {
            return true;
        }
        // device mappers are always 'disks'
        // but Linux seems to report both dm AND sdx traffic so exclude them
        else {
            return false;
        }
    }

    /**
     * Get a sorted list of network interfaces, assuming field names in the DataType are like
     * 'eth0-read-KB/s'.
     */
    public static SortedSet<String> getInterfaces(DataType type) {
        SortedSet<String> ifaces = new java.util.TreeSet<String>();

        for (String field : type.getFields()) {
            int idx = field.indexOf("-read-KB/s");

            if (idx != -1) {
                ifaces.add(field.substring(0, idx));
            }
            else {
                idx = field.indexOf("-write-KB/s");
                if (idx != -1) {
                    ifaces.add(field.substring(0, idx));
                }
            }
        }

        return ifaces;
    }

    private static final boolean IS_IBM_JVM = System.getProperty("java.vm.vendor").startsWith("IBM");

    /**
     * Create a new String. This method is used to save memory usage for Strings that are created
     * via substring.
     */
    public static String newString(String original) {
        // new String to avoid memory leaks when the element is stored
        // IBM JVMs do not create a new char[] on copy, so do it manually
        // this is inefficient because the array is double copied, but that's better
        // than leaving the entire string in memory
        if (IS_IBM_JVM) {
            return new String(original.trim().toCharArray()).intern();
        }
        else {
            // Oracle JVM's String implementation is always the correct size
            return original.trim().intern();
        }
    }

    private DataHelper() {}
}

@@ -1,32 +1,4 @@
-package com.ibm.nmon.util;%0A%0A
 impo

Update DataHelper.java