// Copyright 2025 The Bazel Authors. 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 com.google.devtools.build.lib.profiler;

import com.google.common.util.concurrent.ListenableFuture;
import com.google.devtools.build.lib.clock.Clock;
import com.google.devtools.build.lib.runtime.BlazeService;
import com.google.devtools.build.lib.skybridge.SkybridgeInterface;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.io.OutputStream;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Interface for the Blaze internal profiler. Provides facility to report various Blaze tasks and
 * store them (asynchronously) in the file for future analysis.
 *
 * <p>Implemented as singleton so any caller should use Profiler.instance() to obtain reference.
 *
 * <p>Internally, profiler uses two data structures - ThreadLocal task stack to track nested tasks
 * and single ConcurrentLinkedQueue to gather all completed tasks.
 *
 * <p>Also, due to the nature of the provided functionality (instrumentation of all Blaze
 * components), build.lib.profiler package will be used by almost every other Blaze package, so
 * special attention should be paid to avoid any dependencies on the rest of the Blaze code,
 * including build.lib.util and build.lib.vfs. This is important because build.lib.util and
 * build.lib.vfs contain Profiler invocations and any dependency on those two packages would create
 * circular relationship.
 *
 * @see ProfilerTask enum for recognized task types.
 */
@SkybridgeInterface
@SuppressWarnings("GoodTime") // This code is very performance sensitive.
public interface TraceProfilerService extends BlazeService {

  /** File format. */
  public final class Format {
    public static final Format JSON_TRACE_FILE_FORMAT = new Format();
    public static final Format JSON_TRACE_FILE_COMPRESSED_FORMAT = new Format();

    private Format() {}
  }

  /** Returns the nanoTime of the current profiler instance, or -1 if not active. */
  long nanoTimeMaybe();

  /** Returns true iff profiling is currently enabled. */
  boolean isActive();

  /**
   * Records the beginning of a task as specified, and returns a {@link SilentCloseable} instance
   * that ends the task. This lets the system do the work of ending the task, with the compiler
   * giving a warning if the returned instance is not closed.
   *
   * <p>Use of this method allows to support nested task monitoring. For tasks that are known to not
   * have any subtasks, logSimpleTask() should be used instead.
   *
   * <p>Use like this:
   *
   * <pre>{@code
   * try (SilentCloseable c = Profiler.instance().profile(type, "description")) {
   *   // Your code here.
   * }
   * }</pre>
   *
   * @param type predefined task type - see ProfilerTask for available types.
   * @param description task description. May be stored until the end of the build.
   */
  SilentCloseable profile(ProfilerTask type, String description);

  /**
   * Version of {@link #profile(ProfilerTask, String)} that avoids creating string unless actually
   * profiling.
   */
  SilentCloseable profile(ProfilerTask type, Supplier<String> description);

  /**
   * Records the beginning of a task as specified, and returns a {@link SilentCloseable} instance
   * that ends the task. This lets the system do the work of ending the task, with the compiler
   * giving a warning if the returned instance is not closed.
   *
   * <p>Use of this method allows to support nested task monitoring. For tasks that are known to not
   * have any subtasks, logSimpleTask() should be used instead.
   *
   * <p>This is a convenience method that uses {@link ProfilerTask#INFO}.
   *
   * <p>Use like this:
   *
   * <pre>{@code
   * try (SilentCloseable c = Profiler.instance().profile("description")) {
   *   // Your code here.
   * }
   * }</pre>
   *
   * @param description task description. May be stored until the end of the build.
   */
  SilentCloseable profile(String description);

  /**
   * Used externally to submit simple task (one that does not have any subtasks). Depending on the
   * minDuration attribute of the task type, task may be just aggregated into the parent task and
   * not stored directly.
   *
   * @param startTimeNanos task start time (obtained through {@link Profiler#nanoTimeMaybe()})
   * @param type task type
   * @param description task description. May be stored until the end of the build.
   */
  void logSimpleTask(long startTimeNanos, ProfilerTask type, String description);

  /**
   * Used externally to submit simple task (one that does not have any subtasks). Depending on the
   * minDuration attribute of the task type, task may be just aggregated into the parent task and
   * not stored directly.
   *
   * <p>Note that start and stop time must both be acquired from the same clock instance.
   *
   * @param startTimeNanos task start time
   * @param stopTimeNanos task stop time
   * @param type task type
   * @param description task description. May be stored until the end of the build.
   */
  void logSimpleTask(
      long startTimeNanos, long stopTimeNanos, ProfilerTask type, String description);

