analysis.postRun.PostRunWindow.java Source code

Java tutorial

Introduction

Here is the source code for analysis.postRun.PostRunWindow.java

Source

/**
 *   This file is part of CReST: The Cloud Research Simulation Toolkit 
 *   Copyright (C) 2011, 2012 John Cartlidge 
 * 
 *   For a full list of contributors, refer to file CONTRIBUTORS.txt 
 *
 *   CReST was developed at the University of Bristol, UK, using 
 *   financial support from the UK's Engineering and Physical 
 *   Sciences Research Council (EPSRC) grant EP/H042644/1 entitled 
 *   "Cloud Computing for Large-Scale Complex IT Systems". Refer to
 *   <http://gow.epsrc.ac.uk/NGBOViewGrant.aspx?GrantRef=EP/H042644/1>
 * 
 *   CReST is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/gpl.txt>.
 *
 *   For further information, contact: 
 *
 *   Dr. John Cartlidge: john@john-cartlidge.co.uk
 *   Department of Computer Science,
 *   University of Bristol, The Merchant Venturers Building,
 *   Woodland Road, Bristol, BS8-1UB, United Kingdom.
 *
 */

package analysis.postRun;

import gui.util.GUIFileReader;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedList;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.filechooser.FileNameExtensionFilter;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYLineAnnotation;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.DefaultXYDataset;

/**
 * Post-run analysis window 
 * 
 * @author Sarah Haswell
 */
@Deprecated
public class PostRunWindow implements ActionListener {

    private static PostRunWindow instance = null;

    private LinkedList<String[]> dataList;
    private DefaultXYDataset failureSet, costSet, cpuSet, servSet, conSet;
    private LinkedList<DefaultXYDataset[]> statsSets;
    private double[][][] failureStatSeries, costStatSeries, cpuStatSeries, servStatSeries, conStatSeries; //arrays of series to plot with error bars as can't remove series from datasets
    private double[][][] failErrSeries, costErrSeries, cpuErrSeries, servErrSeries, conErrSeries;
    private String currFile, parent;

    private JFrame MainFrame;
    private JPanel MainPanel;
    private JPanel ButtonPanel;
    private JTabbedPane tabbedPane;
    private Boolean dcLegend, save;
    private Boolean stats, readStats;

    /**
     * Constructor - creates window and tabs.
     */
    private PostRunWindow() {
        dcLegend = false;
        stats = false;
        readStats = false;
        save = false;

        // Create frame
        MainFrame = new JFrame("Cloudsim Post-Run Analysis");
        MainFrame.setSize(1150, 768);
        MainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        //MainFrame.addComponentListener(this);

        // Create panel
        MainPanel = new JPanel();
        MainPanel.setLayout(new BorderLayout());
        MainFrame.add(MainPanel, BorderLayout.CENTER);

        // Create button panel
        ButtonPanel = new JPanel();
        ButtonPanel.setLayout(new BorderLayout());
        MainFrame.add(ButtonPanel, BorderLayout.NORTH);

        // Create "change log" button
        JButton fileButton = new JButton("Change log");
        fileButton.addActionListener(this);
        ButtonPanel.add(fileButton, BorderLayout.WEST);

        // Create "Compare multiple logs" button
        JButton compButton = new JButton("Compare multiple logs");
        compButton.addActionListener(this);
        ButtonPanel.add(compButton, BorderLayout.CENTER);

        // Create "Save Charts" button
        JButton saveButton = new JButton("Save Charts");
        saveButton.addActionListener(this);
        ButtonPanel.add(saveButton, BorderLayout.EAST);

        // Find out which log file to look at & load the data
        failureSet = new DefaultXYDataset();
        cpuSet = new DefaultXYDataset();
        servSet = new DefaultXYDataset();
        costSet = new DefaultXYDataset();

        chooseFile1();
        //getFileName();
        getData(currFile);

        // Create tabs
        tabbedPane = new JTabbedPane();
        failureTab(true);
        cpuTab(true);
        servTab(true);
        costTab(true);
        consistencyTab(true);
        tabbedPane.setSelectedIndex(0);

        // Add + show window
        MainPanel.add(tabbedPane, BorderLayout.CENTER);
        MainFrame.add(MainPanel, BorderLayout.CENTER);
        MainFrame.add(ButtonPanel, BorderLayout.NORTH);
        MainFrame.setLocationByPlatform(true);
        MainFrame.setVisible(true);
    }

