/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// The Gecko Profiler is an always-on profiler that takes fast and low overhead
// samples of the program execution using only userspace functionality for
// portability. The goal of this module is to provide performance data in a
// generic cross-platform way without requiring custom tools or kernel support.
//
// Samples are collected to form a timeline with optional timeline event
// (markers) used for filtering. The samples include both native stacks and
// platform-independent "label stack" frames.

#ifndef GeckoProfiler_h
#define GeckoProfiler_h

// If your file only uses particular APIs (e.g., only markers), please consider
// including only the needed headers instead of this one, to reduce compilation
// dependencies.
#include "ProfileAdditionalInformation.h"
#include "mozilla/BaseProfiler.h"
#include "mozilla/ProfilerCounts.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/ProfilerState.h"
#include "mozilla/ProfilerThreadSleep.h"
#include "mozilla/ProfilerThreadState.h"
#include "mozilla/ProfilerUtils.h"
#include "mozilla/ProgressLogger.h"

#include "js/ProfilingStack.h"
#include "mozilla/Attributes.h"
#include "mozilla/BaseProfilerRAIIMacro.h"
#include "mozilla/Maybe.h"
#include "mozilla/PowerOfTwo.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "nscore.h"
#include "nsINamed.h"
#include "nsString.h"
#include "nsThreadUtils.h"

#include <functional>
#include <stdint.h>

class ProfilerBacktrace;
class ProfilerCodeAddressService;
struct JSContext;

namespace mozilla {
class ProfileBufferControlledChunkManager;
class ProfileChunkedBuffer;
namespace baseprofiler {
class SpliceableJSONWriter;
}  // namespace baseprofiler
}  // namespace mozilla
class nsIURI;

//---------------------------------------------------------------------------
// Give information to the profiler
//---------------------------------------------------------------------------

// Register/unregister threads with the profiler. Both functions operate the
// same whether the profiler is active or inactive.
#define PROFILER_REGISTER_THREAD(name)         \
  do {                                         \
    char stackTop;                             \
    profiler_register_thread(name, &stackTop); \
  } while (0)
#define PROFILER_UNREGISTER_THREAD() profiler_unregister_thread()
ProfilingStack* profiler_register_thread(const char* name, void* guessStackTop);
void profiler_unregister_thread();

// Registers a DOM Window (the JS global `window`) with the profiler. Each
// Window _roughly_ corresponds to a single document loaded within a
// browsing context. Both the Window Id and Browser Id are recorded to allow
// correlating different Windows loaded within the same tab or frame element.
//
// We register pages for each navigations but we do not register
// history.pushState or history.replaceState since they correspond to the same
// Inner Window ID. When a browsing context is first loaded, the first url
// loaded in it will be about:blank. Because of that, this call keeps the first
// non-about:blank registration of window and discards the previous one.
//
//   "aTabID"                 is the BrowserId of that document belongs to.
//                            That's used to determine the tab of that page.
//   "aInnerWindowID"         is the ID of the `window` global object of that
//                            document.
//   "aUrl"                   is the URL of the page.
//   "aEmbedderInnerWindowID" is the inner window id of embedder. It's used to
//                            determine sub documents of a page.
//   "aIsPrivateBrowsing"     is true if this browsing context happens in a
//                            private browsing context.
void profiler_register_page(uint64_t aTabID, uint64_t aInnerWindowID,
                            const nsCString& aUrl,
                            uint64_t aEmbedderInnerWindowID,
                            bool aIsPrivateBrowsing);
// Unregister page with the profiler.
//
// Take a Inner Window ID and unregister the page entry that has the same ID.
void profiler_unregister_page(uint64_t aRegisteredInnerWindowID);

// Remove all registered and unregistered pages in the profiler.
void profiler_clear_all_pages();

class BaseProfilerCount;
void profiler_add_sampled_counter(BaseProfilerCount* aCounter);
void profiler_remove_sampled_counter(BaseProfilerCount* aCounter);

// Register and unregister a thread within a scope.
#define AUTO_PROFILER_REGISTER_THREAD(name) \
  mozilla::AutoProfilerRegisterThread PROFILER_RAII(name)

enum class SamplingState {
  JustStopped,  // Sampling loop has just stopped without sampling, between the
                // callback registration and now.
  SamplingPaused,    // Profiler is active but sampling loop has gone through a
                     // pause.
  SamplingCompleted  // A full sampling loop has completed.
};

using PostSamplingCallback = std::function<void(SamplingState)>;

// Install a callback to be invoked at the end of the next sampling loop.
// - `false` if profiler is not active, `aCallback` will stay untouched.
// - `true` if `aCallback` was successfully moved-from into internal storage,
//   and *will* be invoked at the end of the next sampling cycle. Note that this
//   will happen on the Sampler thread, and will block further sampling, so
//   please be mindful not to block for a long time (e.g., just dispatch a
//   runnable to another thread.) Calling profiler functions from the callback
//   is allowed.
[[nodiscard]] bool profiler_callback_after_sampling(
    PostSamplingCallback&& aCallback);

