ddf.metrics.reporting.internal.rrd4j.RrdMetricsRetrieverTest.java Source code

Java tutorial

Introduction

Here is the source code for ddf.metrics.reporting.internal.rrd4j.RrdMetricsRetrieverTest.java

Source

/**
 * Copyright (c) Codice Foundation
 * <p>
 * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or any later version.
 * <p>
 * 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
 * Lesser General Public License for more details. A copy of the GNU Lesser General Public License
 * is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package ddf.metrics.reporting.internal.rrd4j;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Random;

import org.apache.poi.hslf.model.AutoShape;
import org.apache.poi.hslf.model.Picture;
import org.apache.poi.hslf.model.Shape;
import org.apache.poi.hslf.model.Slide;
import org.apache.poi.hslf.model.TextBox;
import org.apache.poi.hslf.usermodel.PictureData;
import org.apache.poi.hslf.usermodel.SlideShow;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.custommonkey.xmlunit.XMLTestCase;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.junit.After;
import org.junit.Test;
import org.rrd4j.ConsolFun;
import org.rrd4j.DsType;
import org.rrd4j.core.FetchData;
import org.rrd4j.core.FetchRequest;
import org.rrd4j.core.RrdDb;
import org.rrd4j.core.RrdDef;
import org.rrd4j.core.Sample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ddf.metrics.reporting.internal.MetricsGraphException;
import ddf.metrics.reporting.internal.MetricsRetriever;

public class RrdMetricsRetrieverTest extends XMLTestCase {
    private static final transient Logger LOGGER = LoggerFactory.getLogger(RrdMetricsRetrieverTest.class);

    private static final String TEST_DIR = "target/";

    private static final String RRD_FILE_EXTENSION = ".rrd";

    private static final long ONE_DAY_IN_SECONDS = 24 * 60 * 60;

    private static final long START_TIME = 900000000L;

    private static final int RRD_STEP = 60;

    private static final String MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
            "Nov", "Dec" };

    private RrdDb rrdDb;

    @After
    public void tearDown() throws Exception {

        // Delete all .png files in test directory to ensure starting with clean directory
        FilenameFilter pngFilter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
                if (name.endsWith(".rrd") || name.endsWith(".png")) {
                    return true;
                }

                return false;
            }
        };

        File testDir = new File(TEST_DIR);
        File[] fileList = testDir.listFiles(pngFilter);
        if (null != fileList) {
            for (File file : fileList) {
                if (file.isFile()) {
                    file.delete();
                }
            }
        }
    }

    @Test
    public void testMetricsGraphWithGauge() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Gauge" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).dsType(DsType.GAUGE).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        byte[] metricsGraph = metricsRetriever.createGraph("Query Reponse Time", rrdFilename, START_TIME, endTime);

        assertThat(metricsGraph, not(nullValue()));
        assertThat(metricsGraph.length, is(greaterThan(0)));

        // NOTE: RrdGraph provides no way to programmatically verify the title and axis label were
        // set, i.e., no getters
        // All we can verify is that this alternate createGraph() method returns a byte array of the
        // graph image. Only
        // visual inspection can verify the graph's accurracy, title, and axis labels.
        metricsGraph = metricsRetriever.createGraph("Query Reponse Time", rrdFilename, START_TIME, endTime,
                "My Vertical Axis Label", "My Title");

        assertThat(metricsGraph, not(nullValue()));
        assertThat(metricsGraph.length, is(greaterThan(0)));
    }

    @Test
    public void testMetricsGraphWithCounter() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Counter" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        byte[] metricsGraph = metricsRetriever.createGraph("Query Count", rrdFilename, START_TIME, endTime);

        assertThat(metricsGraph, not(nullValue()));
        assertThat(metricsGraph.length, is(greaterThan(0)));

        // NOTE: RrdGraph provides no way to programmatically verify the title and axis label were
        // set, i.e., no getters
        // All we can verify is that this alternate createGraph() method returns a byte array of the
        // graph image. Only
        // visual inspection can verify the graph's accurracy, title, and axis labels.
        metricsGraph = metricsRetriever.createGraph("Query Count", rrdFilename, START_TIME, endTime,
                "My Vertical Axis Label", "My Title");

        assertThat(metricsGraph, not(nullValue()));
        assertThat(metricsGraph.length, is(greaterThan(0)));
    }

    @Test
    public void testMetricsJsonDataWithCounter() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Counter" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        String json = metricsRetriever.createJsonData("queryCount", rrdFilename, START_TIME, endTime);

        JSONParser parser = new JSONParser();
        JSONObject jsonObj = (JSONObject) parser.parse(json);

        // Verify the title, totalCount, and data (i.e., samples) are present
        assertThat(jsonObj.size(), equalTo(3));
        assertThat(jsonObj.get("title"), not(nullValue()));
        assertThat(jsonObj.get("totalCount"), not(nullValue()));

        // Verify 2 samples were retrieved from the RRD file and put in the JSON fetch results
        JSONArray samples = (JSONArray) jsonObj.get("data");
        assertThat(samples.size(), equalTo(6)); // 6 because that's the max num rows configured for
        // the RRD file

        // Verify each retrieved sample has a timestamp and value
        for (int i = 0; i < samples.size(); i++) {
            JSONObject sample = (JSONObject) samples.get(i);
            LOGGER.debug("timestamp = {},   value= {}", (String) sample.get("timestamp"), sample.get("value"));
            assertThat(sample.get("timestamp"), not(nullValue()));
            assertThat(sample.get("value"), not(nullValue()));
        }
    }

    @Test
    public void testMetricsJsonDataWithGauge() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Gauge" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).dsType(DsType.GAUGE).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        String json = metricsRetriever.createJsonData("queryCount", rrdFilename, START_TIME, endTime);

        JSONParser parser = new JSONParser();
        JSONObject jsonObj = (JSONObject) parser.parse(json);

        // Verify the title, and data (i.e., samples) are present
        assertThat(jsonObj.size(), equalTo(2));
        assertThat(jsonObj.get("title"), not(nullValue()));

        // Verify 2 samples were retrieved from the RRD file and put in the JSON fetch results
        JSONArray samples = (JSONArray) jsonObj.get("data");
        assertThat(samples.size(), equalTo(6)); // 6 because that's the max num rows configured for
        // the RRD file

        // Verify each retrieved sample has a timestamp and value
        for (int i = 0; i < samples.size(); i++) {
            JSONObject sample = (JSONObject) samples.get(i);
            LOGGER.debug("timestamp = {}, value = {}", (String) sample.get("timestamp"), sample.get("value"));
            assertThat(sample.get("timestamp"), not(nullValue()));
            assertThat(sample.get("value"), not(nullValue()));
        }
    }

    @Test
    public void testMetricsCsvDataWithCounter() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Counter" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        String csv = metricsRetriever.createCsvData(rrdFilename, START_TIME, endTime);

        // Break up CSV data into its individual lines
        // Each line should have 2 parts (cells)
        // The first line should be the column headers
        String[] csvLines = csv.split("\n");

        // Verify that number of lines should be the column headers + (number of samples - 1)
        // Since 3 samples were taken, but only 2 are added to the RRD file due to averaging,
        // expect 2 lines of data and the one line of column headers.
        assertThat(csvLines.length, equalTo(7));
        assertThat(csvLines[0], equalTo("Timestamp,Value")); // column headers
        String[] cells = csvLines[1].split(",");
        assertThat(cells.length, equalTo(2)); // each line of data has the 2 values
    }

    @Test
    public void testMetricsCsvDataWithGauge() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Gauge" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).dsType(DsType.GAUGE).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        String csv = metricsRetriever.createCsvData(rrdFilename, START_TIME, endTime);

        // Break up CSV data into its individual lines
        // Each line should have 2 parts (cells)
        // The first line should be the column headers
        String[] csvLines = csv.split("\n");

        // Verify that number of lines should be the column headers + (number of samples - 1)
        // Since 3 samples were taken, but only 2 are added to the RRD file due to averaging,
        // expect 2 lines of data and the one line of column headers.
        assertThat(csvLines.length, equalTo(7));
        assertThat(csvLines[0], equalTo("Timestamp,Value")); // column headers
        String[] cells = csvLines[1].split(",");
        assertThat(cells.length, equalTo(2)); // each line of data has the 2 values
    }

    @Test
    public void testMetricsXlsDataWithCounter() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Counter" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        OutputStream os = metricsRetriever.createXlsData("queryCount", rrdFilename, START_TIME, endTime);
        InputStream xls = new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
        assertThat(xls, not(nullValue()));

        HSSFWorkbook wb = new HSSFWorkbook(xls);
        assertThat(wb.getNumberOfSheets(), equalTo(1));

        HSSFSheet sheet = wb.getSheet("Query Count");
        if (null != sheet) {
            assertThat(sheet, not(nullValue()));
            verifyWorksheet(sheet, "Query Count", 6, true);
        } else {
            fail();
        }
    }

    @Test
    public void testGetMetricDataTimeRangeFiltering() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Counter_TimeRange" + RRD_FILE_EXTENSION;
        long startTime = 1430463600L; // May 1st, 2015 00:00:00
        long endTime = 1433141999L; // May 31st, 2015 23:59:59
        int minutes = 31 * 24 * 60; // minutes between startTime and endTime
        int numSamples = minutes + 10; // go into the next month
        new RrdFileBuilder().startTime(startTime - RRD_STEP * 2).rrdFileName(rrdFilename).numRows(numSamples)
                .numSamples(numSamples).dsType(DsType.GAUGE).build();

        RrdMetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        MetricData metricData = metricsRetriever.getMetricData(rrdFilename, startTime, endTime);
        assertThat(metricData.getTimestamps().size(), equalTo(minutes));
        assertThat(metricData.getTimestamps().get(minutes - 1), lessThanOrEqualTo(endTime));
        assertThat(metricData.getTimestamps().get(0), greaterThanOrEqualTo(startTime));
        metricData = metricsRetriever.getMetricData(rrdFilename, startTime - RRD_STEP * 2, endTime + RRD_STEP * 8);
        assertThat(metricData.getTimestamps().get(0), lessThan(startTime));
        assertThat(metricData.getTimestamps().get(numSamples - 1), greaterThan(endTime));
        assertThat(metricData.getTimestamps().size(), equalTo(numSamples));
    }

    @Test
    public void testMetricsXlsDataWithGauge() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Gauge" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).dsType(DsType.GAUGE).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        OutputStream os = metricsRetriever.createXlsData("queryCount", rrdFilename, START_TIME, endTime);
        InputStream xls = new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
        assertThat(xls, not(nullValue()));

        HSSFWorkbook wb = new HSSFWorkbook(xls);
        assertThat(wb.getNumberOfSheets(), equalTo(1));
        HSSFSheet sheet = wb.getSheet("Query Count");
        if (null != sheet) {
            assertThat(sheet, not(nullValue()));
            verifyWorksheet(sheet, "Query Count", 6, false);
        } else {
            fail();
        }
    }

    @Test
    public void testMetricsXmlDataWithCounter() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Counter" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        String xml = metricsRetriever.createXmlData("queryCount", rrdFilename, START_TIME, endTime);
        assertXpathExists("/queryCount", xml);
        assertXpathExists("/queryCount/title", xml);
        assertXpathExists("/queryCount/data", xml);
        assertXpathExists("/queryCount/data/sample", xml);
        assertXpathExists("/queryCount/data/sample/timestamp", xml);
        assertXpathExists("/queryCount/data/sample/value", xml);
        assertXpathExists("/queryCount/data/totalCount", xml);
    }

    @Test
    public void testMetricsXmlDataWithGauge() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Gauge" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).dsType(DsType.GAUGE).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        String xml = metricsRetriever.createXmlData("queryCount", rrdFilename, START_TIME, endTime);
        assertXpathExists("/queryCount", xml);
        assertXpathExists("/queryCount/title", xml);
        assertXpathExists("/queryCount/data", xml);
        assertXpathExists("/queryCount/data/sample", xml);
        assertXpathExists("/queryCount/data/sample/timestamp", xml);
        assertXpathExists("/queryCount/data/sample/value", xml);
        assertXpathNotExists("/queryCount/data/totalCount", xml);
    }

    @Test
    public void testMetricsXlsReport() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Counter" + RRD_FILE_EXTENSION;
        new RrdFileBuilder().rrdFileName(rrdFilename).build();

        rrdFilename = TEST_DIR + "queryCount_Gauge" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).dsType(DsType.GAUGE).build();

        List<String> metricNames = new ArrayList<String>();
        metricNames.add("queryCount_Counter");
        metricNames.add("queryCount_Gauge");

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        OutputStream os = metricsRetriever.createXlsReport(metricNames, TEST_DIR, START_TIME, endTime, null);
        InputStream xls = new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
        assertThat(xls, not(nullValue()));

        HSSFWorkbook wb = new HSSFWorkbook(xls);
        assertThat(wb.getNumberOfSheets(), equalTo(2));

        HSSFSheet sheet = wb.getSheetAt(0);
        assertThat(sheet, not(nullValue()));
        verifyWorksheet(sheet, wb.getSheetName(0), 6, true);

        sheet = wb.getSheetAt(1);
        assertThat(sheet, not(nullValue()));
        verifyWorksheet(sheet, wb.getSheetName(1), 6, false);
    }

    @Test
    public void testMetricsXlsReportSummary() throws Exception {
        String metricName = "queryCount_Gauge";
        long endTime = new DateTime(DateTimeZone.UTC).getMillis();
        List<String> metricNames = new ArrayList<String>();
        metricNames.add(metricName);
        for (RrdMetricsRetriever.SUMMARY_INTERVALS interval : RrdMetricsRetriever.SUMMARY_INTERVALS.values()) {
            long startTime = 0L;
            switch (interval) {
            case minute:
                startTime = new DateTime(DateTimeZone.UTC).minusHours(1).getMillis();
                break;
            case hour:
                startTime = new DateTime(DateTimeZone.UTC).minusDays(1).getMillis();
                break;
            case day:
                startTime = new DateTime(DateTimeZone.UTC).minusWeeks(1).getMillis();
                break;
            case week:
                startTime = new DateTime(DateTimeZone.UTC).minusMonths(1).getMillis();
                break;
            case month:
                startTime = new DateTime(DateTimeZone.UTC).minusYears(1).getMillis();
                break;
            }
            int sampleSize = (int) ((endTime - startTime) / (RRD_STEP * 1000));
            new RrdFileBuilder().rrdFileName(TEST_DIR + metricName + RRD_FILE_EXTENSION).dsType(DsType.GAUGE)
                    .numSamples(sampleSize).numRows(sampleSize).startTime(startTime).build();
            MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
            OutputStream os = metricsRetriever.createXlsReport(metricNames, TEST_DIR, startTime, endTime,
                    interval.toString());
            InputStream xls = new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
            assertThat(xls, not(nullValue()));

            HSSFWorkbook wb = new HSSFWorkbook(xls);
            assertThat(wb.getNumberOfSheets(), equalTo(1));

            HSSFSheet sheet = wb.getSheetAt(0);
            assertThat(sheet, not(nullValue()));
        }
    }

    @Test
    public void testMetricsPptDataWithCounter() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Counter" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        OutputStream os = metricsRetriever.createPptData("queryCount", rrdFilename, START_TIME, endTime);
        InputStream is = new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
        assertThat(is, not(nullValue()));

        SlideShow ppt = new SlideShow(is);
        Slide[] slides = ppt.getSlides();
        assertThat(slides.length, equalTo(1));
        verifySlide(slides[0], "queryCount", true);
    }

    @Test
    public void testMetricsPptDataWithGauge() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Gauge" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).dsType(DsType.GAUGE).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        OutputStream os = metricsRetriever.createPptData("queryCount", rrdFilename, START_TIME, endTime);
        InputStream is = new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
        assertThat(is, not(nullValue()));

        SlideShow ppt = new SlideShow(is);
        Slide[] slides = ppt.getSlides();
        assertThat(slides.length, equalTo(1));
        verifySlide(slides[0], "queryCount", false);
    }

    @Test
    public void testMetricsPptReport() throws Exception {
        String rrdFilename = TEST_DIR + "queryCount_Counter" + RRD_FILE_EXTENSION;
        new RrdFileBuilder().rrdFileName(rrdFilename).build();

        rrdFilename = TEST_DIR + "queryCount_Gauge" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).dsType(DsType.GAUGE).build();

        List<String> metricNames = new ArrayList<String>();
        metricNames.add("queryCount_Counter");
        metricNames.add("queryCount_Gauge");

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        OutputStream os = metricsRetriever.createPptReport(metricNames, TEST_DIR, START_TIME, endTime);
        InputStream is = new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
        assertThat(is, not(nullValue()));

        SlideShow ppt = new SlideShow(is);
        Slide[] slides = ppt.getSlides();
        assertThat(slides.length, equalTo(2));

        verifySlide(slides[0], "queryCount_Counter", true);
        verifySlide(slides[1], "queryCount_Gauge", false);
    }

    @Test
    // (expected = MetricsGraphException.class)
    public void testInvalidDataSourceType() throws Exception {
        String rrdFilename = TEST_DIR + "dummy_Absolute" + RRD_FILE_EXTENSION;
        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).numSamples(4).dsType(DsType.ABSOLUTE).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        try {
            metricsRetriever.createGraph("Dummy", rrdFilename, START_TIME, endTime);
            fail();
        } catch (MetricsGraphException e) {
        }
    }

    @Test
    // (expected = MetricsGraphException.class)
    public void testRrdFileWithMultipleDataSources() throws Exception {
        String rrdFilename = TEST_DIR + "dummy" + RRD_FILE_EXTENSION;

        long endTime = new RrdFileBuilder().rrdFileName(rrdFilename).numSamples(4).secondDataSource(true).build();

        MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
        try {
            metricsRetriever.createGraph("Dummy", rrdFilename, START_TIME, endTime);
            fail();
        } catch (MetricsGraphException e) {
        }
    }

    private void verifyWorksheet(HSSFSheet sheet, String metricName, int expectedNumberOfDataRows,
            boolean hasTotalCount) {
        // 3 = title + blank row + column headers
        int expectedTotalRows = 3 + expectedNumberOfDataRows;
        if (hasTotalCount) {
            expectedTotalRows += 2;
        }
        assertThat(sheet.getPhysicalNumberOfRows(), equalTo(expectedTotalRows));

        // first row should have title in first cell
        HSSFRow row = sheet.getRow(0);
        assertThat(row, not(nullValue()));
        assertThat(row.getCell(0).getStringCellValue(), startsWith(metricName + " for"));

        // third row should have column headers in first and second cells
        row = sheet.getRow(2);
        assertThat(row.getCell(0).getStringCellValue(), equalTo("Timestamp"));
        assertThat(row.getCell(1).getStringCellValue(), equalTo("Value"));

        // verify rows with the sample data, i.e., timestamps and values
        int endRow = 3 + expectedNumberOfDataRows;
        for (int i = 3; i < endRow; i++) {
            row = sheet.getRow(i);
            assertThat(row.getCell(0).getStringCellValue(), not(nullValue()));
            assertThat(row.getCell(1).getNumericCellValue(), not(nullValue()));
        }

        row = sheet.getRow(sheet.getLastRowNum());
        if (hasTotalCount) {
            assertThat(row.getCell(0).getStringCellValue(), startsWith("Total Count:"));
            assertThat(row.getCell(1).getNumericCellValue(), not(nullValue()));
        } else {
            assertThat(row.getCell(0).getStringCellValue(), not(startsWith("Total Count:")));
        }
    }

    private void verifySlide(Slide slide, String metricName, boolean hasTotalCount) {
        assertThat(slide, not(nullValue()));

        Shape[] shapes = slide.getShapes();
        assertThat(shapes, not(nullValue()));

        // expected shapes: title text box, metric's graph, metric's total count text box
        int numExpectedShapes = 2;
        int numExpectedTextBoxes = 1;
        if (hasTotalCount) {
            numExpectedShapes++;
            numExpectedTextBoxes++;
        }
        assertThat(shapes.length, equalTo(numExpectedShapes));

        Picture picture = null;
        int numTextBoxes = 0;

        for (int i = 0; i < numExpectedShapes; i++) {
            if (shapes[i] instanceof Picture) {
                picture = (Picture) shapes[i];
                // title text box is actually an AutoShape
            } else if (shapes[i] instanceof TextBox || shapes[i] instanceof AutoShape) {
                numTextBoxes++;
            }
        }

        assertThat(picture, not(nullValue()));
        PictureData picData = picture.getPictureData();
        assertThat(picData, not(nullValue()));
        assertThat(picData.getType(), equalTo(Picture.PNG));
        assertThat(numTextBoxes, equalTo(numExpectedTextBoxes));
    }

    /**
     * *********************************************************************
     */

    public static class RrdFileBuilder {
        private RrdDb rrdDb;

        private RrdDef rrdDef;

        private int rrdStep;

        private String rrdFileName;

        private DsType dsType;

        private long startTime;

        private long endTime;

        private int numRows;

        private int numSamples;

        private boolean secondDataSource;

        private boolean dumpData;

        public RrdFileBuilder() {
            secondDataSource = false;
            dsType = DsType.COUNTER;
            startTime = START_TIME;
            rrdStep = RRD_STEP;
            numRows = 5;
            numSamples = 50;
            dumpData = false;
        }

        public RrdFileBuilder rrdFileName(String rrdFileName) {
            this.rrdFileName = rrdFileName;
            return this;
        }

        public RrdFileBuilder dsType(DsType dsType) {
            this.dsType = dsType;
            return this;
        }

        public RrdFileBuilder startTime(long startTime) {
            this.startTime = startTime;
            return this;
        }

        public RrdFileBuilder numRows(int numRows) {
            this.numRows = numRows;
            return this;
        }

        public RrdFileBuilder numSamples(int numSamples) {
            this.numSamples = numSamples;
            return this;
        }

        public RrdFileBuilder secondDataSource(boolean secondDataSource) {
            this.secondDataSource = secondDataSource;
            return this;
        }

        public long build() throws Exception {
            rrdDef = new RrdDef(rrdFileName, rrdStep);
            rrdDef.setStartTime(startTime - 1);
            rrdDef.addDatasource("data", dsType, 90, 0, Double.NaN);
            if (secondDataSource) {
                rrdDef.addDatasource("ds2", DsType.GAUGE, 90, 0, Double.NaN);
            }

            // 1 step, 60 seconds per step, for 5 minutes
            rrdDef.addArchive(ConsolFun.TOTAL, 0.5, 1, numRows);
            rrdDef.addArchive(ConsolFun.AVERAGE, 0.5, 1, numRows);
            rrdDef.addArchive(ConsolFun.MAX, 0.5, 1, numRows);
            rrdDef.addArchive(ConsolFun.MIN, 0.5, 1, numRows);

            // 5 steps, 60 seconds per step, for 30 minutes
            rrdDef.addArchive(ConsolFun.TOTAL, 0.5, 5, numRows + 1);
            rrdDef.addArchive(ConsolFun.AVERAGE, 0.5, 5, numRows + 1);
            rrdDef.addArchive(ConsolFun.MAX, 0.5, 5, numRows + 1);
            rrdDef.addArchive(ConsolFun.MIN, 0.5, 5, numRows + 1);

            rrdDb = new RrdDb(rrdDef);

            endTime = (dsType == DsType.GAUGE) ? loadGaugeSamples() : loadCounterSamples();

            if (dumpData) {
                dumpData();
            }
            rrdDb.close();

            return endTime;
        }

        private long loadCounterSamples() throws IOException {
            Sample sample = rrdDb.createSample();

            double queryCount = 0.0;
            int min = 0;
            int max = 20;
            Random random = new Random();
            long timestamp = startTime;

            for (int i = 1; i <= numSamples; i++) {
                int randomQueryCount = random.nextInt(max - min + 1) + min;
                queryCount += randomQueryCount;
                LOGGER.debug("timestamp: {}   ({}),   queryCount = {}", getCalendarTime(timestamp * 1000),
                        timestamp, queryCount);
                sample.setTime(timestamp);
                sample.setValue("data", queryCount);
                sample.update();

                // Increment by RRD step
                // (can only add samples to data source on its step interval, not in between steps)
                timestamp += rrdStep;
            }

            return timestamp;
        }

        private long loadGaugeSamples() throws IOException {
            Sample sample = rrdDb.createSample();

            int min = 200;
            int max = 2000;
            Random random = new Random();
            long timestamp = startTime;

            for (int i = 1; i <= numSamples; i++) {
                int randomQueryResponseTime = random.nextInt(max - min + 1) + min; // in ms
                LOGGER.debug("timestamp: {},  ({}),   queryResponseTime = {}", getCalendarTime(timestamp * 1000),
                        timestamp, randomQueryResponseTime);
                sample.setTime(timestamp);
                sample.setValue("data", randomQueryResponseTime);
                sample.update();

                // Increment by RRD step
                // (can only add samples to data source on its step interval, not in between steps)
                timestamp += rrdStep;
            }

            return timestamp;
        }

        private String getCalendarTime(long timestamp) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(timestamp * 1000);

            String calTime = MONTHS[calendar.get(Calendar.MONTH)] + " " + calendar.get(Calendar.DATE) + " "
                    + calendar.get(Calendar.YEAR) + " ";

            calTime += addLeadingZero(calendar.get(Calendar.HOUR_OF_DAY)) + ":";
            calTime += addLeadingZero(calendar.get(Calendar.MINUTE)) + ":";
            calTime += addLeadingZero(calendar.get(Calendar.SECOND));

            return calTime;
        }

        private String addLeadingZero(int value) {
            if (value < 10) {
                return "0" + String.valueOf(value);
            }

            return String.valueOf(value);
        }

        private void dumpData() throws IOException {
            LOGGER.debug(rrdDb.dump());

            FetchRequest fetchRequest = rrdDb.createFetchRequest(ConsolFun.AVERAGE, startTime, endTime);
            FetchData fetchData = fetchRequest.fetchData();
            LOGGER.debug("************  {}: AVERAGE  **************", dsType.name());
            LOGGER.debug(fetchData.dump());
            long[] timestamps = fetchData.getTimestamps();
            double[] values = fetchData.getValues(0);
            for (int i = 0; i < timestamps.length; i++) {
                LOGGER.debug("{}:  {}", getCalendarTime(timestamps[i]), values[i]);
            }

            fetchRequest = rrdDb.createFetchRequest(ConsolFun.TOTAL, startTime, endTime);
            fetchData = fetchRequest.fetchData();
            LOGGER.debug("************  {}: TOTAL  **************", dsType.name());
            LOGGER.debug(fetchData.dump());
            timestamps = fetchData.getTimestamps();
            values = fetchData.getValues(0);
            for (int i = 0; i < timestamps.length; i++) {
                LOGGER.debug("{}:  {}", getCalendarTime(timestamps[i]), values[i]);
            }

            fetchRequest = rrdDb.createFetchRequest(ConsolFun.MIN, startTime, endTime);
            fetchData = fetchRequest.fetchData();
            LOGGER.debug("************  {}: MIN  **************", dsType);
            LOGGER.debug(fetchData.dump());

            fetchRequest = rrdDb.createFetchRequest(ConsolFun.MAX, startTime, endTime);
            fetchData = fetchRequest.fetchData();
            LOGGER.debug("************  {}: MAX  **************", dsType);
            LOGGER.debug(fetchData.dump());
        }
    }
}