    /**
     * Calls the constructor.
     */
    public static void create() {
        if (instance == null) {
            instance = new PostRunWindow();
        }
    }

    /**
     * Method which returns the current instance of PostRunWindow - useful for redrawing?
     */
    public PostRunWindow getInstance() {
        return instance;
    }

    /**
     * Method which creates the failure graph tab.
     * 
     * @param state
     *             Whether this tab should be included or not.
     */
    private void failureTab(Boolean state) {
        if (state) {
            ChartPanel fPan;
            if (!stats) {
                fPan = getChart(failureSet, "Server Liveness", "Time / Days", "Live Servers / %", dcLegend, true);
            } else {
                fPan = getSChart(failureStatSeries, failErrSeries, "Server Liveness", "Time / Days",
                        "Live Servers / %", dcLegend, true);
            }

            tabbedPane.addTab("Liveness", fPan);
            tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
        } else {
            tabbedPane.remove(tabbedPane.indexOfTab("Liveness"));
        }
    }

    /**
     * Method which creates the cpu utilisation graph tab.
     * 
     * @param state
     *             Whether this tab should be included or not.
     */
    private void cpuTab(Boolean state) {
        if (state) {
            ChartPanel uPan;
            if (!stats) {
                uPan = getChart(cpuSet, "CPU Utilisation", "Time / Days", "CPU Utilisation / %", dcLegend, true);
            } else {
                uPan = getSChart(cpuStatSeries, cpuErrSeries, "CPU Utilisation", "Time / Days",
                        "CPU Utilisation / %", dcLegend, true);
            }

            tabbedPane.addTab("CPU Utilisation", uPan);
            tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
        } else {
            tabbedPane.remove(tabbedPane.indexOfTab("CPU Utilisation"));
        }
    }

    /**
     * Method which creates the services graph tab.
     * 
     * @param state
     *             Whether this tab should be included or not.
     */
    private void servTab(Boolean state) {
        if (state) {
            ChartPanel sPan;
            if (!stats) {
                sPan = getChart(servSet, "Services Running", "Time / Days", "Number of Services", true, false);
            } else {
                sPan = getSChart(servStatSeries, servErrSeries, "Services Running", "Time / Days",
                        "Number of Services", true, false);
            }

            tabbedPane.addTab("Services", sPan);
            tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
        } else {
            tabbedPane.remove(tabbedPane.indexOfTab("Services"));
        }
    }

    /**
     * Method which creates the average cost graph tab.
     * 
     * @param state
     *             Whether this tab should be included or not.
     */
    private void costTab(Boolean state) {
        if (state) {
            ChartPanel cPan;
            if (!stats) {
                cPan = getChart(costSet, "Cost", "Time / Days", "Cost /  per Hour", dcLegend, false);
            } else {
                cPan = getSChart(costStatSeries, costErrSeries, "Cost", "Time / Days", "Cost /  per Hour",
                        dcLegend, false);
            }

            tabbedPane.addTab("Cost", cPan);
            tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
        } else {
            tabbedPane.remove(tabbedPane.indexOfTab("Cost"));
        }
    }

    /**
     * Method which creates the consistency graph tab.
     * 
     * @param state
     *             Whether this tab should be included or not.
     */
    private void consistencyTab(Boolean state) {
        if (state) {
            ChartPanel cnPan;
            if (!stats) {
                cnPan = getChart(conSet, "Inconsistency", "Time / Days", "Inconsistent Nodes / %", dcLegend, true);
            } else {
                cnPan = getSChart(conStatSeries, conErrSeries, "Inconsistency", "Time / Days",
                        "Inconsistent Nodes / %", dcLegend, true);
            }

            tabbedPane.addTab("Inconsistency", cnPan);
            tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
        } else {
            tabbedPane.remove(tabbedPane.indexOfTab("Inconsistency"));
        }
    }

