001/*
002 * Copyright 2002-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.util;
018
019import java.text.NumberFormat;
020import java.util.LinkedList;
021import java.util.List;
022
023/**
024 * Simple stop watch, allowing for timing of a number of tasks,
025 * exposing total running time and running time for each named task.
026 *
027 * <p>Conceals use of {@code System.currentTimeMillis()}, improving the
028 * readability of application code and reducing the likelihood of calculation errors.
029 *
030 * <p>Note that this object is not designed to be thread-safe and does not
031 * use synchronization.
032 *
033 * <p>This class is normally used to verify performance during proof-of-concepts
034 * and in development, rather than as part of production applications.
035 *
036 * @author Rod Johnson
037 * @author Juergen Hoeller
038 * @author Sam Brannen
039 * @since May 2, 2001
040 */
041public class StopWatch {
042
043        /**
044         * Identifier of this stop watch.
045         * Handy when we have output from multiple stop watches
046         * and need to distinguish between them in log or console output.
047         */
048        private final String id;
049
050        private boolean keepTaskList = true;
051
052        private final List<TaskInfo> taskList = new LinkedList<TaskInfo>();
053
054        /** Start time of the current task */
055        private long startTimeMillis;
056
057        /** Is the stop watch currently running? */
058        private boolean running;
059
060        /** Name of the current task */
061        private String currentTaskName;
062
063        private TaskInfo lastTaskInfo;
064
065        private int taskCount;
066
067        /** Total running time */
068        private long totalTimeMillis;
069
070
071        /**
072         * Construct a new stop watch. Does not start any task.
073         */
074        public StopWatch() {
075                this("");
076        }
077
078        /**
079         * Construct a new stop watch with the given id.
080         * Does not start any task.
081         * @param id identifier for this stop watch.
082         * Handy when we have output from multiple stop watches
083         * and need to distinguish between them.
084         */
085        public StopWatch(String id) {
086                this.id = id;
087        }
088
089
090        /**
091         * Return the id of this stop watch, as specified on construction.
092         * @return the id (empty String by default)
093         * @since 4.2.2
094         * @see #StopWatch(String)
095         */
096        public String getId() {
097                return this.id;
098        }
099
100        /**
101         * Determine whether the TaskInfo array is built over time. Set this to
102         * "false" when using a StopWatch for millions of intervals, or the task
103         * info structure will consume excessive memory. Default is "true".
104         */
105        public void setKeepTaskList(boolean keepTaskList) {
106                this.keepTaskList = keepTaskList;
107        }
108
109
110        /**
111         * Start an unnamed task. The results are undefined if {@link #stop()}
112         * or timing methods are called without invoking this method.
113         * @see #stop()
114         */
115        public void start() throws IllegalStateException {
116                start("");
117        }
118
119        /**
120         * Start a named task. The results are undefined if {@link #stop()}
121         * or timing methods are called without invoking this method.
122         * @param taskName the name of the task to start
123         * @see #stop()
124         */
125        public void start(String taskName) throws IllegalStateException {
126                if (this.running) {
127                        throw new IllegalStateException("Can't start StopWatch: it's already running");
128                }
129                this.running = true;
130                this.currentTaskName = taskName;
131                this.startTimeMillis = System.currentTimeMillis();
132        }
133
134        /**
135         * Stop the current task. The results are undefined if timing
136         * methods are called without invoking at least one pair
137         * {@code start()} / {@code stop()} methods.
138         * @see #start()
139         */
140        public void stop() throws IllegalStateException {
141                if (!this.running) {
142                        throw new IllegalStateException("Can't stop StopWatch: it's not running");
143                }
144                long lastTime = System.currentTimeMillis() - this.startTimeMillis;
145                this.totalTimeMillis += lastTime;
146                this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
147                if (this.keepTaskList) {
148                        this.taskList.add(lastTaskInfo);
149                }
150                ++this.taskCount;
151                this.running = false;
152                this.currentTaskName = null;
153        }
154
155        /**
156         * Return whether the stop watch is currently running.
157         * @see #currentTaskName()
158         */
159        public boolean isRunning() {
160                return this.running;
161        }
162
163        /**
164         * Return the name of the currently running task, if any.
165         * @since 4.2.2
166         * @see #isRunning()
167         */
168        public String currentTaskName() {
169                return this.currentTaskName;
170        }
171
172
173        /**
174         * Return the time taken by the last task.
175         */
176        public long getLastTaskTimeMillis() throws IllegalStateException {
177                if (this.lastTaskInfo == null) {
178                        throw new IllegalStateException("No tasks run: can't get last task interval");
179                }
180                return this.lastTaskInfo.getTimeMillis();
181        }
182
183        /**
184         * Return the name of the last task.
185         */
186        public String getLastTaskName() throws IllegalStateException {
187                if (this.lastTaskInfo == null) {
188                        throw new IllegalStateException("No tasks run: can't get last task name");
189                }
190                return this.lastTaskInfo.getTaskName();
191        }
192
193        /**
194         * Return the last task as a TaskInfo object.
195         */
196        public TaskInfo getLastTaskInfo() throws IllegalStateException {
197                if (this.lastTaskInfo == null) {
198                        throw new IllegalStateException("No tasks run: can't get last task info");
199                }
200                return this.lastTaskInfo;
201        }
202
203
204        /**
205         * Return the total time in milliseconds for all tasks.
206         */
207        public long getTotalTimeMillis() {
208                return this.totalTimeMillis;
209        }
210
211        /**
212         * Return the total time in seconds for all tasks.
213         */
214        public double getTotalTimeSeconds() {
215                return this.totalTimeMillis / 1000.0;
216        }
217
218        /**
219         * Return the number of tasks timed.
220         */
221        public int getTaskCount() {
222                return this.taskCount;
223        }
224
225        /**
226         * Return an array of the data for tasks performed.
227         */
228        public TaskInfo[] getTaskInfo() {
229                if (!this.keepTaskList) {
230                        throw new UnsupportedOperationException("Task info is not being kept!");
231                }
232                return this.taskList.toArray(new TaskInfo[this.taskList.size()]);
233        }
234
235
236        /**
237         * Return a short description of the total running time.
238         */
239        public String shortSummary() {
240                return "StopWatch '" + getId() + "': running time (millis) = " + getTotalTimeMillis();
241        }
242
243        /**
244         * Return a string with a table describing all tasks performed.
245         * For custom reporting, call getTaskInfo() and use the task info directly.
246         */
247        public String prettyPrint() {
248                StringBuilder sb = new StringBuilder(shortSummary());
249                sb.append('\n');
250                if (!this.keepTaskList) {
251                        sb.append("No task info kept");
252                }
253                else {
254                        sb.append("-----------------------------------------\n");
255                        sb.append("ms     %     Task name\n");
256                        sb.append("-----------------------------------------\n");
257                        NumberFormat nf = NumberFormat.getNumberInstance();
258                        nf.setMinimumIntegerDigits(5);
259                        nf.setGroupingUsed(false);
260                        NumberFormat pf = NumberFormat.getPercentInstance();
261                        pf.setMinimumIntegerDigits(3);
262                        pf.setGroupingUsed(false);
263                        for (TaskInfo task : getTaskInfo()) {
264                                sb.append(nf.format(task.getTimeMillis())).append("  ");
265                                sb.append(pf.format(task.getTimeSeconds() / getTotalTimeSeconds())).append("  ");
266                                sb.append(task.getTaskName()).append("\n");
267                        }
268                }
269                return sb.toString();
270        }
271
272        /**
273         * Return an informative string describing all tasks performed
274         * For custom reporting, call {@code getTaskInfo()} and use the task info directly.
275         */
276        @Override
277        public String toString() {
278                StringBuilder sb = new StringBuilder(shortSummary());
279                if (this.keepTaskList) {
280                        for (TaskInfo task : getTaskInfo()) {
281                                sb.append("; [").append(task.getTaskName()).append("] took ").append(task.getTimeMillis());
282                                long percent = Math.round((100.0 * task.getTimeSeconds()) / getTotalTimeSeconds());
283                                sb.append(" = ").append(percent).append("%");
284                        }
285                }
286                else {
287                        sb.append("; no task info kept");
288                }
289                return sb.toString();
290        }
291
292
293        /**
294         * Inner class to hold data about one task executed within the stop watch.
295         */
296        public static final class TaskInfo {
297
298                private final String taskName;
299
300                private final long timeMillis;
301
302                TaskInfo(String taskName, long timeMillis) {
303                        this.taskName = taskName;
304                        this.timeMillis = timeMillis;
305                }
306
307                /**
308                 * Return the name of this task.
309                 */
310                public String getTaskName() {
311                        return this.taskName;
312                }
313
314                /**
315                 * Return the time in milliseconds this task took.
316                 */
317                public long getTimeMillis() {
318                        return this.timeMillis;
319                }
320
321                /**
322                 * Return the time in seconds this task took.
323                 */
324                public double getTimeSeconds() {
325                        return (this.timeMillis / 1000.0);
326                }
327        }
328
329}