View Javadoc
1   /*
2    * Copyright 2016 Function1, Inc. All Rights Reserved.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package tools.gsf.time;
17  
18  import org.slf4j.Logger;
19  import org.slf4j.LoggerFactory;
20  
21  /**
22   * Stopwatch that records records into a logger. If the logger specified is not enabled at the debug level,
23   * this timer does nothing. Designed for performance measurements.
24   *
25   * Very lightweight object. Upon instantiation, the current time is measured once.
26   *
27   * @author Tony Field
28   * @since 2016-07-17
29   */
30  public final class LoggerStopwatch implements Stopwatch {
31  
32      /**
33       * Default logger name used by this timer if no other logger is named.
34       */
35      public static final String DEFAULT_LOGGER_NAME = "tools.gsf.time";
36  
37      /**
38       * Stopwatch message format string. This format will be used for all split measurements.
39       * The first {} marker will be replaced by the time in microseconds. The second marker will be replaced
40       * by the human-readable version of the time. The provided message will be appended to the end.
41       */
42      public static final String MESSAGE_FORMAT_SPLIT = "Split timer measurement: {}us ({}) for: ";
43  
44      /**
45       * Stopwatch message format string. This format will be used for all cumulative measurements.
46       * The first {} marker will be replaced by the time in microseconds. The second marker will be replaced
47       * by the human-readable version of the time. The provided message will be appended to the end.
48       */
49      public static final String MESSAGE_FORMAT_ELAPSED = "Elapsed timer measurement: {}us ({}) for: ";
50  
51      private final Logger logger;
52      private long startTimeNanos;
53      private long lastIntervalTimeNanos;
54  
55      /**
56       * Create a logger stopwatch using the logger specified. If debug is not enabled, the NoopStopwatch is returned
57       * @param logger logger instance
58       * @return the stopwatch or the NoopStopwatch if debug is not enabled
59       * @see NoopStopwatch
60       */
61      public static Stopwatch getInstance(Logger logger) {
62          return logger.isDebugEnabled() ? new LoggerStopwatch(logger) : NoopStopwatch.INSTANCE;
63      }
64  
65      /**
66       * Create a logger stopwatch using the logger specified. If debug is not enabled, the NoopStopwatch is returned
67       * @param logger logger instance
68       * @return the stopwatch or the NoopStopwatch if debug is not enabled
69       * @see NoopStopwatch
70       */
71      public static Stopwatch getInstance(String logger) {
72          return getInstance(LoggerFactory.getLogger(logger));
73      }
74  
75      /**
76       * Create a stopwatch using the default logger, if debug is enabled. If debug is not enabled, the NoopStopwatch
77       * is returned.
78       * @return the stopwatch or the NoopStopwatch if debug is not enabled
79       * @see NoopStopwatch
80       * @see #DEFAULT_LOGGER_NAME
81       */
82      public static Stopwatch getInstance() {
83          return getInstance(DEFAULT_LOGGER_NAME);
84      }
85  
86      private LoggerStopwatch(Logger logger) {
87          this.logger = logger;
88          this.startTimeNanos = 0L;
89          this.lastIntervalTimeNanos = 0L;
90      }
91  
92      @Override
93      public void start() {
94          startTimeNanos = System.nanoTime();
95          lastIntervalTimeNanos = startTimeNanos;
96      }
97  
98      public void split(String msg) {
99          if (startTimeNanos == 0L) throw new IllegalStateException("Not yet started. Call start() first.");
100         long now = System.nanoTime();
101         _log(lastIntervalTimeNanos, now, true, msg);
102         lastIntervalTimeNanos = now;
103     }
104 
105     public void split(String msg, Object... args) {
106         if (startTimeNanos == 0L) throw new IllegalStateException("Not yet started. Call start() first.");
107         long now = System.nanoTime();
108         _log(lastIntervalTimeNanos, now, true, msg, args);
109         lastIntervalTimeNanos = now;
110     }
111 
112     public void elapsed(String msg) {
113         if (startTimeNanos == 0L) throw new IllegalStateException("Not yet started. Call start() first.");
114         long now = System.nanoTime();
115         _log(startTimeNanos, now, false, msg);
116     }
117 
118     public void elapsed(String msg, Object... args) {
119         if (startTimeNanos == 0L) throw new IllegalStateException("Not yet started. Call start() first.");
120         long now = System.nanoTime();
121         _log(startTimeNanos, now, false, msg, args);
122     }
123 
124     private void _log(long startNanos, long endNanos, boolean split, String msg, Object... args) {
125 
126         long micros = (endNanos - startNanos) / 1000;
127         String sMicros = Long.toString(micros);
128         String human = _humanFormat(micros);
129 
130         String logmsg = split ? MESSAGE_FORMAT_SPLIT : MESSAGE_FORMAT_ELAPSED;
131         logmsg += msg;
132 
133         if (args == null || args.length == 0) {
134             logger.debug(logmsg, sMicros, human);
135         } else {
136             Object[] arguments = new Object[args.length + 2];
137             arguments[0] = sMicros;
138             arguments[1] = human;
139             System.arraycopy(args, 0, arguments, 2, args.length);
140             logger.debug(logmsg, arguments);
141         }
142     }
143 
144     private String _humanFormat(long micros) {
145         String human;
146         if (micros > 1000000) {
147             final long millis = micros / 1000;
148             human = Long.toString(millis / 1000) + "." + String.format("%03d", (millis % 1000)) + "s";
149         } else if (micros > 1000) {
150             human = Long.toString(micros / 1000) + "." + String.format("%03d", (micros % 1000)) + "ms";
151         } else {
152             human = Long.toString(micros) + "us";
153         }
154         return human;
155     }
156 }