    /**
     * Method which creates and returns a graph in a chartpanel.
     * 
     * @param dset
     *             Which dataset to make a graph of.
     *       title
     *             Title for the graph.
     *      xTitle
     *             Title for the x axis.     
     *       yTitle
     *             Title for the y axis.
     *      legend
     *             Whether or not to include a legend - true if multiple series are to be shown.
     */
    private ChartPanel getChart(DefaultXYDataset dset, String title, String xTitle, String yTitle, Boolean legend,
            Boolean percentage) {
        JFreeChart chart = ChartFactory.createXYLineChart(title, xTitle, yTitle, dset, PlotOrientation.VERTICAL,
                legend, true, false);
        if (percentage) {
            chart.getXYPlot().getRangeAxis().setRange(0.0, 100.0);
        }

        if (save) {
            try {
                //System.out.println("Saving charts.");
                ChartUtilities.saveChartAsPNG(
                        new File(currFile.substring(0, currFile.length() - 4) + title + "Chart"), chart, 1200, 800);
            } catch (IOException e) {
                System.err.println(e.getMessage());
            }
        }
        return new ChartPanel(chart);
    }

    /** Method which creates and returns a graph in a chartpanel.
     * 
     * @param dSeries
     *             Which dataSeries to make a graph of.
     *       title
     *             Title for the graph.
     *      xTitle
     *             Title for the x axis.     
     *       yTitle
     *             Title for the y axis.
     *      legend
     *             Whether or not to include a legend - true if multiple series are to be shown.
     */
    private ChartPanel getSChart(double[][][] dSeries, double[][][] err, String title, String xTitle, String yTitle,
            Boolean legend, Boolean percentage) {

        int seriesNum = dSeries.length;
        DefaultXYDataset temp = new DefaultXYDataset();
        for (int j = 0; j < seriesNum; j++) {
            temp.addSeries(j, dSeries[j]);
        }
        JFreeChart chart = ChartFactory.createXYLineChart(title, xTitle, yTitle, temp, PlotOrientation.VERTICAL,
                legend, true, false);

        if (percentage) {
            chart.getXYPlot().getRangeAxis().setRange(0.0, 100.0);
        }

        for (int i = 0; i < seriesNum; i++) {
            plotErrorBars(chart, dSeries[i], err[i]);
        }

        if (save) {
            try {
                //System.out.println("Saving charts.");
                ChartUtilities.saveChartAsPNG(
                        new File(currFile.substring(0, currFile.length() - 4) + title + "Chart"), chart, 1200, 800);
            } catch (IOException e) {
                System.err.println(e.getMessage());
                //e.printStackTrace();
            }
        }

        return new ChartPanel(chart);
    }

    /** Method which draws error bars from the given series of errors onto the given chart.
     * 
     * @param chart
     *             The chart to draw the error bars on.
     * @param values
     *             The data series which the errors correspond to.
     * @param errors
     *             The data series containing all the error values at times.
     */
    private void plotErrorBars(JFreeChart chart, double[][] values, double[][] errors) {
        Stroke stroke = new BasicStroke();
        Paint paint = Color.black;
        XYPlot plot = chart.getXYPlot();
        if (values.length != errors.length)
            return;
        for (int i = 0; i < values[0].length; i = i + 5) {
            double x = values[0][i];
            double y = values[1][i];
            //double dx = errors[0][i];
            double dy = errors[1][i];
            //System.out.println(dy);
            XYLineAnnotation vertical = new XYLineAnnotation(x, (y - dy), x, (y + dy), stroke, paint);
            plot.addAnnotation(vertical);
            XYLineAnnotation topBar = new XYLineAnnotation(x - 0.25, y + dy, x + 0.25, y + dy, stroke, paint);
            plot.addAnnotation(topBar);
            XYLineAnnotation bottomBar = new XYLineAnnotation(x - 0.25, y - dy, x + 0.25, y - dy, stroke, paint);
            plot.addAnnotation(bottomBar);
        }
    }

