mozjs_sys 0.67.1

System crate for the Mozilla SpiderMonkey JavaScript engine.
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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/. */

#ifndef vm_Stopwatch_h
#define vm_Stopwatch_h

#include "mozilla/RefPtr.h"
#include "mozilla/Vector.h"

#include "jsapi.h"

/*
  An API for following in real-time the amount of CPU spent executing
  webpages, add-ons, etc.
*/

namespace js {

/**
 * A container for performance groups.
 *
 * Performance monitoring deals with the execution duration of code
 * that belongs to components, for a notion of components defined by
 * the embedding.  Typically, in a web browser, a component may be a
 * webpage and/or a frame and/or a module and/or an add-on and/or a
 * sandbox and/or a process etc.
 *
 * A PerformanceGroupHolder is owned by a JS::Realm and maps that realm
 * to all the components to which it belongs.
 */
struct PerformanceGroupHolder {
  /**
   * Get the groups to which this compartment belongs.
   *
   * Pre-condition: Execution must have entered the compartment.
   *
   * May return `nullptr` if the embedding has not initialized
   * support for performance groups.
   */
  const PerformanceGroupVector* getGroups(JSContext*);

  explicit PerformanceGroupHolder(JSRuntime* runtime)
      : runtime_(runtime), initialized_(false) {}
  ~PerformanceGroupHolder();
  void unlink();

 private:
  JSRuntime* runtime_;

  // `true` once a call to `getGroups` has succeeded.
  bool initialized_;

  // The groups to which this compartment belongs. Filled if and only
  // if `initialized_` is `true`.
  PerformanceGroupVector groups_;
};

/**
 * Container class for everything related to performance monitoring.
 */
struct PerformanceMonitoring {
  /**
   * The number of the current iteration of the event loop.
   */
  uint64_t iteration() { return iteration_; }

  explicit PerformanceMonitoring()
      : totalCPOWTime(0),
        stopwatchStartCallback(nullptr),
        stopwatchStartClosure(nullptr),
        stopwatchCommitCallback(nullptr),
        stopwatchCommitClosure(nullptr),
        getGroupsCallback(nullptr),
        getGroupsClosure(nullptr),
        isMonitoringJank_(false),
        isMonitoringCPOW_(false),
        iteration_(0),
        startedAtIteration_(0),
        highestTimestampCounter_(0) {}

  /**
   * Reset the stopwatch.
   *
   * This method is meant to be called whenever we start
   * processing an event, to ensure that we stop any ongoing
   * measurement that would otherwise provide irrelevant
   * results.
   */
  void reset();

  /**
   * Start the stopwatch.
   *
   * This method is meant to be called once we know that the
   * current event contains JavaScript code to execute. Calling
   * this several times during the same iteration is idempotent.
   */
  void start();

  /**
   * Commit the performance data collected since the last call
   * to `start()`, unless `reset()` has been called since then.
   */
  bool commit();

  /**
   * Liberate memory and references.
   */
  void dispose(JSRuntime* rtx);

  /**
   * Activate/deactivate stopwatch measurement of jank.
   *
   * Noop if `value` is `true` and the stopwatch is already
   * measuring jank, or if `value` is `false` and the stopwatch
   * is not measuring jank.
   *
   * Otherwise, any pending measurements are dropped, but previous
   * measurements remain stored.
   *
   * May return `false` if the underlying hashtable cannot be allocated.
   */
  bool setIsMonitoringJank(bool value) {
    if (isMonitoringJank_ != value) {
      reset();
    }

    isMonitoringJank_ = value;
    return true;
  }
  bool isMonitoringJank() const { return isMonitoringJank_; }

  /**
   * Mark that a group has been used in this iteration.
   */
  bool addRecentGroup(PerformanceGroup* group);