  /**
   * Used externally to submit simple task (one that does not have any subtasks). Depending on the
   * minDuration attribute of the task type, task may be just aggregated into the parent task and
   * not stored directly.
   *
   * @param startTimeNanos task start time (obtained through {@link Profiler#nanoTimeMaybe()})
   * @param duration the duration of the task
   * @param type task type
   * @param description task description. May be stored until the end of the build.
   */
  void logSimpleTaskDuration(
      long startTimeNanos, Duration duration, ProfilerTask type, String description);

  /** Used to log "events" happening at a specific time - tasks with zero duration. */
  void logEventAtTime(long atTimeNanos, ProfilerTask type, String description);

  /** Used to log "events" - tasks with zero duration. */
  void logEvent(ProfilerTask type, String description);

  /** Sets the heuristics for determining the filesystem type of a given path. */
  void setVfsTypeHeuristics(Map<String, ? extends Predicate<? super String>> vfsTypeHeuristics);

  /**
   * Enable profiling.
   *
   * <p>Subsequent calls to beginTask/endTask will be recorded in the provided output stream. Please
   * note that stream performance is extremely important and buffered streams should be utilized.
   *
   * @param profiledTasks which of {@link ProfilerTask}s to track
   * @param stream output stream to store profile data. Note: passing unbuffered stream object
   *     reference may result in significant performance penalties
   * @param recordAllDurations iff true, record all tasks regardless of their duration; otherwise
   *     some tasks may get aggregated if they finished quick enough
   * @param clock a {@code BlazeClock.instance()}
   * @param execStartTimeNanos execution start time in nanos obtained from {@code clock.nanoTime()}
   */
  void start(
      Set<ProfilerTask> profiledTasks,
      OutputStream stream,
      Format format,
      String outputBase,
      UUID buildID,
      boolean recordAllDurations,
      Clock clock,
      long execStartTimeNanos,
      boolean slimProfile,
      long slimProfileSizeLimit,
      boolean includePrimaryOutput,
      boolean includeTargetLabel,
      boolean includeConfiguration,
      boolean collectTaskHistograms)
      throws IOException;

  /**
   * Disable profiling and complete profile file creation. Subsequent calls to beginTask/endTask
   * will no longer be recorded in the profile.
   */
  void stop() throws IOException;

  /**
   * Clears the records the profiler instance keeps.
   *
   * <p>Should always be called between a {@link #stop()} and a subsequent {@link #start}.
   */
  void clear();

  /**
   * Returns task histograms. This must be called between calls to {@link #start} and {@link #stop},
   * or the returned recorders are all empty. Note that the returned recorders may still be modified
   * concurrently (but at least they are thread-safe, so that's good).
   *
   * <p>The stat recorders are indexed by {@code ProfilerTask#ordinal}. //TODO(b/458037154): Maybe
   * make the enums stable.
   */
  List<StatRecorder> getTasksHistograms();

  /**
   * Returns task histograms. This must be called between calls to {@link #start} and {@link #stop},
   * or the returned list is empty.
   */
  Iterable<SlowTask> getSlowestTasks();

  boolean isProfiling(ProfilerTask type);

  /** Convenience method to log phase marker tasks. */
  void markPhase(ProfilePhase phase) throws InterruptedException;

  /**
   * Similar to {@link #profile}, but specific to action-related events. Takes an extra argument:
   * primaryOutput.
   */
  SilentCloseable profileAction(
      ProfilerTask type,
      String mnemonic,
      String description,
      String primaryOutput,
      String targetLabel,
      String configuration);

  /**
   * Records the end of a task as specified.
   *
   * @param startTimeNanos task start time
   * @param type task type
   * @param description task description
   */
  void completeTask(long startTimeNanos, ProfilerTask type, String description);

  void registerCounterSeriesCollector(CounterSeriesCollector collector);

  void unregisterCounterSeriesCollector(CounterSeriesCollector collector);

  /** Adds a whole action count series to the writer bypassing histogram and subtask creation. */
  void logCounters(
      Map<CounterSeriesTask, double[]> counterSeriesMap,
      Duration profileStart,
      Duration bucketDuration);

  Duration getProfileElapsedTime();

  Duration getServerProcessCpuTime();

  /**
   * Profiles a future.
   *
   * @param future the future to profile
   * @param prefix the prefix of the virtual lanes. Similar to the thread name prefix.
   * @param type task type
   * @param description task description. May be stored until the end of the build.
   */
  @CanIgnoreReturnValue
  <T> ListenableFuture<T> profileFuture(
      ListenableFuture<T> future, String prefix, ProfilerTask type, String description);

  /**
   * Creates a profiler that can be used to profile async operations of a task.
   *
   * @param prefix the prefix of the virtual lanes. Similar to the thread name prefix.
   * @param description the description of task.
   */
  AsyncProfiler profileAsync(String prefix, String description);

  /** Creates a time series with the given start time and bucket duration. */
  TimeSeries createTimeSeries(Duration startTime, Duration bucketDuration);
}