    /**
     * Open log file and read lines of data into dataList, then calls makeSeries to format this data (only calls if file read has worked).
     * If the file is one generated from averages, draw error bars.
     * @param fileName
     *                File name to open log/ * .csv
     */
    private void getData(String fileName) {
        dataList = new LinkedList<String[]>();
        String input[];
        try {
            BufferedReader in = new BufferedReader(new FileReader(fileName));
            String strln = new String();
            while ((strln = in.readLine()) != null) {
                input = strln.split(",");
                dataList.add(input);
            }
            in.close();
            if (fileName.contains(",")) {
                //System.out.println("Reading averages");
                readStats = true;
                stats = true;
            }
            makeSeries();
        } catch (IOException e) {
            if (!stats) {
                chooseFile1();
                getData(currFile);
            } else {
                System.err.println("Error: File error - " + fileName);
            }
            //System.err.println("Error: " + e.getMessage());
        }
    }

    /**
     * Method which iterates through dataList building series to use in graphs.
     */
    private void makeSeries() {
        //      System.out.println("in makeSeries, readStats is "+readStats+ " stats is "+stats);
        //reinitialise datasets
        failureSet = new DefaultXYDataset();
        costSet = new DefaultXYDataset();
        cpuSet = new DefaultXYDataset();
        servSet = new DefaultXYDataset();
        conSet = new DefaultXYDataset();

        int l = dataList.size();
        String fails, cpus, costs, cons;
        String[] buffer, sFails, sCPUs, sServs, sCosts, test, servS, sErrs, sCons;
        double ts;

        //find out how many datacentres there are by doing a trial split
        test = dataList.get(0)[1].split(" ");
        int dcs = test.length;
        if (dcs > 1) {
            dcLegend = true;
        }

        double[][][] failureSeries = new double[dcs][2][l];
        double[][][] costSeries = new double[dcs][2][l];
        double[][][] cpuSeries = new double[dcs][2][l];
        double[][][] servSeries = new double[dcs * 4][2][l];
        double[][][] conSeries = new double[dcs][2][l];

        //iterate through list
        for (int k = 0; k < l; k++) {
            buffer = dataList.get(k);
            ts = Double.parseDouble(buffer[0]);

            //Split into useful data & add to series. May be multiple space-separated datacentres.
            sFails = buffer[1].split(" ");
            sCosts = buffer[2].split(" ");
            sCPUs = buffer[3].split(" ");
            sServs = buffer[4].split(" ");
            sCons = buffer[5].split(" ");
            sErrs = new String[dcs];
            if (readStats == true) {
                sErrs = buffer[6].split("'");
            }

            for (int i = 0; i < dcs; i++) { //add a value to the series for each datacentre
                fails = sFails[i];
                fails = (String) fails.subSequence(1, fails.length() - 1);
                failureSeries[i][0][k] = ts;
                failureSeries[i][1][k] = (Double.parseDouble(fails));

                costs = sCosts[i];
                costs = (String) costs.subSequence(1, costs.length() - 1);
                costSeries[i][0][k] = ts;
                costSeries[i][1][k] = Double.parseDouble(costs);

                cpus = sCPUs[i];
                cpus = (String) cpus.subSequence(1, cpus.length() - 1);
                cpuSeries[i][0][k] = ts;
                cpuSeries[i][1][k] = (Double.parseDouble(cpus));

                //services have multiple values
                servS = sServs[i].split("'");
                for (int x = 0; x < 4; x++) {
                    servSeries[i + x][0][k] = ts;
                    servSeries[i + x][1][k] = Double.parseDouble(servS[x + 1]);
                }

                cons = sCons[0];
                cons = (String) cons.subSequence(1, cons.length() - 1);
                conSeries[i][0][k] = ts;
                conSeries[i][1][k] = (Double.parseDouble(cons));

                failErrSeries = new double[dcs][2][l];
                costErrSeries = new double[dcs][2][l];
                cpuErrSeries = new double[dcs][2][l];
                servErrSeries = new double[dcs * 4][2][l];
                conErrSeries = new double[dcs][2][l];

                if (readStats == true) {
                    failErrSeries[i][1][k] = Double.parseDouble(sErrs[1]);
                    failErrSeries[i][0][k] = ts;
                    costErrSeries[i][1][k] = Double.parseDouble(sErrs[2]);
                    costErrSeries[i][0][k] = ts;
                    cpuErrSeries[i][1][k] = Double.parseDouble(sErrs[3]);
                    cpuErrSeries[i][0][k] = ts;

                    servErrSeries[i][1][k] = Double.parseDouble(sErrs[4]);
                    servErrSeries[i][0][k] = ts;
                    servErrSeries[dcs + i][1][k] = Double.parseDouble(sErrs[5]);
                    servErrSeries[dcs + i][0][k] = ts;
                    servErrSeries[(2 * dcs) + i][1][k] = Double.parseDouble(sErrs[6]);
                    servErrSeries[(2 * dcs) + i][0][k] = ts;
                    servErrSeries[(3 * dcs) + i][1][k] = Double.parseDouble(sErrs[7]);
                    servErrSeries[(3 * dcs) + i][0][k] = ts;

                    conErrSeries[i][1][k] = Double.parseDouble(sErrs[8]);
                    conErrSeries[i][0][k] = ts;
                    //System.out.println(sErrs[8]);
                }

            }
        }

        for (int j = 0; j < dcs; j++) {
            failureSet.addSeries("Datacentre " + j, failureSeries[j]);
            costSet.addSeries("Datacentre " + j, costSeries[j]);
            cpuSet.addSeries("Datacentre " + j, cpuSeries[j]);
            servSet.addSeries("Total - Dc " + j, servSeries[j * 4]);
            servSet.addSeries("Complete - Dc " + j, servSeries[(j * 4) + 1]);
            servSet.addSeries("Failed - Dc " + j, servSeries[(j * 4) + 2]);
            servSet.addSeries("Running - Dc " + j, servSeries[(j * 4) + 3]);
            conSet.addSeries("Consistency - Dc " + j, conSeries[j]);
        }

        failureStatSeries = new double[failureSeries.length][failureSeries[0].length][failureSeries[0][0].length];
        costStatSeries = new double[costSeries.length][costSeries[0].length][costSeries[0][0].length];
        cpuStatSeries = new double[cpuSeries.length][cpuSeries[0].length][cpuSeries[0][0].length];
        servStatSeries = new double[servSeries.length][servSeries[0].length * 4][servSeries[0][0].length];
        conStatSeries = new double[conSeries.length][conSeries[0].length * 4][conSeries[0][0].length];

        if (readStats == true) {
            failureStatSeries = failureSeries;
            costStatSeries = costSeries;
            cpuStatSeries = cpuSeries;
            servStatSeries = servSeries;
            conStatSeries = conSeries;
        }
    }