  /**
   * Activate/deactivate stopwatch measurement of CPOW.
   *
   * Noop if `value` is `true` and the stopwatch is already
   * measuring CPOW, or if `value` is `false` and the stopwatch
   * is not measuring CPOW.
   *
   * Otherwise, any pending measurements are dropped, but previous
   * measurements remain stored.
   *
   * May return `false` if the underlying hashtable cannot be allocated.
   */
  bool setIsMonitoringCPOW(bool value) {
    if (isMonitoringCPOW_ != value) {
      reset();
    }

    isMonitoringCPOW_ = value;
    return true;
  }

  bool isMonitoringCPOW() const { return isMonitoringCPOW_; }

  /**
   * Callbacks called when we start executing an event/when we have
   * run to completion (including enqueued microtasks).
   *
   * If there are no nested event loops, each call to
   * `stopwatchStartCallback` is followed by a call to
   * `stopwatchCommitCallback`. However, embedders should not assume
   * that this will always be the case, unless they take measures to
   * prevent nested event loops.
   *
   * In presence of nested event loops, several calls to
   * `stopwatchStartCallback` may occur before a call to
   * `stopwatchCommitCallback`. Embedders should assume that a
   * second call to `stopwatchStartCallback` cancels any measure
   * started by the previous calls to `stopwatchStartCallback` and
   * which have not been committed by `stopwatchCommitCallback`.
   */
  void setStopwatchStartCallback(js::StopwatchStartCallback cb, void* closure) {
    stopwatchStartCallback = cb;
    stopwatchStartClosure = closure;
  }
  void setStopwatchCommitCallback(js::StopwatchCommitCallback cb,
                                  void* closure) {
    stopwatchCommitCallback = cb;
    stopwatchCommitClosure = closure;
  }

  /**
   * Callback called to associate a JS::Realm to the set of
   * `PerformanceGroup`s that represent the components to which
   * it belongs.
   */
  void setGetGroupsCallback(js::GetGroupsCallback cb, void* closure) {
    getGroupsCallback = cb;
    getGroupsClosure = closure;
  }

  /**
   * The total amount of time spent waiting on CPOWs since the
   * start of the process, in microseconds.
   */
  uint64_t totalCPOWTime;

  /**
   * A variant of RDTSC artificially made monotonic.
   *
   * Always return 0 on platforms that do not support RDTSC.
   */
  uint64_t monotonicReadTimestampCounter();

  /**
   * Data extracted by the AutoStopwatch to determine how often
   * we reschedule the process to a different CPU during the
   * execution of JS.
   *
   * Warning: These values are incremented *only* on platforms
   * that offer a syscall/libcall to check on which CPU a
   * process is currently executed.
   */
  struct TestCpuRescheduling {
    // Incremented once we have finished executing code
    // in a group, if the CPU on which we started
    // execution is the same as the CPU on which
    // we finished.
    uint64_t stayed;
    // Incremented once we have finished executing code
    // in a group, if the CPU on which we started
    // execution is different from the CPU on which
    // we finished.
    uint64_t moved;
    TestCpuRescheduling() : stayed(0), moved(0) {}
  };
  TestCpuRescheduling testCpuRescheduling;

 private:
  PerformanceMonitoring(const PerformanceMonitoring&) = delete;
  PerformanceMonitoring& operator=(const PerformanceMonitoring&) = delete;

 private:
  friend struct PerformanceGroupHolder;
  js::StopwatchStartCallback stopwatchStartCallback;
  void* stopwatchStartClosure;
  js::StopwatchCommitCallback stopwatchCommitCallback;
  void* stopwatchCommitClosure;

  js::GetGroupsCallback getGroupsCallback;
  void* getGroupsClosure;

  /**
   * `true` if stopwatch monitoring is active for Jank, `false` otherwise.
   */
  bool isMonitoringJank_;
  /**
   * `true` if stopwatch monitoring is active for CPOW, `false` otherwise.
   */
  bool isMonitoringCPOW_;