// Called by the JSRuntime's operation callback. This is used to start profiling
// on auxiliary threads. Operates the same whether the profiler is active or
// not.
#define PROFILER_JS_INTERRUPT_CALLBACK() profiler_js_interrupt_callback()
void profiler_js_interrupt_callback();

// Set and clear the current thread's JSContext.
#define PROFILER_SET_JS_CONTEXT(cx) profiler_set_js_context(cx)
#define PROFILER_CLEAR_JS_CONTEXT() profiler_clear_js_context()
void profiler_set_js_context(mozilla::CycleCollectedJSContext* aCx);
void profiler_clear_js_context();

//---------------------------------------------------------------------------
// Get information from the profiler
//---------------------------------------------------------------------------

// Get the chunk manager used in the current profiling session, or null.
mozilla::ProfileBufferControlledChunkManager*
profiler_get_controlled_chunk_manager();

// The number of milliseconds since the process started. Operates the same
// whether the profiler is active or inactive.
double profiler_time();

// An object of this class is passed to profiler_suspend_and_sample_thread().
// For each stack frame, one of the Collect methods will be called.
class ProfilerStackCollector {
 public:
  // Some collectors need to worry about possibly overwriting previous
  // generations of data. If that's not an issue, this can return Nothing,
  // which is the default behaviour.
  virtual mozilla::Maybe<uint64_t> SamplePositionInBuffer() {
    return mozilla::Nothing();
  }
  virtual mozilla::Maybe<uint64_t> BufferRangeStart() {
    return mozilla::Nothing();
  }

  // This method will be called once if the thread being suspended is the main
  // thread. Default behaviour is to do nothing.
  virtual void SetIsMainThread() {}

  // WARNING: The target thread is suspended when the Collect methods are
  // called. Do not try to allocate or acquire any locks, or you could
  // deadlock. The target thread will have resumed by the time this function
  // returns.

  virtual void CollectNativeLeafAddr(void* aAddr) = 0;

  virtual void CollectJitReturnAddr(void* aAddr) = 0;

  virtual void CollectWasmOrSyncJITFrame(JS::ProfilingCategoryPair aCategory,
                                         const char* aLabel,
                                         uint32_t aSourceId) = 0;

  virtual void CollectProfilingStackFrame(
      const js::ProfilingStackFrame& aFrame) = 0;
};

// This method suspends the thread identified by aThreadId, samples its
// profiling stack, JS stack, and (optionally) native stack, passing the
// collected frames into aCollector. aFeatures dictates which compiler features
// are used. |Leaf| is the only relevant one.
// Use `ProfilerThreadId{}` (unspecified) to sample the current thread.
void profiler_suspend_and_sample_thread(ProfilerThreadId aThreadId,
                                        uint32_t aFeatures,
                                        ProfilerStackCollector& aCollector,
                                        bool aSampleNative = true);

struct ProfilerBacktraceDestructor {
  void operator()(ProfilerBacktrace*);
};

using UniqueProfilerBacktrace =
    mozilla::UniquePtr<ProfilerBacktrace, ProfilerBacktraceDestructor>;

// Immediately capture the current thread's call stack, store it in the provided
// buffer (usually to avoid allocations if you can construct the buffer on the
// stack). Returns false if unsuccessful, or if the profiler is inactive.
bool profiler_capture_backtrace_into(
    mozilla::ProfileChunkedBuffer& aChunkedBuffer,
    mozilla::StackCaptureOptions aCaptureOptions);

// Immediately capture the current thread's call stack, and return it in a
// ProfileChunkedBuffer (usually for later use in MarkerStack::TakeBacktrace()).
// May be null if unsuccessful, or if the profiler is inactive.
mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> profiler_capture_backtrace();

// Immediately capture the current thread's call stack, and return it in a
// ProfilerBacktrace (usually for later use in marker function that take a
// ProfilerBacktrace). May be null if unsuccessful, or if the profiler is
// inactive.
UniqueProfilerBacktrace profiler_get_backtrace();

struct ProfilerStats {
  unsigned n = 0;
  double sum = 0;
  double min = std::numeric_limits<double>::max();
  double max = 0;
  void Count(double v) {
    ++n;
    sum += v;
    if (v < min) {
      min = v;
    }
    if (v > max) {
      max = v;
    }
  }
};

struct ProfilerBufferInfo {
  // Index of the oldest entry.
  uint64_t mRangeStart;
  // Index of the newest entry.
  uint64_t mRangeEnd;
  // Buffer capacity in number of 8-byte entries.
  uint32_t mEntryCount;
  // Sampling stats: Interval between successive samplings.
  ProfilerStats mIntervalsUs;
  // Sampling stats: Total sampling duration. (Split detail below.)
  ProfilerStats mOverheadsUs;
  // Sampling stats: Time to acquire the lock before sampling.
  ProfilerStats mLockingsUs;
  // Sampling stats: Time to discard expired data.
  ProfilerStats mCleaningsUs;
  // Sampling stats: Time to collect counter data.
  ProfilerStats mCountersUs;
  // Sampling stats: Time to sample thread stacks.
  ProfilerStats mThreadsUs;
};

