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}