    /**
     * Method which creates a dialogue asking the user to input the name of the log file they wish to view.
     */
    @SuppressWarnings("unused")
    private void getFileName() {
        ImageIcon icon = new ImageIcon();
        Object[] possibilities = null;
        String s = (String) JOptionPane.showInputDialog(MainFrame, "Enter filename of log, looking in folder log.",
                "Inspect Run", JOptionPane.PLAIN_MESSAGE, icon, possibilities, "log/*.csv");

        //If a string was returned, set it as the fileName and call getData.
        if ((s != null) && (s.length() > 0)) {
            //System.out.println("Filename entered: " + s);
            currFile = s;
            return;
        }

        //If you're here, the return value was null/empty.
        JOptionPane.showMessageDialog(MainFrame, "No filename entered.");
    }

    /**
      * Method which creates a dialogue asking the user to input the indexes of the log files they wish to compare.
      */
    private String getFileList() {
        ImageIcon icon = new ImageIcon();
        Object[] possibilities = null;
        String s = (String) JOptionPane.showInputDialog(MainFrame,
                "Enter indexes of log files to compare, separated by commas. e.g. '1,2'", "Compare Log Files",
                JOptionPane.PLAIN_MESSAGE, icon, possibilities, ",");

        //If a string was returned, set it as the fileName and call getData.
        if ((s != null) && (s.length() > 0)) {
            //System.out.println("File indexes entered: " + s);
            return s;
        } else {
            //If you're here, the return value was null/empty.
            JOptionPane.showMessageDialog(MainFrame, "No filename entered.");
            return s;
        }
    }

