source: branches/java-utils-alignment/src/main/java/de/ugoe/cs/util/StopWatch.java @ 2106

Last change on this file since 2106 was 1710, checked in by rkrimmel, 10 years ago

Better saving of intermediate results

File size: 10.0 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.util;
16
17import java.io.PrintStream;
18import java.io.Serializable;
19import java.text.DecimalFormat;
20import java.util.HashMap;
21import java.util.LinkedList;
22import java.util.List;
23import java.util.Map;
24
25/**
26 * <p>
27 * This is a simple implementation for a stop watch that can be used for performance measures.
28 * A stop watch can be used to measure several splits. Each split is started and stopped using the
29 * same id provided to the {@link #start(String)} and {@link #stop(String)} methods. The measured
30 * durations can be retrieved afterwards using {@link #getDuration(String)}.
31 * {@link #dumpStatistics(PrintStream)} is a convenience method useful to effectively dump all
32 * information for the different splits.
33 * </p>
34 *
35 * @author Patrick Harms
36 */
37public class StopWatch implements Serializable{
38   
39    /**
40         *
41         */
42        private static final long serialVersionUID = -4216393284789336830L;
43        /**
44     * the splits hold internally
45     */
46    private Map<String, Split> mSplits = new HashMap<String, Split>();
47
48    /**
49     * starts a split with the given id. If the split with the id is already started, an
50     * {@link IllegalStateException} is thrown.
51     *
52     * @param id the id of the split to be started
53     *
54     * @throws IllegalStateException if the split is already started
55     */
56    public void start(String id) throws IllegalStateException {
57        Split split = mSplits.get(id);
58       
59        if (split == null) {
60            split = new Split(id);
61            mSplits.put(id, split);
62        }
63       
64        split.start();
65    }
66   
67    /**
68     * stops a split with the given id. If the split with the id is already stopped, an
69     * {@link IllegalStateException} is thrown. If no split with the given id exists, an
70     * {@link IllegalArgumentException} is thrown.
71     *
72     * @param id the id of the split to be stopped
73     *
74     * @throws IllegalStateException    if the split is not running
75     * @throws IllegalArgumentException if the split with the given id does not exist
76     */
77    public void stop(String id) throws IllegalStateException, IllegalArgumentException {
78        Split split = mSplits.get(id);
79       
80        if (split == null) {
81            throw new IllegalArgumentException("split with id " + id + " does not exist");
82        }
83       
84        split.stop();
85    }
86   
87    /**
88     * returns the duration of a split with the given id. If the split with the id is currently
89     * running, it is stopped. If no split with the given id exists, an
90     * {@link IllegalArgumentException} is thrown.
91     *
92     * @param id the id of the split for which the duration shall be returned
93     *
94     * @return the duration measured for the split
95     *
96     * @throws IllegalArgumentException if the split with the given id does not exist
97     */
98    public long getDuration(String id) throws IllegalArgumentException {
99        Split split = mSplits.get(id);
100       
101        if (split == null) {
102            throw new IllegalArgumentException("split with id " + id + " does not exist");
103        }
104       
105        if (split.isRunning()) {
106            split.stop();
107        }
108       
109        return split.getDuration();
110    }
111
112    /**
113     * resets the stop watch and clears all splits
114     */
115    public void reset() {
116        mSplits.clear();
117    }
118   
119    /**
120     * dumps the statistics about the splits. Splits still running are stopped. The method checks
121     * if the longest split also covers the other splits. If so, it considers this split as a
122     * kind of overall duration and dumps the proportion of all other splits to this split in
123     * percentage.
124     *
125     * @param out the stream to dump the statistics to
126     */
127    public void dumpStatistics(PrintStream out) {
128        if (mSplits.size() <= 0) {
129            throw new IllegalStateException("no splits registered that could be dumped");
130        }
131       
132        Map<String, Long> durations = new HashMap<String, Long>();
133       
134        // get durations
135        for (String id : mSplits.keySet()) {
136            durations.put(id, getDuration(id));
137        }
138       
139        // sort by duration
140        List<String> sortedIds = new LinkedList<String>();
141        int maxIdLength = 0;
142       
143        for (Map.Entry<String, Long> entry : durations.entrySet()) {
144            boolean added = false;
145            for (int i = 0; i < sortedIds.size(); i++) {
146                if (durations.get(sortedIds.get(i)) >= entry.getValue()) {
147                    sortedIds.add(i, entry.getKey());
148                    added = true;
149                    break;
150                }
151            }
152           
153            if (!added) {
154                sortedIds.add(entry.getKey());
155            }
156           
157            maxIdLength = Math.max(maxIdLength, entry.getKey().length());
158        }
159       
160        // get longest duration and check whether it spans all other entries
161        String id = sortedIds.get(sortedIds.size() - 1);
162        Split longestWatch = mSplits.get(id);
163        boolean longestDurationCoversOthers = true;
164       
165        for (Map.Entry<String, Split> watch : mSplits.entrySet()) {
166            if ((watch.getValue().getFirstStart() < longestWatch.getFirstStart()) ||
167                (watch.getValue().getLastStop() > longestWatch.getLastStop()))
168            {
169                longestDurationCoversOthers = false;
170                break;
171            }
172        }
173       
174        // no finally start the dumping
175        out.println();
176        out.println("Watch Statistics");
177        out.println("================");
178
179        for (String sortedId : sortedIds) {
180            out.print(sortedId);
181           
182            for (int i = sortedId.length(); i <= maxIdLength; i++) {
183                out.print(' ');
184            }
185           
186            out.print(": ");
187           
188            out.print(durations.get(sortedId));
189            out.print(" ms");
190           
191            out.print(" (");
192            out.print(mSplits.get(sortedId).getNoOfStarts());
193            out.print(" starts");
194           
195            //out.print(", ");
196            //out.print(1000 * durations.get(sortedId) / mSplits.get(sortedId).getNoOfStarts());
197            //out.print(" ms per 1000 starts");
198
199            if (longestDurationCoversOthers) {
200                out.print(", ");
201                out.print(DecimalFormat.getPercentInstance().format
202                              ((double) durations.get(sortedId) / longestWatch.getDuration()));
203                out.print(" of overall duration");
204            }
205           
206            out.println(')');
207        }
208       
209        out.println();
210    }
211   
212    /**
213     * internally used to store splits
214     */
215    private static class Split implements Serializable {
216       
217        /**
218                 *
219                 */
220                private static final long serialVersionUID = 7767677492954506604L;
221
222                /**
223         * the id of the split
224         */
225        private String id;
226       
227        /**
228         * the system time of the first start of the split
229         */
230        private long firstStart = -1;
231       
232        /**
233         * the system time of the last start of the split
234         */
235        private long lastStart = -1;
236       
237        /**
238         * the system time of the last stop of the split
239         */
240        private long lastStop = -1;
241       
242        /**
243         * the duration so far for the split (excluding the time since the last start)
244         */
245        private long duration = 0;
246       
247        /**
248         * the number of starts or the splits
249         */
250        private long noOfStarts = 0;
251       
252        /**
253         * initializes the split with its id
254         */
255        private Split(String id) {
256            this.id = id;
257        }
258       
259        /**
260         * starts the split if it is not already started
261         */
262        private void start() throws IllegalStateException {
263            if (lastStart > -1) {
264                throw new IllegalStateException("split with id " + id + " already running");
265            }
266           
267            lastStart = System.currentTimeMillis();
268           
269            if (firstStart < 0) {
270                firstStart = lastStart;
271            }
272           
273            noOfStarts++;
274        }
275       
276        /**
277         * checks if the split is currently running
278         */
279        private boolean isRunning() {
280            return (lastStart > -1);
281        }
282       
283        /**
284         * stops the split if it is not already stopped
285         */
286        private void stop() throws IllegalStateException {
287            if (lastStart < 0) {
288                throw new IllegalStateException("split with id " + id + " not running");
289            }
290           
291            lastStop = System.currentTimeMillis();
292            duration += lastStop - lastStart;
293            lastStart = -1;
294        }
295       
296        /**
297         * returns the fist start of the split
298         */
299        private long getFirstStart() {
300            return firstStart;
301        }
302
303        /**
304         * returns the last stop of the split
305         */
306        private long getLastStop() {
307            return lastStop;
308        }
309
310        /**
311         * returns the duration of the split measured so far excluding the time since the last
312         * start if the split is currently started
313         */
314        private long getDuration() {
315            return duration;
316        }
317
318        /**
319         * returns the number of starts for the split
320         */
321        private long getNoOfStarts() {
322            return noOfStarts;
323        }
324    }
325}
Note: See TracBrowser for help on using the repository browser.