Spring 計時器 StopWatch

有時候要測試某個東東的執行效率,每次都得寫一堆 System.currentTimeMillis(),很顯然一般會封裝一下,避免重複造輪子,可以直接使用 Spring 提供的工具 StopWatch。其實在 Spring Boot 程序啓動後都會打印的啓動時間,也是基於這個類來的:

2020-04-25 17:09:41.468  INFO 60479 --- [           main] d.d.m.s.n.bootstrap.ConditionBootstrap   : Started ConditionBootstrap in 1.006 seconds (JVM running for 1.688)

這個類應該是 Spring 最簡單的類之一了,代碼非常非常的簡單,沒有那些複雜的功能,適用於很簡單的場景。結合 startstop 方法來看,對於單個 StopWatch 對象來說,只能開一個計時,任務執行完成後關閉計時,然後再開一個這樣操作,所以在多線程的環境下使用一定要注意,儘量避免 StopWatch 成爲公共變量。如果在多線程下有特殊需求,可以對 StopWatch 進行改造或者結合 ThreadLocal 來做。如果想做到無侵入性統計,可以基於 Java Agent 來做。

public class StopWatch {

	/**
	 * Identifier of this stop watch.
	 * Handy when we have output from multiple stop watches
	 * and need to distinguish between them in log or console output.
	 */
	//唯一標識
	private final String id;

  //是否需要保存執行的任務
	private boolean keepTaskList = true;

  //記錄所有執行的任務
	private final List<TaskInfo> taskList = new LinkedList<>();

	/** Start time of the current task. */
	private long startTimeMillis;

	/** Name of the current task. */
	@Nullable
	private String currentTaskName;

  //最近的任務
	@Nullable
	private TaskInfo lastTaskInfo;

	private int taskCount;

	/** Total running time. */
	private long totalTimeMillis;


	/**
	 * Construct a new stop watch. Does not start any task.
	 */
	public StopWatch() {
		this("");
	}

...

	/**
	 * Start an unnamed task. The results are undefined if {@link #stop()}
	 * or timing methods are called without invoking this method.
	 * @see #stop()
	 */
	public void start() throws IllegalStateException {
		start("");
	}

	/**
	 * Start a named task. The results are undefined if {@link #stop()}
	 * or timing methods are called without invoking this method.
	 * @param taskName the name of the task to start
	 * @see #stop()
	 */
  //開始任務
	public void start(String taskName) throws IllegalStateException {
    //不能連續開啓任務,只能開一個關一個
		if (this.currentTaskName != null) {
			throw new IllegalStateException("Can't start StopWatch: it's already running");
		}
		this.currentTaskName = taskName;
		this.startTimeMillis = System.currentTimeMillis();
	}

	/**
	 * Stop the current task. The results are undefined if timing
	 * methods are called without invoking at least one pair
	 * {@code start()} / {@code stop()} methods.
	 * @see #start()
	 */
  //結束任務,要注意這裏不能指定任務名稱,也就是說只能開一個關一個這樣去執行
	public void stop() throws IllegalStateException {
		if (this.currentTaskName == null) {
			throw new IllegalStateException("Can't stop StopWatch: it's not running");
		}
		long lastTime = System.currentTimeMillis() - this.startTimeMillis;
		this.totalTimeMillis += lastTime;
		this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
		if (this.keepTaskList) {
			this.taskList.add(this.lastTaskInfo);
		}
		++this.taskCount;
    //將當前任務名稱置爲 null,所以判斷有沒有任務在執行可以根據這個參數是否爲null進行判斷
		this.currentTaskName = null;
	}

	/**
	 * Return whether the stop watch is currently running.
	 * @see #currentTaskName()
	 */
	public boolean isRunning() {
		return (this.currentTaskName != null);
	}

	/**
	 * Return the name of the currently running task, if any.
	 * @since 4.2.2
	 * @see #isRunning()
	 */
	@Nullable
	public String currentTaskName() {
		return this.currentTaskName;
	}


	/**
	 * Return the time taken by the last task.
	 */
	public long getLastTaskTimeMillis() throws IllegalStateException {
		if (this.lastTaskInfo == null) {
			throw new IllegalStateException("No tasks run: can't get last task interval");
		}
		return this.lastTaskInfo.getTimeMillis();
	}

	/**
	 * Return the name of the last task.
	 */
	public String getLastTaskName() throws IllegalStateException {
		if (this.lastTaskInfo == null) {
			throw new IllegalStateException("No tasks run: can't get last task name");
		}
		return this.lastTaskInfo.getTaskName();
	}