    /**
      * Method which amalgamates data from several different log files into exciting statistics.
      */
    private void genStats() {
        // Stage 1: Get file names & load all data to statsSet
        String fileList = null;
        while (fileList == null || fileList.equals(",") || (fileList.split(",").length) == 0) { //if fileList isn't useable, ask again
            fileList = getFileList();
        }

        String[] fList = fileList.split(",");
        statsSets = new LinkedList<DefaultXYDataset[]>();
        DefaultXYDataset[] sets = new DefaultXYDataset[4];

        for (int i = 0; i < fList.length; i++) {
            getData("log/log" + fList[i] + ".csv");
            sets = new DefaultXYDataset[5];
            sets[0] = failureSet;
            sets[1] = costSet;
            sets[2] = cpuSet;
            sets[3] = servSet;
            sets[4] = conSet;
            statsSets.add(sets);
            //System.out.println("added "+fList[i]);
        }

        //Stage 2: Go through statsSets and make exciting data. Create a list of arrays, one for averages at each timestep.
        int fNum = statsSets.size();
        int setLength = statsSets.get(0)[0].getItemCount(0);
        //Make set as long as the shortest run to avoid array size errors.
        for (int z = 0; z < fNum; z++) {
            setLength = Math.min(setLength, statsSets.get(z)[0].getItemCount(0));
        }

        // If files have different number of datasets calculations won't work & comparison isn't meaningful.
        int dcNum = statsSets.get(0)[0].getSeriesCount();
        for (int y = 0; y < fNum; y++) {
            if (dcNum != statsSets.get(y)[0].getSeriesCount()) {
                System.err.println("Error: Datacentre counts not equal.");
                System.exit(1); //TODO: change this to not exit but stop everything?
            }
        }

        LinkedList<String[]> writeList = new LinkedList<String[]>();

        //loop through time/length of datacentre
        for (int i = 0; i < setLength; i++) {
            String[] wLine = new String[1 + (6 * dcNum)]; //make a new string array for each line

            //loop through each datacentre
            for (int j = 0; j < dcNum; j++) {

                double time = statsSets.get(0)[0].getXValue(0, i); //get timestamp from one of the files
                double[] fVals, costVals, CPUVals, serv0Vals, serv1Vals, serv2Vals, serv3Vals, conVals;
                fVals = new double[fNum];
                costVals = new double[fNum];
                CPUVals = new double[fNum];
                serv0Vals = new double[fNum];
                serv1Vals = new double[fNum];
                serv2Vals = new double[fNum];
                serv3Vals = new double[fNum];
                conVals = new double[fNum];

                //loop through files adding values from each file to arrays
                for (int k = 0; k < fNum; k++) {
                    fVals[k] = statsSets.get(k)[0].getYValue(j, i); //failures series item i in datacentre j at time k
                    costVals[k] = statsSets.get(k)[1].getYValue(j, i); //cost series item i in datacentre j at time k
                    CPUVals[k] = statsSets.get(k)[2].getYValue(j, i); //cpu series item i in datacentre j at time k
                    serv0Vals[k] = statsSets.get(k)[3].getYValue(j * 4, i); //services series item i in datacentre j at time k
                    serv1Vals[k] = statsSets.get(k)[3].getYValue((j * 4) + 1, i); //services series item i in datacentre j at time k - complete
                    serv2Vals[k] = statsSets.get(k)[3].getYValue((j * 4) + 2, i); //services series item i in datacentre j at time k - failed      
                    serv3Vals[k] = statsSets.get(k)[3].getYValue((j * 4) + 3, i); //services series item i in datacentre j at time k - running
                    conVals[k] = statsSets.get(k)[4].getYValue(j, i);
                }

                //get average & standard deviation of values collected, store them so don't have to calculate twice.
                double[] fAvs = getAvs(fVals);
                double[] costAvs = getAvs(costVals);
                double[] CPUAvs = getAvs(CPUVals);
                double[] serv0Avs = getAvs(serv0Vals);
                double[] serv1Avs = getAvs(serv1Vals);
                double[] serv2Avs = getAvs(serv2Vals);
                double[] serv3Avs = getAvs(serv3Vals);
                double[] conAvs = getAvs(conVals);

                //String.format("%.3g", #) rounds the number to 3dp - doesn't work well on large numbers
                // Stick calculated values into wLine for printing, space to leave room for data on other datacentres.
                wLine[0] = time + ",";
                wLine[1 + j] = "'" + String.format("%.3g", fAvs[0]) + "' ";
                if (j == dcNum - 1) {
                    wLine[1 + j] = wLine[1 + j] + ",";
                }
                wLine[(1 + dcNum) + j] = "'" + String.format("%.3g", costAvs[0]) + "' ";
                if (j == dcNum - 1) {
                    wLine[1 + dcNum + j] = wLine[1 + dcNum + j] + ",";
                }
                wLine[1 + (2 * dcNum) + j] = "'" + String.format("%.3g", CPUAvs[0]) + "' ";
                if (j == dcNum - 1) {
                    wLine[1 + (2 * dcNum) + j] = wLine[1 + (2 * dcNum) + j] + ",";
                }
                wLine[1 + (3 * dcNum) + j] = "'" + String.format("%.3g", serv0Avs[0]) + "'"
                        + String.format("%.3g", serv1Avs[0]) + "'" + String.format("%.3g", serv2Avs[0]) + "'"
                        + String.format("%.3g", serv3Avs[0]) + "' ";
                if (j == dcNum - 1) {
                    wLine[1 + (3 * dcNum) + j] = wLine[1 + (3 * dcNum) + j] + ",";
                }
                wLine[1 + (4 * dcNum) + j] = "'" + String.format("%.3g", conAvs[0]) + "' ";
                if (j == dcNum - 1) {
                    wLine[1 + (4 * dcNum) + j] = wLine[1 + (4 * dcNum) + j] + ",";
                }
                wLine[1 + (5 * dcNum) + j] = "'" + String.format("%.3g", fAvs[1]) + "'"
                        + String.format("%.3g", costAvs[1]) + "'" + String.format("%.3g", CPUAvs[1]) + "'"
                        + String.format("%.3g", serv0Avs[1]) + "'" + String.format("%.3g", serv1Avs[1]) + "'"
                        + String.format("%.3g", serv2Avs[1]) + "'" + String.format("%.3g", serv3Avs[1]) + "'"
                        + String.format("%.3g", conAvs[1]) + "' ";
                if (j == dcNum - 1) {
                    wLine[1 + (5 * dcNum) + j] = wLine[2 + (4 * dcNum) + j] + ",";
                }
            }
            writeList.add(wLine);
        }

        String filePath = parent + "/" + fileList + ".csv";
        writeToFile(filePath, writeList);
        readStats = true;
        //System.out.println(readStats);
        getData(filePath);
    }

