/* Copyright (c) 2008-2009 HomeAway, Inc. * All rights reserved. http://www.perf4j.org * * Licensed 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.perf4j.helpers; import org.perf4j.GroupedTimingStatistics; import org.perf4j.TimingStatistics; import java.util.Map; /** * This helper formatter class outputs {@link org.perf4j.GroupedTimingStatistics} in a comma-separated value format. * This class supports two main types of formats: * <p/> * <ul> * <li>Pivoted output, where each GroupedTimingStatistics is output as a single line. The client must specify * which tags from the GroupedTimingStatistics should have its values output. * <li>Non-pivoted output, where each individual tag in the GroupedTimingStatistics gets its own row. * </ul> * * @author Alex Devine */ public class GroupedTimingStatisticsCsvFormatter implements GroupedTimingStatisticsFormatter { /** * The default format string for a non-pivoted formatter. */ public static final String DEFAULT_FORMAT_STRING = "tag,start,stop,mean,min,max,stddev,count"; //whether or not the output is pivoted private boolean pivot; //valueRetrievers is only used if pivot is false, otherwise it's null. private TimingStatsValueRetriever[] valueRetrievers; //pivotedValueRetrievers is only used if pivot is true, otherwise it's null. private GroupedTimingStatisticsValueRetriever[] pivotedValueRetrievers; // --- Constructors --- /** * Creates a non-pivoted CSV formatter using the default format string. */ public GroupedTimingStatisticsCsvFormatter() { this(false, DEFAULT_FORMAT_STRING); } /** * Creates a CSV formatter which allows you to config which values are output. * * @param pivot Whether the output is pivoted (one row for each GroupedTimingStatisitcs) or not (one row * for each tagged TimingStatistics item contained in the GroupedTimingStatisitcs). * @param configString The config string defines which values will be output, and should be a comma-separated list * of the values. Possible values if pivot is false are * tag, start, stop, mean, min, max, stddev, count and tps. If pivot is true the possible * values are start, stop, and then one of the statistics prefixed with the tag name. For * example, a possible configString could be "start,stop,codeBlock1Mean,codeBlock2Max". */ public GroupedTimingStatisticsCsvFormatter(boolean pivot, String configString) { this.pivot = pivot; String[] configElements = MiscUtils.splitAndTrim(configString, ","); if (pivot) { pivotedValueRetrievers = new GroupedTimingStatisticsValueRetriever[configElements.length]; for (int i = 0; i < configElements.length; i++) { pivotedValueRetrievers[i] = parsePivotedTimingStatsConfig(configElements[i]); } } else { valueRetrievers = new TimingStatsValueRetriever[configElements.length]; for (int i = 0; i < configElements.length; i++) { valueRetrievers[i] = parseTimingStatsConfig(configElements[i]); } } } // --- formatting methods --- /** * Formats the specified GroupedTimingStatistics instance for CSV output. * * @param stats the GroupedTimingStatistics instance, may not be null * @return The output formatted according to the configString passed in on this formatter's constructor * (or the DEFAULT_FORMAT_STRING). */ public String format(GroupedTimingStatistics stats) { String startTime = formatDate(stats.getStartTime()); String stopTime = formatDate(stats.getStopTime()); long windowLength = stats.getStopTime() - stats.getStartTime(); StringBuilder retVal = new StringBuilder(); if (pivot) { for (int i = 0; i < pivotedValueRetrievers.length; i++) { if (i > 0) { retVal.append(','); } pivotedValueRetrievers[i].appendValue(startTime, stopTime, windowLength, stats, retVal); } retVal.append(MiscUtils.NEWLINE); } else { //iterate over each TimingStatistics item, creating one row for each for (Map.Entry<String, TimingStatistics> tagAndStats : stats.getStatisticsByTag().entrySet()) { String tag = tagAndStats.getKey(); TimingStatistics timingStats = tagAndStats.getValue(); for (int i = 0; i < valueRetrievers.length; i++) { if (i > 0) { retVal.append(','); } valueRetrievers[i].appendValue(tag, startTime, stopTime, windowLength, timingStats, retVal); } retVal.append(MiscUtils.NEWLINE); } } return retVal.toString(); } // --- helper methods --- /** * Formats the specified time in yyyy-MM-dd HH:mm:ss format. Subclasses may override to give a different output. * * @param timeInMillis The time in milliseconds. * @return The formatted date/time String */ protected String formatDate(long timeInMillis) { return MiscUtils.formatDateIso8601(timeInMillis); } /** * Helper method parses the specified single element from a config string to return the corresponding * GroupedTimingStatisticsValueRetriever. * * @param configName The element from the config string * @return The corresponding GroupedTimingStatisticsValueRetriever * @throws IllegalArgumentException Thrown if configName is an unknown value designator */ protected GroupedTimingStatisticsValueRetriever parsePivotedTimingStatsConfig(String configName) { if ("start".equalsIgnoreCase(configName)) { return new GroupedTimingStatisticsValueRetriever() { public void appendValue(String start, String stop, long windowLength, GroupedTimingStatistics stats, StringBuilder toAppend) { toAppend.append(start); } }; } else if ("stop".equalsIgnoreCase(configName)) { return new GroupedTimingStatisticsValueRetriever() { public void appendValue(String start, String stop, long windowLength, GroupedTimingStatistics stats, StringBuilder toAppend) { toAppend.append(stop); } }; } else if (configName.toLowerCase().endsWith("mean")) { final String tag = configName.substring(0, configName.length() - "mean".length()); return new GroupedTimingStatisticsValueRetriever() { public void appendValue(String start, String stop, long windowLength, GroupedTimingStatistics stats, StringBuilder toAppend) { TimingStatistics timingStats = stats.getStatisticsByTag().get(tag); toAppend.append((timingStats == null) ? "" : timingStats.getMean()); } }; } else if (configName.toLowerCase().endsWith("min")) { final String tag = configName.substring(0, configName.length() - "min".length()); return new GroupedTimingStatisticsValueRetriever() { public void appendValue(String start, String stop, long windowLength, GroupedTimingStatistics stats, StringBuilder toAppend) { TimingStatistics timingStats = stats.getStatisticsByTag().get(tag); toAppend.append((timingStats == null) ? "" : timingStats.getMin()); } }; } else if (configName.toLowerCase().endsWith("max")) { final String tag = configName.substring(0, configName.length() - "max".length()); return new GroupedTimingStatisticsValueRetriever() { public void appendValue(String start, String stop, long windowLength, GroupedTimingStatistics stats, StringBuilder toAppend) { TimingStatistics timingStats = stats.getStatisticsByTag().get(tag); toAppend.append((timingStats == null) ? "" : timingStats.getMax()); } }; } else if (configName.toLowerCase().endsWith("stddev")) { final String tag = configName.substring(0, configName.length() - "stddev".length()); return new GroupedTimingStatisticsValueRetriever() { public void appendValue(String start, String stop, long windowLength, GroupedTimingStatistics stats, StringBuilder toAppend) { TimingStatistics timingStats = stats.getStatisticsByTag().get(tag); toAppend.append((timingStats == null) ? "" : timingStats.getStandardDeviation()); } }; } else if (configName.toLowerCase().endsWith("count")) { final String tag = configName.substring(0, configName.length() - "count".length()); return new GroupedTimingStatisticsValueRetriever() { public void appendValue(String start, String stop, long windowLength, GroupedTimingStatistics stats, StringBuilder toAppend) { TimingStatistics timingStats = stats.getStatisticsByTag().get(tag); toAppend.append((timingStats == null) ? "" : timingStats.getCount()); } }; } else if (configName.toLowerCase().endsWith("tps")) { final String tag = configName.substring(0, configName.length() - "tps".length()); return new GroupedTimingStatisticsValueRetriever() { public void appendValue(String start, String stop, long windowLength, GroupedTimingStatistics stats, StringBuilder toAppend) { TimingStatistics timingStats = stats.getStatisticsByTag().get(tag); if (timingStats == null) { toAppend.append(""); } else { toAppend.append((timingStats.getCount() * 1000.0) / windowLength); } } }; } else { throw new IllegalArgumentException("Unknown CSV format config string: " + configName); } } /** * Helper method parses the specified single element from a config string to return the corresponding * TimingStatsValueRetriever. * * @param configName The element from the config string * @return The corresponding TimingStatsValueRetriever * @throws IllegalArgumentException Thrown if configName is an unknown value designator */ protected TimingStatsValueRetriever parseTimingStatsConfig(String configName) { if ("tag".equals(configName)) { return new TimingStatsValueRetriever() { public void appendValue(String tag, String start, String stop, long windowLength, TimingStatistics timingStats, StringBuilder toAppend) { MiscUtils.escapeStringForCsv(tag, toAppend); } }; } else if ("start".equals(configName)) { return new TimingStatsValueRetriever() { public void appendValue(String tag, String start, String stop, long windowLength, TimingStatistics timingStats, StringBuilder toAppend) { toAppend.append(start); } }; } else if ("stop".equals(configName)) { return new TimingStatsValueRetriever() { public void appendValue(String tag, String start, String stop, long windowLength, TimingStatistics timingStats, StringBuilder toAppend) { toAppend.append(stop); } }; } else if ("mean".equals(configName)) { return new TimingStatsValueRetriever() { public void appendValue(String tag, String start, String stop, long windowLength, TimingStatistics timingStats, StringBuilder toAppend) { toAppend.append(timingStats.getMean()); } }; } else if ("min".equals(configName)) { return new TimingStatsValueRetriever() { public void appendValue(String tag, String start, String stop, long windowLength, TimingStatistics timingStats, StringBuilder toAppend) { toAppend.append(timingStats.getMin()); } }; } else if ("max".equals(configName)) { return new TimingStatsValueRetriever() { public void appendValue(String tag, String start, String stop, long windowLength, TimingStatistics timingStats, StringBuilder toAppend) { toAppend.append(timingStats.getMax()); } }; } else if ("stddev".equals(configName)) { return new TimingStatsValueRetriever() { public void appendValue(String tag, String start, String stop, long windowLength, TimingStatistics timingStats, StringBuilder toAppend) { toAppend.append(timingStats.getStandardDeviation()); } }; } else if ("count".equals(configName)) { return new TimingStatsValueRetriever() { public void appendValue(String tag, String start, String stop, long windowLength, TimingStatistics timingStats, StringBuilder toAppend) { toAppend.append(timingStats.getCount()); } }; } else if ("tps".equals(configName)) { return new TimingStatsValueRetriever() { public void appendValue(String tag, String start, String stop, long windowLength, TimingStatistics timingStats, StringBuilder toAppend) { toAppend.append((timingStats.getCount() * 1000.0) / windowLength); } }; } else { throw new IllegalArgumentException("Unknown CSV format config string: " + configName); } } // --- Helper interfaces --- protected static interface TimingStatsValueRetriever { public void appendValue(String tag, String start, String stop, long windowLength, TimingStatistics stats, StringBuilder toAppend); } protected static interface GroupedTimingStatisticsValueRetriever { public void appendValue(String start, String stop, long windowLength, GroupedTimingStatistics stats, StringBuilder toAppend); } }