content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You 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 org.apache.arrow.memory.util;
  
  import com.google.errorprone.annotations.FormatMethod;
  import com.google.errorprone.annotations.FormatString;
  import java.util.ArrayDeque;
  import java.util.Arrays;
  import java.util.Deque;
  import org.checkerframework.checker.nullness.qual.Nullable;
  import org.slf4j.Logger;
  
  /**
   * Utility class that can be used to log activity within a class for later logging and debugging.
   * Supports recording events and recording the stack at the time they occur.
   */
  public class HistoricalLog {
  
    private final Deque<Event> history = new ArrayDeque<>();
    private final String idString; // the formatted id string
    private final int limit; // the limit on the number of events kept
    private @Nullable Event firstEvent; // the first stack trace recorded
  
    /**
     * Constructor. The format string will be formatted and have its arguments substituted at the time
     * this is called.
     *
     * @param idStringFormat {@link String#format} format string that can be used to identify this
     *     object in a log. Including some kind of unique identifier that can be associated with the
     *     object instance is best.
     * @param args for the format string, or nothing if none are required
     */
    @FormatMethod
    public HistoricalLog(@FormatString final String idStringFormat, Object... args) {
      this(Integer.MAX_VALUE, idStringFormat, args);
    }
  
    /**
     * Constructor. The format string will be formatted and have its arguments substituted at the time
     * this is called.
     *
     * <p>This form supports the specification of a limit that will limit the number of historical
     * entries kept (which keeps down the amount of memory used). With the limit, the first entry made
     * is always kept (under the assumption that this is the creation site of the object, which is
     * usually interesting), and then up to the limit number of entries are kept after that. Each time
     * a new entry is made, the oldest that is not the first is dropped.
     *
     * @param limit the maximum number of historical entries that will be kept, not including the
     *     first entry made
     * @param idStringFormat {@link String#format} format string that can be used to identify this
     *     object in a log. Including some kind of unique identifier that can be associated with the
     *     object instance is best.
     * @param args for the format string, or nothing if none are required
     */
    @FormatMethod
    public HistoricalLog(final int limit, @FormatString final String idStringFormat, Object... args) {
      this.limit = limit;
      this.idString = String.format(idStringFormat, args);
      this.firstEvent = null;
    }
  
    /**
     * Record an event. Automatically captures the stack trace at the time this is called. The format
     * string will be formatted and have its arguments substituted at the time this is called.
     *
     * @param noteFormat {@link String#format} format string that describes the event
     * @param args for the format string, or nothing if none are required
     */
    @FormatMethod
    public synchronized void recordEvent(@FormatString final String noteFormat, Object... args) {
      final String note = String.format(noteFormat, args);
      final Event event = new Event(note);
      if (firstEvent == null) {
        firstEvent = event;
      }
      if (history.size() == limit) {
        history.removeFirst();
      }
      history.add(event);
    }
  
    /**
     * Write the history of this object to the given {@link StringBuilder}. The history includes the
     * identifying string provided at construction time, and all the recorded events with their stack
     * traces.
     *
     * @param sb {@link StringBuilder} to write to
     * @param includeStackTrace whether to include the stacktrace of each event in the history
     */
    public void buildHistory(final StringBuilder sb, boolean includeStackTrace) {
      buildHistory(sb, 0, includeStackTrace);
    }
  
    /**
     * Build the history and write it to sb.
     *
     * @param sb output
     * @param indent starting indent (usually "")
     * @param includeStackTrace whether to include the stacktrace of each event.
     */
    public synchronized void buildHistory(
        final StringBuilder sb, int indent, boolean includeStackTrace) {
      final char[] indentation = new char[indent];
      final char[] innerIndentation = new char[indent + 2];
      Arrays.fill(indentation, ' ');
      Arrays.fill(innerIndentation, ' ');
  
      sb.append(indentation).append("event log for: ").append(idString).append('\n');
  
      if (firstEvent != null) {
        long time = firstEvent.time;
        String note = firstEvent.note;
        final StackTrace stackTrace = firstEvent.stackTrace;
        sb.append(innerIndentation).append(time).append(' ').append(note).append('\n');
        if (includeStackTrace) {
          stackTrace.writeToBuilder(sb, indent + 2);
        }
  
        for (final Event event : history) {
          if (event == firstEvent) {
            continue;
          }
          sb.append(innerIndentation)
              .append("  ")
              .append(event.time)
              .append(' ')
              .append(event.note)
              .append('\n');
  
          if (includeStackTrace) {
            event.stackTrace.writeToBuilder(sb, indent + 2);
            sb.append('\n');
          }
        }
      }
    }
  
    /**
     * Write the history of this object to the given {@link Logger}. The history includes the
     * identifying string provided at construction time, and all the recorded events with their stack
     * traces.
     *
     * @param logger {@link Logger} to write to
     */
    public void logHistory(final Logger logger) {
      final StringBuilder sb = new StringBuilder();
      buildHistory(sb, 0, true);
      logger.debug(sb.toString());
    }
  
    private static class Event {
  
      private final String note; // the event text
      private final StackTrace stackTrace; // where the event occurred
      private final long time;
  
      public Event(final String note) {
        this.note = note;
        this.time = System.nanoTime();
        stackTrace = new StackTrace();
      }
    }
  }
  