    /**
     * Method which calculates the mean and standard deviation of an array of doubles.
     * @param vals
     *             Array of doubles to find average and sd of.
     */
    private double[] getAvs(double[] vals) {
        double[] avSd = new double[2];
        double tot = 0;
        for (int i = 0; i < vals.length; i++) {
            tot = tot + vals[i];
        }
        avSd[0] = tot / vals.length;
        double total = 0;
        double buff = 0;
        for (int j = 0; j < vals.length; j++) {
            buff = vals[j] - avSd[0];
            total = total + Math.pow(buff, 2);
        }
        avSd[1] = Math.sqrt((total / vals.length));
        //      System.out.println(avSd[0]);
        //      System.out.println(avSd[1]);
        return avSd;
    }

    /**
     * Method which writes the text passed to the file specified.
     * @param fileName
     *                Which file to write to.
     *         text
     *                List of string arrays to write.
     */
    private void writeToFile(String fileName, LinkedList<String[]> text) {
        //append text to file fileName
        try {
            BufferedWriter b = new BufferedWriter(new FileWriter(fileName));
            for (int k = 0; k < text.size(); k++) {
                for (int l = 0; l < text.get(0).length; l++) {
                    b.append(text.get(k)[l]);
                }
                if (k != (text.size() - 1)) {
                    b.append('\n');
                }
            }
            b.close();
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
    }

    /**
      * Method which redraws all tabs.
      */
    private void redrawAll() {
        MainPanel.remove(tabbedPane);
        tabbedPane = new JTabbedPane();
        failureTab(true);
        cpuTab(true);
        servTab(true);
        costTab(true);
        consistencyTab(true);
        tabbedPane.setSelectedIndex(0);
        MainPanel.add(tabbedPane, BorderLayout.CENTER);
        MainPanel.revalidate(); //makes it actually appear on redraw!
    }

    /**
     * Method which opens a file chooser so the user can select a log to view.
     */
    //   void chooseFile()
    //   {
    //      JFileChooser fc = new JFileChooser();
    //      fc.setDialogTitle("Select Log");
    //      fc.setCurrentDirectory(new File(System.getProperty("user.dir") + "/log"));
    //      fc.setAcceptAllFileFilterUsed(false);
    //      FileFilter filter = new FileNameExtensionFilter(" file", "csv");
    //      fc.addChoosableFileFilter(filter);
    //      int rVal = fc.showOpenDialog(null);
    //
    //      if (rVal == JFileChooser.APPROVE_OPTION)
    //      {
    //         currFile = fc.getSelectedFile().getAbsolutePath();
    //         //System.out.println(currFile);
    //         parent=fc.getSelectedFile().getParent();
    //         System.out.println(parent);
    //         getData(fc.getSelectedFile().getAbsolutePath());
    //      }
    //      else {
    //         System.exit(0);
    //      }
    //   }

    //dialogTitle="select log"
    //directoryPath="/log"
    //allFileFilterUsed=false
    //description= "file"
    //FileNameExtensionFilter
    //extension = "csv"
    //FileFilter filter = new FileNameExtensionFilter(" file", "csv");
    void chooseFile1() {
        JFileChooser fc = GUIFileReader.getFileChooser("Select Log", "/log",
                new FileNameExtensionFilter(" file", "csv"));

        int rVal = fc.showOpenDialog(null);

        if (rVal == JFileChooser.APPROVE_OPTION) {
            currFile = fc.getSelectedFile().getAbsolutePath();
            //System.out.println(currFile);
            parent = fc.getSelectedFile().getParent();
            System.out.println(parent);
            getData(fc.getSelectedFile().getAbsolutePath());
        } else {
            System.exit(0);
        }
    }

    /**
      * Method which handles any action events, i.e. when the log chooser button is pressed.
      * @param e
      *          Event thrown by button.
      */
    @Override
    public void actionPerformed(ActionEvent e) {

        String buttonText = e.getActionCommand();
        if (buttonText.equals("Change log")) //if update button is pressed, update
        {
            chooseFile1();
            //getFileName();
            redrawAll();
        } else if (buttonText.equals("Compare multiple logs")) {
            stats = true;
            genStats();
            redrawAll();
            readStats = false;
            stats = false;
        } else if (buttonText.equals("Save Charts")) {
            save = true;
            redrawAll();
            save = false;
        }
    }

}