/* 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;
import org.perf4j.helpers.MiscUtils;
import java.io.Serializable;
import java.util.*;
/**
* Represents a set of TimingStatistics calculated for a specific time period for a set of tags.
*
* @author Alex Devine
*/
public class GroupedTimingStatistics implements Serializable, Cloneable {
private static final long serialVersionUID = 6506566405934476649L;
private SortedMap<String, TimingStatistics> statisticsByTag = new TreeMap<String, TimingStatistics>();
private long startTime;
private long stopTime;
private boolean createRollupStatistics;
// --- Constructors ---
/**
* Default constructor allows you to set statistics later using the addStopWatch and setter methods.
*/
public GroupedTimingStatistics() {}
/**
* Creates a GroupedTimingStatistics instance for a set of tags for a specified time span.
*
* @param statisticsByTag This Map maps String tag times to the aggregated TimingStatistics for that tag.
* @param startTime The start time (as reported by System.currentTimeMillis()) of the time span
* for which the statistics apply.
* @param stopTime The end time of the time span for which the statistics apply.
* @param createRollupStatistics Whether or not the statisticsByTag contains "rollup statistics". Rollup statistics
* allow users to time different execution paths of the same code block. For example,
* when timing a code block, one may which to log execution time with a
* "codeBlock.success" tag when execution completes normally and a "codeBlock.failure"
* tag when an exception is thrown. If rollup statistics are used, then in addition
* to the codeBlock.success and codeBlock.failure tags, a codeBlock tag is created
* that represents StopWatch logs from EITHER the success or failure tags.
*/
public GroupedTimingStatistics(SortedMap<String, TimingStatistics> statisticsByTag,
long startTime,
long stopTime,
boolean createRollupStatistics) {
this.statisticsByTag = statisticsByTag;
this.startTime = startTime;
this.stopTime = stopTime;
this.createRollupStatistics = createRollupStatistics;
}
// --- Utility Methods ---
/**
* This method updates the calculated statistics when a new logged StopWatch is added.
*
* @param stopWatch The StopWatch being used to update the statistics.
* @return this GroupedTimingStatistics instance
*/
public GroupedTimingStatistics addStopWatch(StopWatch stopWatch) {
String tag = stopWatch.getTag();
addStopWatchToStatsByTag(tag, stopWatch);
//create rollup statistics if desired by splitting up the tag
if (createRollupStatistics) {
int indexOfDot = -1;
while ((indexOfDot = tag.indexOf('.', indexOfDot + 1)) >= 0) {
addStopWatchToStatsByTag(tag.substring(0, indexOfDot), stopWatch);
}
}
return this;
}
/**
* Updates these statistics with all of the StopWatches in the specified collection.
*
* @param stopWatches The collection of StopWatches to add to this GroupedTimingStatistics data set.
* @return this GroupedTimingStatistics instance
*/
public GroupedTimingStatistics addStopWatches(Collection<StopWatch> stopWatches) {
for (StopWatch stopWatch : stopWatches) {
addStopWatch(stopWatch);
}
return this;
}
/**
* The TimeZone to use when displaying start/stop time information
*/
private static TimeZone timeZone = TimeZone.getDefault();
/**
* Returns the <tt>TimeZone</tt> that should be used for display of timestamp values.
*
* @return the timezone in which the statistics should be presented
*/
public static TimeZone getTimeZone() {
return timeZone;
}
/**
* Sets the <tt>TimeZone</tt> which should be used to present this data.
*
* @param tz the timezone to use
*/
public static void setTimeZone(TimeZone tz) {
timeZone = tz;
}
// --- Bean Properties ---
public SortedMap<String, TimingStatistics> getStatisticsByTag() {
return statisticsByTag;
}
public void setStatisticsByTag(SortedMap<String, TimingStatistics> statisticsByTag) {
this.statisticsByTag = statisticsByTag;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public long getStopTime() {
return stopTime;
}
public void setStopTime(long stopTime) {
this.stopTime = stopTime;
}
public boolean isCreateRollupStatistics() {
return createRollupStatistics;
}
public void setCreateRollupStatistics(boolean createRollupStatistics) {
this.createRollupStatistics = createRollupStatistics;
}
// --- Helper Methods ---
private void addStopWatchToStatsByTag(String tag, StopWatch stopWatch) {
TimingStatistics stats = statisticsByTag.get(tag);
if (stats == null) {
statisticsByTag.put(tag, stats = new TimingStatistics());
}
stats.addSampleTime(stopWatch.getElapsedTime());
}
// --- Object Methods ---
public String toString() {
StringBuilder retVal = new StringBuilder();
int paddingToAllowForLongestTag = Math.max(getLongestTag(statisticsByTag.keySet()), "Tag".length());
//output the time window
retVal.append("Performance Statistics ")
.append(MiscUtils.formatDateIso8601(startTime))
.append(" - ")
.append(MiscUtils.formatDateIso8601(stopTime))
.append(MiscUtils.NEWLINE);
//output the header
retVal.append(String.format("%-" + paddingToAllowForLongestTag + "s%12s%12s%12s%12s%12s%12s%n",
"Tag", "Avg(ms)", "Min", "Max", "Std-Dev", "Count", "Total"));
//output each statistics
for (Map.Entry<String, TimingStatistics> tagWithTimingStatistics : statisticsByTag.entrySet()) {
String tag = tagWithTimingStatistics.getKey();
TimingStatistics timingStatistics = tagWithTimingStatistics.getValue();
double totalTimeForTag = timingStatistics.getCount() * timingStatistics.getMean();
retVal.append(String.format("%-" + paddingToAllowForLongestTag + "s%12.1f%12d%12d%12.1f%12d%12.0f%n",
tag,
timingStatistics.getMean(),
timingStatistics.getMin(),
timingStatistics.getMax(),
timingStatistics.getStandardDeviation(),
timingStatistics.getCount(),
totalTimeForTag));
}
return retVal.toString();
}
private int getLongestTag(Set<String> keySet) {
int longestLength = 0;
for (String tag : keySet) {
if (tag.length() > longestLength) {
longestLength = tag.length();
}
}
return longestLength;
}
public GroupedTimingStatistics clone() {
try {
GroupedTimingStatistics retVal = (GroupedTimingStatistics) super.clone();
retVal.statisticsByTag = new TreeMap<String, TimingStatistics>(retVal.statisticsByTag);
for (Map.Entry<String, TimingStatistics> tagAndStats : retVal.statisticsByTag.entrySet()) {
tagAndStats.setValue(tagAndStats.getValue().clone());
}
return retVal;
} catch (CloneNotSupportedException cnse) {
throw new Error("Unexpected CloneNotSupportedException");
}
}
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof GroupedTimingStatistics)) {
return false;
}
GroupedTimingStatistics that = (GroupedTimingStatistics) o;
return startTime == that.startTime &&
stopTime == that.stopTime &&
statisticsByTag.equals(that.statisticsByTag);
}
public int hashCode() {
int result;
result = statisticsByTag.hashCode();
result = 31 * result + (int) (startTime ^ (startTime >>> 32));
result = 31 * result + (int) (stopTime ^ (stopTime >>> 32));
return result;
}
}