// Get information about the current buffer status.
// Returns Nothing() if the profiler is inactive.
//
// This information may be useful to a user-interface displaying the current
// status of the profiler, allowing the user to get a sense for how fast the
// buffer is being written to, and how much data is visible.
mozilla::Maybe<ProfilerBufferInfo> profiler_get_buffer_info();

// Record through glean how many times profiler_thread_wake has been
// called.
void profiler_record_wakeup_count(const nsACString& aProcessType);

//---------------------------------------------------------------------------
// Output profiles
//---------------------------------------------------------------------------

// Set a user-friendly process name, used in JSON stream.  Allows an optional
// detailed name which may include private info (eTLD+1 in fission)
void profiler_set_process_name(const nsACString& aProcessName,
                               const nsACString* aETLDplus1 = nullptr);

// Record an exit profile from a child process. The struct carries the profile
// JSON along with optional additional information (shared libraries, JS
// sources) that was gathered alongside it in the child process.
void profiler_received_exit_profile(
    mozilla::ProfileAndAdditionalInformation&& aExitProfileAndAdditionalInfo);

// Get the profile encoded as a JSON string. A no-op (returning nullptr) if the
// profiler is inactive.
// If aIsShuttingDown is true, the current time is included as the process
// shutdown time in the JSON's "meta" object.
mozilla::UniquePtr<char[]> profiler_get_profile(double aSinceTime = 0,
                                                bool aIsShuttingDown = false);

// Write the profile for this process (excluding subprocesses) into aWriter.
// Returns a failed result if the profiler is inactive.
ProfilerResult<mozilla::ProfileGenerationAdditionalInformation>
profiler_stream_json_for_this_process(
    mozilla::baseprofiler::SpliceableJSONWriter& aWriter, double aSinceTime = 0,
    bool aIsShuttingDown = false,
    ProfilerCodeAddressService* aService = nullptr,
    mozilla::ProgressLogger aProgressLogger = {});

// Get the profile and write it into a file. A no-op if the profile is
// inactive.
//
// This function is 'extern "C"' so that it is easily callable from a debugger
// in a build without debugging information (a workaround for
// http://llvm.org/bugs/show_bug.cgi?id=22211).
extern "C" {
void profiler_save_profile_to_file(const char* aFilename);
}

// Schedule a one-shot profile dump that the sampler thread performs off the
// main thread once aDelaySeconds have elapsed, writing this process's profile
// to aFilename (a no-op if the profiler stops first).
//
// Because it runs on the sampler thread rather than a main-thread timer, it
// still fires while the main thread is blocked in a long synchronous run (or a
// blocking native call) and can't write a profile itself; the sampled
// main-thread stack shows where the time went. The caller that set the deadline
// is responsible for reporting the written file, and should call
// profiler_cancel_scheduled_dump() if it no longer wants the dump.
//
// If aExitAfterDump is true, the process exits as soon as the profile has been
// written.
void profiler_schedule_dump_to_file(double aDelaySeconds, const char* aFilename,
                                    bool aExitAfterDump);
void profiler_cancel_scheduled_dump();

// Block until an in-progress scheduled dump (see
// profiler_schedule_dump_to_file) finishes; a no-op if none is in progress, and
// always a no-op outside automation. Lets a caller on a fatal-abort path avoid
// acting over a half-written profile; when the dump was scheduled with
// aExitAfterDump the process exits on completion, so the caller never returns.
// It can block for as long as the dump takes, hence the automation-only
// restriction.
void profiler_wait_for_scheduled_dump();

//---------------------------------------------------------------------------
// RAII classes
//---------------------------------------------------------------------------

namespace mozilla {

// Convenience class to register and unregister a thread with the profiler.
// Needs to be the first object on the stack of the thread.
class MOZ_RAII AutoProfilerRegisterThread final {
 public:
  explicit AutoProfilerRegisterThread(const char* aName) {
    profiler_register_thread(aName, this);
  }

  ~AutoProfilerRegisterThread() { profiler_unregister_thread(); }

 private:
  AutoProfilerRegisterThread(const AutoProfilerRegisterThread&) = delete;
  AutoProfilerRegisterThread& operator=(const AutoProfilerRegisterThread&) =
      delete;
};

// Get the MOZ_PROFILER_STARTUP* environment variables that should be
// supplied to a child process that is about to be launched, in order
// to make that child process start with the same profiler settings as
// in the current process.  The given function is invoked once for
// each variable to be set.
void GetProfilerEnvVarsForChildProcess(
    std::function<void(const char* key, const char* value)>&& aSetEnv);

}  // namespace mozilla

#endif  // GeckoProfiler_h
