org.apache.phoenix.pherf.util.GoogleChartGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.phoenix.pherf.util.GoogleChartGenerator.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.phoenix.pherf.util;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.phoenix.pherf.PherfConstants;
import org.apache.phoenix.pherf.result.file.ResultFileDetails;

/**
 * Compare results based on set threshold and render results as Google Charts
 */
public class GoogleChartGenerator {

    private String[] labels;
    private final Map<String, DataNode> datanodes = new TreeMap<String, DataNode>();
    private final PherfConstants constants = PherfConstants.create();
    private final String resultDir = constants.getProperty("pherf.default.results.dir");
    private final double threshold = Double
            .parseDouble(constants.getProperty("pherf.default.comparison.threshold"));

    public GoogleChartGenerator(String labels) {
        this.setLabels(labels);
    }

    String[] getLabels() {
        return labels;
    }

    void setLabels(String[] labels) {
        this.labels = labels;
    }

    void setLabels(String labels) {
        this.labels = labels.split(",");
    }

    public void readAndRender() {
        try {
            for (String label : labels) {
                read(label);
            }
            renderAsGoogleChartsHTML();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Reads aggregate file and convert it to DataNode 
     * @param label
     * @throws Exception
     */
    private void read(String label) throws Exception {
        String resultFileName = resultDir + PherfConstants.PATH_SEPARATOR + PherfConstants.RESULT_PREFIX + label
                + ResultFileDetails.CSV_AGGREGATE_PERFORMANCE.getExtension();

        FileReader in = new FileReader(resultFileName);
        final CSVParser parser = new CSVParser(in, CSVFormat.DEFAULT.withHeader());

        for (CSVRecord record : parser) {
            String group = record.get("QUERY_GROUP");
            String query = record.get("QUERY");
            String explain = record.get("EXPLAIN_PLAN");
            String tenantId = record.get("TENANT_ID");
            long avgTime = Long.parseLong(record.get("AVG_TIME_MS"));
            long minTime = Long.parseLong(record.get("AVG_MIN_TIME_MS"));
            long numRuns = Long.parseLong(record.get("RUN_COUNT"));
            long rowCount = Long.parseLong(record.get("RESULT_ROW_COUNT"));
            Node node = new Node(minTime, avgTime, numRuns, explain, query, tenantId, label, rowCount);

            if (datanodes.containsKey(group)) {
                datanodes.get(group).getDataSet().put(label, node);
            } else {
                datanodes.put(group, new DataNode(label, node));
            }
        }
        parser.close();
    }

    /**
     * Verifies if the first result is within the set 
     * threshold of pherf.default.comparison.threshold
     * set in pherf.properties files
     * @param threshold
     * @return
     */
    private boolean verifyWithinThreshold(double threshold) {
        long resetTimeToCompare = -1;
        long timeToCompare = resetTimeToCompare;
        for (Map.Entry<String, DataNode> dn : datanodes.entrySet()) {
            for (Map.Entry<String, Node> node : dn.getValue().getDataSet().entrySet()) {
                if (timeToCompare == -1) {
                    timeToCompare = node.getValue().getMinTime();
                    if (timeToCompare < 10) { // extremely small query time in ms therefore don't compare
                        return true;
                    }
                }
                if ((((double) (timeToCompare - node.getValue().getMinTime()))
                        / (double) node.getValue().getMinTime()) > threshold) {
                    return false;
                }
            }
            timeToCompare = resetTimeToCompare;
        }
        return true;
    }

    /**
     * Render results as Google charts
     * @throws FileNotFoundException
     * @throws UnsupportedEncodingException
     */
    private void renderAsGoogleChartsHTML() throws FileNotFoundException, UnsupportedEncodingException {
        String lastKeyPrefix = "";
        StringBuffer sb = new StringBuffer();
        for (String label : labels) {
            sb.append("dataTable.addColumn('number', '" + label + "');\n");
            sb.append("dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});\n");
        }
        sb.append("dataTable.addRows([\n");
        for (Map.Entry<String, DataNode> dn : datanodes.entrySet()) {
            String currentKeyPrefix = dn.getKey().substring(0, dn.getKey().indexOf('|'));
            if (!lastKeyPrefix.equalsIgnoreCase(currentKeyPrefix) && lastKeyPrefix != "") {
                sb.append(getBlankRow());
            }
            lastKeyPrefix = currentKeyPrefix;
            sb.append("['" + dn.getKey() + "'");
            for (Map.Entry<String, Node> nodeSet : dn.getValue().getDataSet().entrySet()) {
                sb.append(", " + nodeSet.getValue().getMinTime());
                sb.append(",'" + getToolTipAsHTML(dn.getValue().getDataSet()) + "'");
            }
            sb.append("],\n");
        }
        String summaryFile = PherfConstants.create().getProperty("pherf.default.summary.file");
        String title = labels[0];
        PrintWriter writer = new PrintWriter(summaryFile, "UTF-8");

        writer.println(StaticGoogleChartsRenderingData.HEADER.replace("[title]", title));
        writer.println(sb.substring(0, sb.length() - 2) + "\n]);");
        String thresholdString = Math.round((threshold * 100)) + "%";
        String footer = StaticGoogleChartsRenderingData.FOOTER.replace("[summary]",
                ((verifyWithinThreshold(threshold) == true ? "<font color=green>PASSED | Results are within "
                        : "<font color=red>FAILED | Results are outside ")) + "set threshold of " + thresholdString
                        + "</font><br>" + new SimpleDateFormat("yyyy/MM/dd ha z").format(new Date()));
        footer = footer.replace("[title]", title);
        writer.println(footer);
        writer.close();
    }

    /**
     * Render a blank Google charts row
     * @return
     */
    private String getBlankRow() {
        String ret = "['" + new String(new char[60]).replace("\0", ".") + "'";
        for (int i = 0; i < labels.length; i++)
            ret += ",0,''";
        ret += "],";
        return ret;
    }

    /**
     * Render tooltip as HTML table
     * @param nodeDataSet
     * @return
     */
    private String getToolTipAsHTML(Map<String, Node> nodeDataSet) {
        String ret = "<table width=1000 cellpadding=1 cellspacing=0 border=0 bgcolor=#F4F4F4><tr>";
        for (Map.Entry<String, Node> nodeSet : nodeDataSet.entrySet())
            ret += "<td>" + getToolText(nodeSet.getValue()) + "</td>";
        return ret + "</tr></table>";
    }

    /**
     * Get tooltip for node
     * @param node
     * @return
     */
    private String getToolText(Node node) {
        return node.getLabelAsHTML() + node.getAvgTimeAsHTML() + node.getMinTimeAsHTML() + node.getNumRunsAsHTML()
                + node.getRowCountAsHTML() + node.getExplainPlanAsHTML() + node.getQueryAsHTML();
    }

    /**
     * DataNode to store results to render and compare 
     */
    class DataNode {
        private Map<String, Node> dataSet = new LinkedHashMap<String, Node>();

        public DataNode(String label, Node node) {
            this.getDataSet().put(label, node);
        }

        public Map<String, Node> getDataSet() {
            return dataSet;
        }

        public void setDataSet(Map<String, Node> dataSet) {
            this.dataSet = dataSet;
        }
    }

    class Node {
        private String explainPlan;
        private String query;
        private String tenantId;
        private long minTime;
        private long avgTime;
        private long numRuns;
        private long rowCount;
        private String label;
        private DecimalFormat df = new DecimalFormat("#.#");

        public Node(long minTime, long avgTime, long numRuns, String explainPlan, String query, String tenantId,
                String label, long rowCount) {
            this.setMinTime(minTime);
            this.setAvgTime(avgTime);
            this.setNumRuns(numRuns);
            this.setExplainPlan(explainPlan);
            this.setQuery(query);
            this.setTenantId(tenantId);
            this.setLabel(label);
            this.setRowCount(rowCount);
        }

        String getExplainPlan() {
            return explainPlan;
        }

        String getExplainPlanAsHTML() {
            return "</br><font face=arial size=1><b>EXPLAIN PLAN </b>" + explainPlan.replace("'", "")
                    + "</font><br>";
        }

        void setExplainPlan(String explainPlan) {
            this.explainPlan = explainPlan;
        }

        long getMinTime() {
            if (minTime <= 2)
                return 2;
            else
                return minTime;
        }

        public String getMinTimeAsHTML() {
            return "<font face=arial size=1><b>MIN TIME </b></font><font face=arial size=3>" + minTime + " ms ("
                    + df.format((double) minTime / 1000) + " sec)</font><br>";
        }

        void setMinTime(long minTime) {
            this.minTime = minTime;
        }

        long getAvgTime() {
            return avgTime;
        }

        public String getAvgTimeAsHTML() {
            return "<font face=arial size=1><b>AVERAGE TIME </b></font><font face=arial size=3>" + avgTime + " ms ("
                    + df.format((double) avgTime / 1000) + " sec)</font><br>";
        }

        void setAvgTime(long avgTime) {
            this.avgTime = avgTime;
        }

        public long getNumRuns() {
            return numRuns;
        }

        public String getNumRunsAsHTML() {
            return "<font face=arial size=1><b>NUMBER OF RUNS </b></font><font face=arial size=3>" + numRuns
                    + "</font><br>";
        }

        public void setNumRuns(long numRuns) {
            this.numRuns = numRuns;
        }

        public String getQuery() {
            return query;
        }

        public String getQueryAsHTML() {
            return "<br><font face=arial size=1><b>QUERY </b>" + query.replace("'", "") + " (TENANT ID: "
                    + getTenantId() + ")</font><br>";
        }

        public void setQuery(String query) {
            this.query = query;
        }

        public String getTenantId() {
            return tenantId;
        }

        public void setTenantId(String tenantId) {
            this.tenantId = tenantId;
        }

        public String getLabel() {
            return label;
        }

        public String getLabelAsHTML() {
            return "<font face=arial size=4 color=#666699>" + label + "</font><br>";
        }

        public void setLabel(String label) {
            this.label = label;
        }

        public long getRowCount() {
            return rowCount;
        }

        public String getRowCountAsHTML() {
            return "<font face=arial size=1><b>RESULT ROW COUNT </b></font><font face=arial size=3>" + rowCount
                    + "</font><br>";
        }

        public void setRowCount(long rowCount) {
            this.rowCount = rowCount;
        }
    }

    static class StaticGoogleChartsRenderingData {
        public static String HEADER = "<html><head><title>[title]</title>"
                + "<script type='text/javascript' src='https://www.google.com/jsapi'></script>"
                + "<script type='text/javascript'>google.load('visualization', '1', {packages: ['corechart', 'bar']});google.setOnLoadCallback(drawMaterial);"
                + "function drawMaterial() {var dataTable = new google.visualization.DataTable();dataTable.addColumn('string', 'Query');";
        public static String FOOTER = "var options = {title: '',titleTextStyle: {color: 'gray', fontName: 'Raleway', fontSize: '24'},hAxis: {title: 'Minimum query time for all runs in milli-seconds (ms) | Scaled logrithmic | Hover to see details',titleTextStyle: {italic: false,fontName: 'arial', fontSize: '12'},"
                + "logScale: true,minValue: 0,textStyle: { fontName: 'arial', fontSize: '14'},},vAxis: {textStyle: {fontName: 'arial', fontSize: '12', fontWidth: 'normal', paddingRight: '100',marginRight: '100'}},"
                + "chartArea: {left:300, width: 500, right: 400, top: 50, height: 700},"
                + "legend:{textStyle:{fontSize:'13', fontName:'arial'}},"
                + "tooltip: {isHtml: true},width: 1200,height: 800,"
                + "bars:'horizontal',bar: { groupWidth: '75%' },colors: ['#E1A5A9', '#A9A5BC', '#A9A5E1']};"
                + "var material = new google.visualization.BarChart(document.getElementById('chart_div'));"
                + "material.draw(dataTable, options);}"
                + "</script></head><body><b><font face=raleway size=5>PHERFED [title]</font><br><font face=raleway size=4>[summary]</font></b><div id='chart_div' style='margin:0px;padding:0px;'></div></body></html>";
    }
}