// Copyright 2012 Georg-August-Universität Göttingen, Germany // // 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 de.ugoe.cs.util; import java.io.PrintStream; import java.io.Serializable; import java.text.DecimalFormat; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** *

* This is a simple implementation for a stop watch that can be used for performance measures. * A stop watch can be used to measure several splits. Each split is started and stopped using the * same id provided to the {@link #start(String)} and {@link #stop(String)} methods. The measured * durations can be retrieved afterwards using {@link #getDuration(String)}. * {@link #dumpStatistics(PrintStream)} is a convenience method useful to effectively dump all * information for the different splits. *

* * @author Patrick Harms */ public class StopWatch implements Serializable{ /** * */ private static final long serialVersionUID = -4216393284789336830L; /** * the splits hold internally */ private Map mSplits = new HashMap(); /** * starts a split with the given id. If the split with the id is already started, an * {@link IllegalStateException} is thrown. * * @param id the id of the split to be started * * @throws IllegalStateException if the split is already started */ public void start(String id) throws IllegalStateException { Split split = mSplits.get(id); if (split == null) { split = new Split(id); mSplits.put(id, split); } split.start(); } /** * stops a split with the given id. If the split with the id is already stopped, an * {@link IllegalStateException} is thrown. If no split with the given id exists, an * {@link IllegalArgumentException} is thrown. * * @param id the id of the split to be stopped * * @throws IllegalStateException if the split is not running * @throws IllegalArgumentException if the split with the given id does not exist */ public void stop(String id) throws IllegalStateException, IllegalArgumentException { Split split = mSplits.get(id); if (split == null) { throw new IllegalArgumentException("split with id " + id + " does not exist"); } split.stop(); } /** * returns the duration of a split with the given id. If the split with the id is currently * running, it is stopped. If no split with the given id exists, an * {@link IllegalArgumentException} is thrown. * * @param id the id of the split for which the duration shall be returned * * @return the duration measured for the split * * @throws IllegalArgumentException if the split with the given id does not exist */ public long getDuration(String id) throws IllegalArgumentException { Split split = mSplits.get(id); if (split == null) { throw new IllegalArgumentException("split with id " + id + " does not exist"); } if (split.isRunning()) { split.stop(); } return split.getDuration(); } /** * resets the stop watch and clears all splits */ public void reset() { mSplits.clear(); } /** * dumps the statistics about the splits. Splits still running are stopped. The method checks * if the longest split also covers the other splits. If so, it considers this split as a * kind of overall duration and dumps the proportion of all other splits to this split in * percentage. * * @param out the stream to dump the statistics to */ public void dumpStatistics(PrintStream out) { if (mSplits.size() <= 0) { throw new IllegalStateException("no splits registered that could be dumped"); } Map durations = new HashMap(); // get durations for (String id : mSplits.keySet()) { durations.put(id, getDuration(id)); } // sort by duration List sortedIds = new LinkedList(); int maxIdLength = 0; for (Map.Entry entry : durations.entrySet()) { boolean added = false; for (int i = 0; i < sortedIds.size(); i++) { if (durations.get(sortedIds.get(i)) >= entry.getValue()) { sortedIds.add(i, entry.getKey()); added = true; break; } } if (!added) { sortedIds.add(entry.getKey()); } maxIdLength = Math.max(maxIdLength, entry.getKey().length()); } // get longest duration and check whether it spans all other entries String id = sortedIds.get(sortedIds.size() - 1); Split longestWatch = mSplits.get(id); boolean longestDurationCoversOthers = true; for (Map.Entry watch : mSplits.entrySet()) { if ((watch.getValue().getFirstStart() < longestWatch.getFirstStart()) || (watch.getValue().getLastStop() > longestWatch.getLastStop())) { longestDurationCoversOthers = false; break; } } // no finally start the dumping out.println(); out.println("Watch Statistics"); out.println("================"); for (String sortedId : sortedIds) { out.print(sortedId); for (int i = sortedId.length(); i <= maxIdLength; i++) { out.print(' '); } out.print(": "); out.print(durations.get(sortedId)); out.print(" ms"); out.print(" ("); out.print(mSplits.get(sortedId).getNoOfStarts()); out.print(" starts"); //out.print(", "); //out.print(1000 * durations.get(sortedId) / mSplits.get(sortedId).getNoOfStarts()); //out.print(" ms per 1000 starts"); if (longestDurationCoversOthers) { out.print(", "); out.print(DecimalFormat.getPercentInstance().format ((double) durations.get(sortedId) / longestWatch.getDuration())); out.print(" of overall duration"); } out.println(')'); } out.println(); } /** * internally used to store splits */ private static class Split implements Serializable { /** * */ private static final long serialVersionUID = 7767677492954506604L; /** * the id of the split */ private String id; /** * the system time of the first start of the split */ private long firstStart = -1; /** * the system time of the last start of the split */ private long lastStart = -1; /** * the system time of the last stop of the split */ private long lastStop = -1; /** * the duration so far for the split (excluding the time since the last start) */ private long duration = 0; /** * the number of starts or the splits */ private long noOfStarts = 0; /** * initializes the split with its id */ private Split(String id) { this.id = id; } /** * starts the split if it is not already started */ private void start() throws IllegalStateException { if (lastStart > -1) { throw new IllegalStateException("split with id " + id + " already running"); } lastStart = System.currentTimeMillis(); if (firstStart < 0) { firstStart = lastStart; } noOfStarts++; } /** * checks if the split is currently running */ private boolean isRunning() { return (lastStart > -1); } /** * stops the split if it is not already stopped */ private void stop() throws IllegalStateException { if (lastStart < 0) { throw new IllegalStateException("split with id " + id + " not running"); } lastStop = System.currentTimeMillis(); duration += lastStop - lastStart; lastStart = -1; } /** * returns the fist start of the split */ private long getFirstStart() { return firstStart; } /** * returns the last stop of the split */ private long getLastStop() { return lastStop; } /** * returns the duration of the split measured so far excluding the time since the last * start if the split is currently started */ private long getDuration() { return duration; } /** * returns the number of starts for the split */ private long getNoOfStarts() { return noOfStarts; } } }