/* * Copyright 2016 Function1, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tools.gsf.time; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Stopwatch that records records into a logger. If the logger specified is not enabled at the debug level, * this timer does nothing. Designed for performance measurements. * * Very lightweight object. Upon instantiation, the current time is measured once. * * @author Tony Field * @since 2016-07-17 */ public final class LoggerStopwatch implements Stopwatch { /** * Default logger name used by this timer if no other logger is named. */ public static final String DEFAULT_LOGGER_NAME = "tools.gsf.time"; /** * Stopwatch message format string. This format will be used for all split measurements. * The first {} marker will be replaced by the time in microseconds. The second marker will be replaced * by the human-readable version of the time. The provided message will be appended to the end. */ public static final String MESSAGE_FORMAT_SPLIT = "Split timer measurement: {}us ({}) for: "; /** * Stopwatch message format string. This format will be used for all cumulative measurements. * The first {} marker will be replaced by the time in microseconds. The second marker will be replaced * by the human-readable version of the time. The provided message will be appended to the end. */ public static final String MESSAGE_FORMAT_ELAPSED = "Elapsed timer measurement: {}us ({}) for: "; private final Logger logger; private long startTimeNanos; private long lastIntervalTimeNanos; /** * Create a logger stopwatch using the logger specified. If debug is not enabled, the NoopStopwatch is returned * @param logger logger instance * @return the stopwatch or the NoopStopwatch if debug is not enabled * @see NoopStopwatch */ public static Stopwatch getInstance(Logger logger) { return logger.isDebugEnabled() ? new LoggerStopwatch(logger) : NoopStopwatch.INSTANCE; } /** * Create a logger stopwatch using the logger specified. If debug is not enabled, the NoopStopwatch is returned * @param logger logger instance * @return the stopwatch or the NoopStopwatch if debug is not enabled * @see NoopStopwatch */ public static Stopwatch getInstance(String logger) { return getInstance(LoggerFactory.getLogger(logger)); } /** * Create a stopwatch using the default logger, if debug is enabled. If debug is not enabled, the NoopStopwatch * is returned. * @return the stopwatch or the NoopStopwatch if debug is not enabled * @see NoopStopwatch * @see #DEFAULT_LOGGER_NAME */ public static Stopwatch getInstance() { return getInstance(DEFAULT_LOGGER_NAME); } private LoggerStopwatch(Logger logger) { this.logger = logger; this.startTimeNanos = 0L; this.lastIntervalTimeNanos = 0L; } @Override public void start() { startTimeNanos = System.nanoTime(); lastIntervalTimeNanos = startTimeNanos; } public void split(String msg) { if (startTimeNanos == 0L) throw new IllegalStateException("Not yet started. Call start() first."); long now = System.nanoTime(); _log(lastIntervalTimeNanos, now, true, msg); lastIntervalTimeNanos = now; } public void split(String msg, Object... args) { if (startTimeNanos == 0L) throw new IllegalStateException("Not yet started. Call start() first."); long now = System.nanoTime(); _log(lastIntervalTimeNanos, now, true, msg, args); lastIntervalTimeNanos = now; } public void elapsed(String msg) { if (startTimeNanos == 0L) throw new IllegalStateException("Not yet started. Call start() first."); long now = System.nanoTime(); _log(startTimeNanos, now, false, msg); } public void elapsed(String msg, Object... args) { if (startTimeNanos == 0L) throw new IllegalStateException("Not yet started. Call start() first."); long now = System.nanoTime(); _log(startTimeNanos, now, false, msg, args); } private void _log(long startNanos, long endNanos, boolean split, String msg, Object... args) { long micros = (endNanos - startNanos) / 1000; String sMicros = Long.toString(micros); String human = _humanFormat(micros); String logmsg = split ? MESSAGE_FORMAT_SPLIT : MESSAGE_FORMAT_ELAPSED; logmsg += msg; if (args == null || args.length == 0) { logger.debug(logmsg, sMicros, human); } else { Object[] arguments = new Object[args.length + 2]; arguments[0] = sMicros; arguments[1] = human; System.arraycopy(args, 0, arguments, 2, args.length); logger.debug(logmsg, arguments); } } private String _humanFormat(long micros) { String human; if (micros > 1000000) { final long millis = micros / 1000; human = Long.toString(millis / 1000) + "." + String.format("%03d", (millis % 1000)) + "s"; } else if (micros > 1000) { human = Long.toString(micros / 1000) + "." + String.format("%03d", (micros % 1000)) + "ms"; } else { human = Long.toString(micros) + "us"; } return human; } }