package test.stress; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.lang.management.MemoryMXBean; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties; import java.awt.Toolkit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import simulator.DataCenterWorkloadSimulator; /** * Class responsible for performin memory and time analysis of experiments * execution. * * @author Jarek Szymczak * @version $Id$ */ public class MemoryAndTimeAnalyzer { /** The class logger. */ private static final Log LOGGER = LogFactory.getLog(MemoryAndTimeAnalyzer.class); /** The properties. */ private static Properties properties = new Properties(); /** * Variables determinig what type of memories should be meassured and should * it be performed in detailed or simple way. */ private static boolean heap, nonHeap, jvm, heapDet, nonHeapDet, jvmDet; /** * Determines if the time is meassured precisly (it's not disturb with * memory meassuring). */ private static boolean timePrec; /** Memory details output stream. */ private static FileOutputStream outDetails; /** The current number of iterations - used for progress calculation. */ private static int currentNumberOfIterations; /** The total number of iterations - used for progress calculation. */ private static int totalNumberOfIterations; /** Number of repeats of each experiments. */ private static int repeats; /** * It stores information about amount of system memory in a certain machine * (in megabytes). */ private static int memoryInMB; /** The interval between memory checks (in miliseconds). */ private static int interval; /** * The object used for gathering the information about heap and non-heap * memory. */ private static MemoryMXBean memoryMXBean = java.lang.management.ManagementFactory.getMemoryMXBean(); /** * The GSSIM object which is analyzed with certain experiment (for each * experiment new object is created, and the old one is dereferenced and * deleted (probably). */ private static DataCenterWorkloadSimulator gssim = new DataCenterWorkloadSimulator(); /** * Variables helpful during time and memory consumption analysis (they're * not local to simplify working with the thread responsible for memory * analysis and to write the details to outDetails output stream. */ private static long timestart, hmemstart, hmemmax, nmemmax, jvmmemmax, timend; /** * Performs memory and time analysis of a single file. * * @param filename * experiment file path * @param iteration * iteration number * @return array with results: time, heap memory, non-heap memory and JVM * memory * @throws InterruptedException * the interrupted exception when thread was interrupted during * sleep */ private static double[] singleTask(String filename, int iteration) throws InterruptedException { double[] result = new double[4]; Thread t = new Thread() { public void run() { long htemp, ntemp, jvmtemp; hmemmax = hmemstart; nmemmax = 0; jvmmemmax = 0; try { while (!interrupted()) { sleep(interval); htemp = heap ? memoryMXBean.getHeapMemoryUsage() .getUsed() : 0; ntemp = nonHeap ? memoryMXBean.getNonHeapMemoryUsage() .getUsed() : 0; jvmtemp = jvm ? getJVMMemory() : 0; if (htemp > hmemmax) hmemmax = htemp; if (ntemp > nmemmax) nmemmax = ntemp; if (htemp > jvmmemmax) jvmmemmax = jvmtemp; if (heapDet) outDetails.write(String.format("%.3f\t", (hmemmax - hmemstart) / (1024.0 * 1024.0)) .getBytes("UTF-8")); if (nonHeapDet) outDetails.write(String.format("%.3f\t", nmemmax / (1024.0 * 1024.0)).getBytes( "UTF-8")); if (jvmDet) outDetails.write(String.format("%.3f\t", jvmmemmax / (1024.0 * 1024.0)).getBytes( "UTF-8")); if (heapDet || nonHeapDet || jvmDet) outDetails.write("\n".getBytes("UTF-8")); } } catch (InterruptedException e) { return; } catch (IOException e) { // if this happens it's very bad,,, but it's totally // unexpected - only if there's a problem with closing // stream from ps process throw new RuntimeException(e); } } }; try { if (heapDet || nonHeapDet || jvmDet) outDetails.write(("\n\n" + filename + " (iteration: " + Integer.toString(iteration) + ") \n\n") .getBytes("UTF-8")); if (heapDet) outDetails.write("Hmem\t".getBytes("UTF-8")); if (nonHeapDet) outDetails.write("Nmem\t".getBytes("UTF-8")); if (jvmDet) outDetails.write("JVMmem\t".getBytes("UTF-8")); if (heapDet || nonHeapDet || jvmDet) outDetails.write("\n".getBytes("UTF-8")); } catch (IOException e) { // if this happens it's very bad,,, but it's totally unexpected throw new RuntimeException(e); } LOGGER.info("Current progress in number of experiments performed: " + Integer.toString(currentNumberOfIterations) + " / " + Integer.toString(totalNumberOfIterations) + " (" + Integer.toString((currentNumberOfIterations) * 100 / totalNumberOfIterations) + "%)"); currentNumberOfIterations++; gssim = null; for (int i = 0; i < 5; i++) { Thread.sleep(600); System.gc(); } hmemstart = memoryMXBean.getHeapMemoryUsage().getUsed(); gssim = new DataCenterWorkloadSimulator(); if (heap || nonHeap || jvm) t.start(); timestart = System.currentTimeMillis(); gssim.run(new String[] { filename }); timend = System.currentTimeMillis(); if (heap || nonHeap || jvm) { t.interrupt(); t.join(); if (timePrec) { gssim = null; for (int i = 0; i < 5; i++) System.gc(); gssim = new DataCenterWorkloadSimulator(); timestart = System.currentTimeMillis(); gssim.run(new String[] { filename }); timend = System.currentTimeMillis(); } } result[0] = (timend - timestart) / 1000.0; result[1] = (hmemmax - hmemstart) / (1024.0 * 1024.0); result[2] = nmemmax / (1024.0 * 1024.0); result[3] = jvmmemmax / (1024.0 * 1024.0); return result; } /** * Repeats task number specified in variable "repeats" times * * @param filename * experiment filename * @return the formatted line with results (content depends on * configuration) * @throws InterruptedException * thrown when thread is interrupted during sleep */ private static String repeatTask(String filename) throws InterruptedException { double[] temp; double tavg = 0; double tstdev = 0; double hmavg = 0; double hmstdev = 0; double nmstdev = 0; double nmavg = 0; double jvmmavg = 0; double jvmmstdev = 0; for (int i = 0; i < repeats; i++) { temp = singleTask(filename, i); tavg += temp[0]; tstdev += temp[0] * temp[0]; hmavg += temp[1]; hmstdev += temp[1] * temp[1]; nmavg += temp[2]; nmstdev += temp[2] * temp[2]; jvmmavg += temp[3]; jvmmstdev += temp[3] * temp[3]; } tavg /= repeats; tstdev /= repeats; hmavg /= repeats; hmstdev /= repeats; nmavg /= repeats; nmstdev /= repeats; jvmmavg /= repeats; jvmmstdev /= repeats; tstdev = Math.sqrt(tstdev - tavg * tavg); hmstdev = Math.sqrt(hmstdev - hmavg * hmavg); nmstdev = Math.sqrt(nmstdev - nmavg * nmavg); jvmmstdev = Math.sqrt(jvmmstdev - jvmmavg * jvmmavg); StringBuilder result = new StringBuilder(String.format("%.3f\t%.3f\t", tavg, tstdev)); if (heap) result.append(String.format("%.3f\t%.3f\t", hmavg, hmstdev)); if (nonHeap) result.append(String.format("%.3f\t%.3f\t", nmavg, nmstdev)); if (jvm) result.append(String.format("%.3f\t%.3f\t", jvmmavg, jvmmstdev)); return result.toString(); } /** * Gets the JVM memory. It works only under linux and only if the process * name begins with "java" (so, for instance, it doesn't work when the * application is run under Eclipse SDK). It depends on {@link #memoryInMB} * variable * * @return amount of memory used by JVM process (in bytes) * @throws IOException * Signals that an I/O exception has occurred. */ public static Long getJVMMemory() throws IOException { BufferedReader reader = null; try { Process proc = Runtime .getRuntime() .exec( new String[] { "bash", "-c", "ps u --width=5 | grep java | tr -s ' ' | cut -d ' ' -f 4" }); reader = new BufferedReader(new InputStreamReader(proc .getInputStream())); String line; if (null != (line = reader.readLine())) { return Math.round(Double.parseDouble(line) / 100.0 * memoryInMB * 1024.0 * 1024.0); } return null; } catch (IOException e) { System.out.println(e.getMessage()); return null; } finally { if (reader != null) reader.close(); } } /** * The main method. It reads the properties of analysis from file * "properties/performance.properties" - this path is relative so it's * important from which folder the application is run. Meaning of each * property is described in the .properties file, please do not change it's * content other than parameters' values * * @param args * the arguments of main methods are relative paths to * .properties files with experiments or directories containing * these files (it's not recursive) * @throws IOException * Signals that an I/O exception has occurred. * @throws InterruptedException * fall-through exception which is not possible to occur */ public static void main(String[] args) throws IOException, InterruptedException { LOGGER.info("Reading the properties"); properties.load(new FileInputStream(new File( "properties/performance.properties"))); int noOfBeeps = Integer.parseInt(properties.getProperty("NO_OF_BEEPS", "0")); interval = Integer.parseInt(properties.getProperty("INTERVAL")); repeats = Integer.parseInt(properties.getProperty("REPEATS")); memoryInMB = Integer.parseInt(properties.getProperty("MEMORY_IN_MB")); heap = "simple".equals(properties.getProperty("HEAP_MEMORY")) || "details".equals(properties .getProperty("HEAP_MEMORY")); heapDet = "details".equals(properties.getProperty("HEAP_MEMORY")); nonHeap = "simple".equals(properties.getProperty("NON_HEAP_MEMORY")) || "details".equals(properties .getProperty("NON_HEAP_MEMORY")); nonHeapDet = "details".equals(properties.getProperty("NON_HEAP_MEMORY")); jvm = "simple".equals(properties.getProperty("JVM_MEMORY")) || "details".equals(properties .getProperty("JVM_MEMORY")); jvmDet = "details".equals(properties.getProperty("JVM_MEMORY")); timePrec = (!jvm && !heap && !nonHeap); if ("precisly".equals(properties.getProperty("TIME_MEASURING"))) timePrec = true; LOGGER.info("Preparing file list to perform experiment"); List records = new ArrayList(); File temp; if (args.length < 1) return; for (int i = 0; i < args.length; i++) { if ((new File(args[i])).exists()) if (args[i].endsWith(".properties")) records.add(args[i]); else { temp = new File(args[i]); String[] files = temp.list(); if (files != null) { for (String file : files) { if ((new File(args[i] + "/" + file).exists() && file .endsWith(".properties"))) records.add(args[i] + "/" + file); } } } } totalNumberOfIterations = repeats * records.size(); if (records.size() == 0) return; int counter = 0; File file = null; File fileDetails = null; do { counter++; file = new File("performance_result_" + Integer.toString(counter) + ".txt"); fileDetails = new File("performance_result_" + Integer.toString(counter) + "_details.txt"); } while (file.exists() || fileDetails.exists()); FileOutputStream out = new FileOutputStream(file); if (heapDet || nonHeapDet || jvmDet) outDetails = new FileOutputStream(fileDetails); StringBuilder header = new StringBuilder("Measuring time precisly: "); if (timePrec) header.append("yes\n"); else header.append("no\n"); header.append(String.format("Number of repets: %d\n", repeats)); header.append(String.format( "Interval between memory checking: %d ms\n", interval)); header.append(String.format("System memory: %d MB\n\n", memoryInMB)); header.append("Name\tTavg\tTstdev\t"); if (heap) header.append("HMavg\tHMstdev\t"); if (nonHeap) header.append("NMavg\tNMstdev\t"); if (jvm) header.append("JVMMavg\tJVMMstdev\t"); header.append('\n'); LOGGER.info("Measuring of performance begun"); try { out.write(header.toString().getBytes("UTF-8")); if (heapDet || nonHeapDet || jvmDet) outDetails .write(("Detailed analysis of memory usage with interval: " + interval + "ms\n\n").getBytes("UTF-8")); Collections.sort(records); for (String record : records) { out.write((record + "\t" + repeatTask(record) + "\n") .getBytes("UTF-8")); } } finally { if (out != null) out.close(); if (outDetails != null) outDetails.close(); } LOGGER.info("Measuring of performance ended succesfully"); for (int i = 0; i < noOfBeeps; i++) { Toolkit.getDefaultToolkit().beep(); Thread.sleep(1000); } } }