	/**
	 * Return the last task as a TaskInfo object.
	 */
	public TaskInfo getLastTaskInfo() throws IllegalStateException {
		if (this.lastTaskInfo == null) {
			throw new IllegalStateException("No tasks run: can't get last task info");
		}
		return this.lastTaskInfo;
	}


	/**
	 * Return the total time in milliseconds for all tasks.
	 */
	public long getTotalTimeMillis() {
		return this.totalTimeMillis;
	}

	/**
	 * Return the total time in seconds for all tasks.
	 */
	public double getTotalTimeSeconds() {
		return this.totalTimeMillis / 1000.0;
	}

	/**
	 * Return the number of tasks timed.
	 */
	public int getTaskCount() {
		return this.taskCount;
	}

	/**
	 * Return an array of the data for tasks performed.
	 */
	public TaskInfo[] getTaskInfo() {
		if (!this.keepTaskList) {
			throw new UnsupportedOperationException("Task info is not being kept!");
		}
		return this.taskList.toArray(new TaskInfo[0]);
	}


	/**
	 * Return a short description of the total running time.
	 */
	public String shortSummary() {
		return "StopWatch '" + getId() + "': running time (millis) = " + getTotalTimeMillis();
	}

	/**
	 * Return a string with a table describing all tasks performed.
	 * For custom reporting, call getTaskInfo() and use the task info directly.
	 */
  //打印任務信息,這裏會 format 成一個類似表格的統計信息
	public String prettyPrint() {
		StringBuilder sb = new StringBuilder(shortSummary());
		sb.append('\n');
		if (!this.keepTaskList) {
			sb.append("No task info kept");
		}
		else {
			sb.append("-----------------------------------------\n");
			sb.append("ms     %     Task name\n");
			sb.append("-----------------------------------------\n");
			NumberFormat nf = NumberFormat.getNumberInstance();
			nf.setMinimumIntegerDigits(5);
			nf.setGroupingUsed(false);
			NumberFormat pf = NumberFormat.getPercentInstance();
			pf.setMinimumIntegerDigits(3);
			pf.setGroupingUsed(false);
			for (TaskInfo task : getTaskInfo()) {
				sb.append(nf.format(task.getTimeMillis())).append("  ");
				sb.append(pf.format(task.getTimeSeconds() / getTotalTimeSeconds())).append("  ");
				sb.append(task.getTaskName()).append("\n");
			}
		}
		return sb.toString();
	}

	/**
	 * Return an informative string describing all tasks performed
	 * For custom reporting, call {@code getTaskInfo()} and use the task info directly.
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder(shortSummary());
		if (this.keepTaskList) {
			for (TaskInfo task : getTaskInfo()) {
				sb.append("; [").append(task.getTaskName()).append("] took ").append(task.getTimeMillis());
				long percent = Math.round((100.0 * task.getTimeSeconds()) / getTotalTimeSeconds());
				sb.append(" = ").append(percent).append("%");
			}
		}
		else {
			sb.append("; no task info kept");
		}
		return sb.toString();
	}


	/**
	 * Inner class to hold data about one task executed within the stop watch.
	 */
  //任務類
	public static final class TaskInfo {

		private final String taskName;

		private final long timeMillis;

		TaskInfo(String taskName, long timeMillis) {
			this.taskName = taskName;
			this.timeMillis = timeMillis;
		}

		/**
		 * Return the name of this task.
		 */
		public String getTaskName() {
			return this.taskName;
		}

		/**
		 * Return the time in milliseconds this task took.
		 */
		public long getTimeMillis() {
			return this.timeMillis;
		}

		/**
		 * Return the time in seconds this task took.
		 */
		public double getTimeSeconds() {
			return (this.timeMillis / 1000.0);
		}
	}

}

先看一個簡單的使用事例:

public class STDemo {

    private static StopWatch sw = new StopWatch("20200425_Test");

    private static ObjectMapper mapper = new ObjectMapper();

    public static void main(String[] args) {

        sw.start("task1");
        sleep(1000);
        printInfo();
        sw.stop();
        printInfo();
        sw.start("task2");
        printInfo();
        sleep(2000);
        sw.stop();
        printInfo();
        sw.start("task3");
        sleep(3000);
        printInfo();
        sw.stop();
        printInfo();
        System.out.println(sw.prettyPrint());
    }

    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException ignored) {
        }
    }

    private static void printInfo() {
        //System.out.println("lastTaskInfo:" + sw.getLastTaskInfo());
        Stream.of(sw.getTaskInfo()).forEach(a-> {
            try {
                System.out.println(mapper.writeValueAsString(a));
            } catch (JsonProcessingException ignored) {
            }
        });
        System.out.println("-----------------------");
    }
}

歡迎關注公衆號
​​​​​​在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章