content: /*
   * Copyright 2002-2021 the original author or authors.
   *
   * 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
   *
   *      https://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 org.apache.avro.util.springframework;
  
  import org.apache.avro.reflect.Nullable;
  
  import java.text.NumberFormat;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.TimeUnit;
  
  /**
   * Simple stop watch, allowing for timing of a number of tasks, exposing total
   * running time and running time for each named task.
   *
   * <p>
   * Conceals use of {@link System#nanoTime()}, improving the readability of
   * application code and reducing the likelihood of calculation errors.
   *
   * <p>
   * Note that this object is not designed to be thread-safe and does not use
   * synchronization.
   *
   * <p>
   * This class is normally used to verify performance during proof-of-concept
   * work and in development, rather than as part of production applications.
   *
   * <p>
   * As of Spring Framework 5.2, running time is tracked and reported in
   * nanoseconds.
   *
   * @author Rod Johnson
   * @author Juergen Hoeller
   * @author Sam Brannen
   * @since May 2, 2001
   */
  class StopWatch {
  
    /**
     * Identifier of this {@code StopWatch}.
     * <p>
     * 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 ArrayList<>(1);
  
    /** Start time of the current task. */
    private long startTimeNanos;
  
    /** Name of the current task. */
    @Nullable
    private String currentTaskName;
  
    @Nullable
    private TaskInfo lastTaskInfo;
  
    private int taskCount;
  
    /** Total running time. */
    private long totalTimeNanos;
  
    /**
     * Construct a new {@code StopWatch}.
     * <p>
     * Does not start any task.
     */
    public StopWatch() {
      this("");
    }
  
    /**
     * Construct a new {@code StopWatch} with the given ID.
     * <p>
     * The ID is handy when we have output from multiple stop watches and need to
     * distinguish between them.
     * <p>
     * Does not start any task.
     *
     * @param id identifier for this stop watch
     */
    public StopWatch(String id) {
      this.id = id;
    }
  
    /**
     * Get the ID of this {@code StopWatch}, as specified on construction.
     *
     * @return the ID (empty String by default)
     * @since 4.2.2
     * @see #StopWatch(String)
     */
    public String getId() {
      return this.id;
    }
  
    /**
     * Configure whether the {@link TaskInfo} array is built over time.
     * <p>
     * Set this to {@code false} when using a {@code StopWatch} for millions of
     * intervals; otherwise, the {@code TaskInfo} structure will consume excessive
     * memory.
     * <p>
     * Default is {@code true}.
     */
    public void setKeepTaskList(boolean keepTaskList) {
      this.keepTaskList = keepTaskList;
    }
  
    /**
     * Start an unnamed task.
     * <p>
     * The results are undefined if {@link #stop()} or timing methods are called
     * without invoking this method first.
     *
     * @see #start(String)
     * @see #stop()
     */
    public void start() throws IllegalStateException {
      start("");
    }
  
    /**
     * Start a named task.
     * <p>
     * The results are undefined if {@link #stop()} or timing methods are called
     * without invoking this method first.
     *
     * @param taskName the name of the task to start
     * @see #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.startTimeNanos = System.nanoTime();
    }
  
    /**
     * Stop the current task.
     * <p>
     * The results are undefined if timing methods are called without invoking at
     * least one pair of {@code start()} / {@code stop()} methods.
     *
     * @see #start()
     * @see #start(String)
     */
    public void stop() throws IllegalStateException {
      if (this.currentTaskName == null) {
        throw new IllegalStateException("Can't stop StopWatch: it's not running");
      }
      long lastTime = System.nanoTime() - this.startTimeNanos;
      this.totalTimeNanos += lastTime;
      this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
      if (this.keepTaskList) {
        this.taskList.add(this.lastTaskInfo);
      }
      ++this.taskCount;
      this.currentTaskName = null;
    }
  
    /**
     * Determine whether this {@code StopWatch} is currently running.
     *
     * @see #currentTaskName()
     */
    public boolean isRunning() {
      return (this.currentTaskName != null);
    }
  
    /**
     * Get the name of the currently running task, if any.
     *
     * @since 4.2.2
     * @see #isRunning()
     */
    @Nullable
    public String currentTaskName() {
      return this.currentTaskName;
    }
  
    /**
     * Get the time taken by the last task in nanoseconds.
     *
     * @since 5.2
     * @see #getLastTaskTimeMillis()
     */
    public long getLastTaskTimeNanos() throws IllegalStateException {
      if (this.lastTaskInfo == null) {
        throw new IllegalStateException("No tasks run: can't get last task interval");
      }
      return this.lastTaskInfo.getTimeNanos();
    }
  
    /**
     * Get the time taken by the last task in milliseconds.
     *
     * @see #getLastTaskTimeNanos()
     */
    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();
    }
  
    /**
     * Get 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();
    }
  
    /**
     * Get the last task as a {@link 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;
    }
  
    /**
     * Get the total time in nanoseconds for all tasks.
     *
     * @since 5.2
     * @see #getTotalTimeMillis()
     * @see #getTotalTimeSeconds()
     */
    public long getTotalTimeNanos() {
      return this.totalTimeNanos;
    }
  
    /**
     * Get the total time in milliseconds for all tasks.
     *
     * @see #getTotalTimeNanos()
     * @see #getTotalTimeSeconds()
     */
    public long getTotalTimeMillis() {
      return nanosToMillis(this.totalTimeNanos);
    }
  
    /**
     * Get the total time in seconds for all tasks.
     *
     * @see #getTotalTimeNanos()
     * @see #getTotalTimeMillis()
     */
    public double getTotalTimeSeconds() {
      return nanosToSeconds(this.totalTimeNanos);
    }
  
    /**
     * Get the number of tasks timed.
     */
    public int getTaskCount() {
      return this.taskCount;
    }
  
    /**
     * Get 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]);
    }
  
    /**
     * Get a short description of the total running time.
     */
    public String shortSummary() {
      return "StopWatch '" + getId() + "': running time = " + getTotalTimeNanos() + " ns";
    }
  
    /**
     * Generate a string with a table describing all tasks performed.
     * <p>
     * For custom reporting, call {@link #getTaskInfo()} and use the task info
     * directly.
     */
    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("ns         %     Task name\n");
        sb.append("---------------------------------------------\n");
        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMinimumIntegerDigits(9);
        nf.setGroupingUsed(false);
        NumberFormat pf = NumberFormat.getPercentInstance();
        pf.setMinimumIntegerDigits(3);
        pf.setGroupingUsed(false);
        for (TaskInfo task : getTaskInfo()) {
          sb.append(nf.format(task.getTimeNanos())).append("  ");
          sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append("  ");
          sb.append(task.getTaskName()).append('\n');
        }
      }
      return sb.toString();
    }
  
    /**
     * Generate an informative string describing all tasks performed
     * <p>
     * For custom reporting, call {@link #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.getTimeNanos()).append(" ns");
          long percent = Math.round(100.0 * task.getTimeNanos() / getTotalTimeNanos());
          sb.append(" = ").append(percent).append('%');
        }
      } else {
        sb.append("; no task info kept");
      }
      return sb.toString();
    }
  
    private static long nanosToMillis(long duration) {
      return TimeUnit.NANOSECONDS.toMillis(duration);
    }
  
    private static double nanosToSeconds(long duration) {
      return duration / 1_000_000_000.0;
    }
  
    /**
     * Nested class to hold data about one task executed within the
     * {@code StopWatch}.
     */
    public static final class TaskInfo {
  
      private final String taskName;
  
      private final long timeNanos;
  
      TaskInfo(String taskName, long timeNanos) {
        this.taskName = taskName;
        this.timeNanos = timeNanos;
      }
  
      /**
       * Get the name of this task.
       */
      public String getTaskName() {
        return this.taskName;
      }
  
      /**
       * Get the time in nanoseconds this task took.
       *
       * @since 5.2
       * @see #getTimeMillis()
       * @see #getTimeSeconds()
       */
      public long getTimeNanos() {
        return this.timeNanos;
      }
  
      /**
       * Get the time in milliseconds this task took.
       *
       * @see #getTimeNanos()
       * @see #getTimeSeconds()
       */
      public long getTimeMillis() {
        return nanosToMillis(this.timeNanos);
      }
  
      /**
       * Get the time in seconds this task took.
       *
       * @see #getTimeMillis()
       * @see #getTimeNanos()
       */
      public double getTimeSeconds() {
        return nanosToSeconds(this.timeNanos);
      }
  
    }
  
  }
  