  /**
   * The number of times we have entered the event loop.
   * Used to reset counters whenever we enter the loop,
   * which may be caused either by having completed the
   * previous run of the event loop, or by entering a
   * nested loop.
   *
   * Always incremented by 1, may safely overflow.
   */
  uint64_t iteration_;

  /**
   * The iteration at which the stopwatch was last started.
   *
   * Used both to avoid starting the stopwatch several times
   * during the same event loop and to avoid committing stale
   * stopwatch results.
   */
  uint64_t startedAtIteration_;

  /**
   * Groups used in the current iteration.
   */
  PerformanceGroupVector recentGroups_;

  /**
   * The highest value of the timestamp counter encountered
   * during this iteration.
   */
  uint64_t highestTimestampCounter_;
};

// Temporary disable untested code path.
#if 0  // WINVER >= 0x0600
struct cpuid_t {
    uint16_t group_;
    uint8_t number_;
    cpuid_t(uint16_t group, uint8_t number)
        : group_(group),
          number_(number)
    { }
    cpuid_t()
        : group_(0),
          number_(0)
    { }
};
#else
typedef struct {
} cpuid_t;
#endif  // defined(WINVER >= 0x0600)

/**
 * RAII class to start/stop measuring performance when
 * entering/leaving a compartment.
 */
class AutoStopwatch final {
  // The context with which this object was initialized.
  // Non-null.
  JSContext* const cx_;

  // An indication of the number of times we have entered the event
  // loop.  Used only for comparison.
  uint64_t iteration_;

  // `true` if we are monitoring jank, `false` otherwise.
  bool isMonitoringJank_;
  // `true` if we are monitoring CPOW, `false` otherwise.
  bool isMonitoringCPOW_;

  // Timestamps captured while starting the stopwatch.
  uint64_t cyclesStart_;
  uint64_t CPOWTimeStart_;

  // The CPU on which we started the measure. Defined only
  // if `isMonitoringJank_` is `true`.
  cpuid_t cpuStart_;

  PerformanceGroupVector groups_;

 public:
  // If the stopwatch is active, constructing an instance of
  // AutoStopwatch causes it to become the current owner of the
  // stopwatch.
  //
  // Previous owner is restored upon destruction.
  explicit AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
  ~AutoStopwatch();

 private:
  void inline enter();

  bool inline exit();

  // Attempt to acquire a group
  // If the group is inactive or if the group already has a stopwatch,
  // do nothing and return `null`.
  // Otherwise, bind the group to `this` for the current iteration
  // and return `group`.
  PerformanceGroup* acquireGroup(PerformanceGroup* group);

  // Release a group. Noop if `this` is not the stopwatch of
  // `group` for the current iteration.
  void releaseGroup(PerformanceGroup* group);

  // Add recent changes to all the groups owned by this stopwatch.
  // Mark the groups as changed recently.
  bool addToGroups(uint64_t cyclesDelta, uint64_t CPOWTimeDelta);

  // Add recent changes to a single group. Mark the group as changed recently.
  bool addToGroup(JSRuntime* runtime, uint64_t cyclesDelta,
                  uint64_t CPOWTimeDelta, PerformanceGroup* group);

  // Update telemetry statistics.
  void updateTelemetry(const cpuid_t& a, const cpuid_t& b);

  // Perform a subtraction for a quantity that should be monotonic
  // but is not guaranteed to be so.
  //
  // If `start <= end`, return `end - start`.
  // Otherwise, return `0`.
  uint64_t inline getDelta(const uint64_t end, const uint64_t start) const;

  // Return the value of the Timestamp Counter, as provided by the CPU.
  // 0 on platforms for which we do not have access to a Timestamp Counter.
  uint64_t inline getCycles(JSRuntime*) const;

  // Return the identifier of the current CPU, on platforms for which we have
  // access to the current CPU.
  cpuid_t inline getCPU() const;

  // Compare two CPU identifiers.
  bool inline isSameCPU(const cpuid_t& a, const cpuid_t& b) const;

 private:
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
};

}  // namespace js

#endif  // vm_Stopwatch_h