@ 261105.16
current
#ifndef SRC_HISTOGRAM_H_
#define SRC_HISTOGRAM_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <hdr/hdr_histogram.h>
#include "base_object.h"
#include "memory_tracker.h"
#include "node_messaging.h"
#include "util.h"
#include "uv.h"
#include "v8.h"

#include <limits>
#include <vector>

namespace node {

class ExternalReferenceRegistry;

template <typename T>
void StartHandleHistogram(v8::Local<v8::Value> receiver, bool reset);
template <typename T>
void StopHandleHistogram(v8::Local<v8::Value> receiver);

constexpr int kDefaultHistogramFigures = 3;

class Histogram : public MemoryRetainer {
 public:
  struct Options {
    int64_t lowest = 1;
    int64_t highest = std::numeric_limits<int64_t>::max();
    int figures = kDefaultHistogramFigures;
    double half_life = 0;   // EWMA half-life in number of samples (0 = off)
    int64_t threshold = 0;  // SLO threshold (0 = off). When set with
                            // half_life, tracks EWMA error rate for values
                            // exceeding this threshold.
  };

  struct RecordedBucket {
    double value;
    double resolution;
    int64_t count;
    int64_t cumulative_count;
  };

  struct RecordedSnapshot {
    std::vector<RecordedBucket> buckets;
    int64_t total_count = 0;
  };

  using HistogramPointer = DeleteFnPtr<hdr_histogram, hdr_close>;

  struct RecordedSnapshotSource {
    HistogramPointer histogram;
    std::shared_ptr<const RecordedSnapshot> snapshot;
    uint64_t generation = 0;
    bool cache_hit = false;
  };

  // Factory method that returns nullptr on hdr_init failure.
  static std::shared_ptr<Histogram> Create(const Options& options);

  // Returns an independent copy of this histogram's current state, or nullptr
  // if the copy cannot be allocated.
  std::shared_ptr<Histogram> Clone() const;

  enum class DiffError {
    kNone,
    kOutOfMemory,
    // `other` has a different layout.
    kIncompatible,
    // Values were removed from this histogram after `other` was taken.
    kReset,
    // `other` contains values that this histogram does not.
    kNotEarlier,
  };

  // Returns a new histogram containing the values recorded in this histogram
  // after `other`, an earlier copy of it, was taken. Returns nullptr and sets
  // `error` if the difference cannot be computed.
  std::shared_ptr<Histogram> Diff(const Histogram& other,
                                  DiffError* error) const;

  Histogram(HistogramPointer histogram, const Options& options);
  virtual ~Histogram() = default;

  inline bool Record(int64_t value);
  inline void Reset();
  inline int64_t Min() const;
  inline int64_t Max() const;
  inline double Mean() const;
  inline double Stddev() const;
  inline double EwmaMean() const;
  inline double EwmaStddev() const;
  inline double EwmaErrorRate() const;
  inline int64_t Percentile(double percentile) const;
  inline size_t Exceeds() const;
  inline size_t Count() const;
  inline uint64_t ResetCount() const;

  inline uint64_t RecordDelta();

  inline double Add(const Histogram& other);

  // Iterator is a function type that takes two doubles as argument, one for
  // percentile and one for the value at that percentile.
  template <typename Iterator>
  inline void Percentiles(Iterator&& fn) const;

  inline size_t GetMemorySize() const;

  // Analysis methods
  inline int64_t CountAt(int64_t value) const;
  double Cdf(int64_t value) const;
  double Skewness() const;
  double Kurtosis() const;
  double KsTest(const Histogram& other) const;
  double Subtract(const Histogram& other);
  void PercentilesAt(const double* percentiles,
                     int64_t* values,
                     size_t length) const;

  // Statistical analysis
  struct MeanCIResult {
    double mean;
    double lower;
    double upper;
  };

  struct WelchTestResult {
    double t_statistic;
    double degrees_of_freedom;
    double p_value;
    double ci_lower;
    double ci_upper;
  };

  struct MannWhitneyResult {
    double u_statistic;
    double z_score;
    double p_value;
  };

  struct PercentileCIResult {
    int64_t value;
    int64_t lower;
    int64_t upper;
  };

  MeanCIResult MeanCI(double confidence = 0.95) const;
  WelchTestResult WelchTest(const Histogram& other,
                            double confidence = 0.95) const;
  MannWhitneyResult MannWhitneyTest(const Histogram& other) const;
  double CohensD(const Histogram& other) const;
  double CliffsD(const Histogram& other) const;
  PercentileCIResult PercentileCI(double percentile,
                                  double confidence = 0.95) const;

  // CBOR-encoded export/import for histogram exchange.
  std::vector<uint8_t> Export() const;
  static std::shared_ptr<Histogram> Import(const uint8_t* data, size_t len);

  inline bool RecordCorrected(int64_t value, int64_t expected_interval);

  template <typename Iterator>
  void LinearBuckets(int64_t step_size, Iterator&& fn) const;

  template <typename Iterator>
  void LogBuckets(int64_t first_bucket, double log_base, Iterator&& fn) const;

  bool IsCompatible(const Histogram& other) const;
  RecordedSnapshotSource GetRecordedSnapshotSource(bool use_cache) const;
  static RecordedSnapshot BuildRecordedSnapshot(const hdr_histogram* histogram);
  void CacheRecordedSnapshot(uint64_t generation,
                             std::shared_ptr<const RecordedSnapshot> snapshot);

  void MemoryInfo(MemoryTracker* tracker) const override;
  SET_MEMORY_INFO_NAME(Histogram)
  SET_SELF_SIZE(Histogram)

 private:
  inline void UpdateEwma(double value);
  inline void InvalidateRecordedSnapshot();
  size_t GetCachedRecordedSnapshotMemorySize() const;
  std::shared_ptr<Histogram> CreateWithSameLayout() const;

  HistogramPointer histogram_;
  uint64_t prev_ = 0;
  size_t exceeds_ = 0;
  // Incremented whenever recorded values are removed by Reset() or Subtract().
  uint64_t reset_count_ = 0;

  // EWMA state (active when ewma_alpha_ > 0)
  double ewma_alpha_ = 0;
  double ewma_mean_ = 0;
  double ewma_variance_ = 0;
  bool ewma_initialized_ = false;

  // SLO error rate EWMA (active when threshold_ > 0 and ewma_alpha_ > 0)
  int64_t threshold_ = 0;
  double ewma_error_rate_ = 0;

  uint64_t mutation_generation_ = 0;
  std::shared_ptr<const RecordedSnapshot> recorded_snapshot_cache_;

  RwLock mutex_;
};

class HistogramImpl {
 public:
  enum InternalFields {
    kSlot = BaseObject::kSlot,
    kImplField = HandleWrap::kInternalFieldCount,
    kInternalFieldCount
  };

  explicit HistogramImpl(
      const Histogram::Options& options = Histogram::Options {});
  explicit HistogramImpl(std::shared_ptr<Histogram> histogram);

  Histogram* operator->() { return histogram_.get(); }

  const std::shared_ptr<Histogram>& histogram() const { return histogram_; }

  static void DoReset(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetCountBigInt(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetMinBigInt(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetMaxBigInt(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetExceedsBigInt(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetCount(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetMin(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetMax(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetMean(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetExceeds(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetStddev(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetPercentile(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetPercentileBigInt(
      const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetPercentiles(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetPercentilesBigInt(
      const v8::FunctionCallbackInfo<v8::Value>& args);

  static void GetSkewness(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetKurtosis(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetCdf(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetCountAt(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetKsTest(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetPercentilesAt(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetLinearBuckets(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetLogBuckets(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetMeanCI(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetWelchTest(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetMannWhitneyTest(
      const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetCohensD(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetCliffsD(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetPercentileCI(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetQrde(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetEwmaMean(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetEwmaStddev(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetEwmaErrorRate(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void DoExport(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void DoImport(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void DoSnapshot(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void DoDiff(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void GetResetCount(const v8::FunctionCallbackInfo<v8::Value>& args);

  static void FastReset(v8::Local<v8::Value> receiver);
  static double FastGetCount(v8::Local<v8::Value> receiver);
  static double FastGetMin(v8::Local<v8::Value> receiver);
  static double FastGetMax(v8::Local<v8::Value> receiver);
  static double FastGetMean(v8::Local<v8::Value> receiver);
  static double FastGetExceeds(v8::Local<v8::Value> receiver);
  static double FastGetStddev(v8::Local<v8::Value> receiver);
  static double FastGetPercentile(v8::Local<v8::Value> receiver,
                                  const double percentile);
  static double FastGetSkewness(v8::Local<v8::Value> receiver);
  static double FastGetKurtosis(v8::Local<v8::Value> receiver);
  static double FastGetCdf(v8::Local<v8::Value> receiver, const int64_t value);
  static double FastGetCountAt(v8::Local<v8::Value> receiver,
                               const int64_t value);
  static double FastGetEwmaMean(v8::Local<v8::Value> receiver);
  static double FastGetEwmaStddev(v8::Local<v8::Value> receiver);
  static double FastGetEwmaErrorRate(v8::Local<v8::Value> receiver);

  static void AddMethods(v8::Isolate* isolate,
                         v8::Local<v8::FunctionTemplate> tmpl);

  static void RegisterExternalReferences(ExternalReferenceRegistry* registry);

  static HistogramImpl* FromJSObject(v8::Local<v8::Value> value);

 private:
  std::shared_ptr<Histogram> histogram_;

  static v8::CFunction fast_reset_;
  static v8::CFunction fast_get_count_;
  static v8::CFunction fast_get_min_;
  static v8::CFunction fast_get_max_;
  static v8::CFunction fast_get_mean_;
  static v8::CFunction fast_get_exceeds_;
  static v8::CFunction fast_get_stddev_;
  static v8::CFunction fast_get_percentile_;
  static v8::CFunction fast_get_skewness_;
  static v8::CFunction fast_get_kurtosis_;
  static v8::CFunction fast_get_cdf_;
  static v8::CFunction fast_get_count_at_;
  static v8::CFunction fast_get_ewma_mean_;
  static v8::CFunction fast_get_ewma_stddev_;
  static v8::CFunction fast_get_ewma_error_rate_;
};

class HistogramBase final : public BaseObject, public HistogramImpl {
 public:
  enum InternalFields {
    kInternalFieldCount = std::max<uint32_t>(
        BaseObject::kInternalFieldCount, HistogramImpl::kInternalFieldCount),
  };

  static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
      IsolateData* isolate_data);
  static void Initialize(IsolateData* isolate_data,
                         v8::Local<v8::ObjectTemplate> target);
  static void RegisterExternalReferences(ExternalReferenceRegistry* registry);

  static BaseObjectPtr<HistogramBase> Create(
      Environment* env,
      const Histogram::Options& options = Histogram::Options {});

  static BaseObjectPtr<HistogramBase> Create(
      Environment* env,
      std::shared_ptr<Histogram> histogram);

  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);

  void MemoryInfo(MemoryTracker* tracker) const override;
  SET_MEMORY_INFO_NAME(HistogramBase)
  SET_SELF_SIZE(HistogramBase)

  static void Record(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void RecordDelta(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void RecordCorrected(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void Add(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void Subtract(const v8::FunctionCallbackInfo<v8::Value>& args);

  static void FastRecord(v8::Local<v8::Value> receiver, const int64_t value);
  static void FastRecordDelta(v8::Local<v8::Value> receiver);

  HistogramBase(
      Environment* env,
      v8::Local<v8::Object> wrap,
      const Histogram::Options& options = Histogram::Options {});

  HistogramBase(
      Environment* env,
      v8::Local<v8::Object> wrap,
      std::shared_ptr<Histogram> histogram);

  ~HistogramBase() override;

  BaseObject::TransferMode GetTransferMode() const override {
    return TransferMode::kCloneable;
  }
  std::unique_ptr<worker::TransferData> CloneForMessaging() const override;

  class HistogramTransferData : public worker::TransferData {
   public:
    explicit HistogramTransferData(const HistogramBase* histogram)
        : histogram_(histogram->histogram()) {}

    explicit HistogramTransferData(std::shared_ptr<Histogram> histogram)
        : histogram_(std::move(histogram)) {}

    BaseObjectPtr<BaseObject> Deserialize(
        Environment* env,
        v8::Local<v8::Context> context,
        std::unique_ptr<worker::TransferData> self) override;

    void MemoryInfo(MemoryTracker* tracker) const override;
    SET_MEMORY_INFO_NAME(HistogramTransferData)
    SET_SELF_SIZE(HistogramTransferData)

   private:
    std::shared_ptr<Histogram> histogram_;
  };

 private:
  void ReportExternalMemory();

  // The native memory reported to V8 while this object is alive.
  size_t external_memory_ = 0;

  static v8::CFunction fast_record_;
  static v8::CFunction fast_record_delta_;
};

// BaseObject disallows cloning and transfer, so ring state is confined to the
// owning Environment's thread.
class SlidingWindowHistogram final : public BaseObject {
 public:
  static void Initialize(IsolateData* isolate_data,
                         v8::Local<v8::ObjectTemplate> target);
  static void RegisterExternalReferences(ExternalReferenceRegistry* registry);

  void MemoryInfo(MemoryTracker* tracker) const override;
  SET_MEMORY_INFO_NAME(SlidingWindowHistogram)
  SET_SELF_SIZE(SlidingWindowHistogram)

 private:
  static constexpr uint64_t kNoGeneration =
      std::numeric_limits<uint64_t>::max();

  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void Record(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void FastRecord(v8::Local<v8::Value> receiver,
                         int64_t value,
                         v8::FastApiCallbackOptions& options);
  static void Snapshot(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void Reset(const v8::FunctionCallbackInfo<v8::Value>& args);

  SlidingWindowHistogram(Environment* env,
                         v8::Local<v8::Object> wrap,
                         const Histogram::Options& options,
                         size_t chunk_count,
                         bool time_based,
                         uint64_t rotate_at,
                         std::shared_ptr<Histogram> spare);
  ~SlidingWindowHistogram() override;

  Histogram* GetChunk(uint64_t generation);
  bool RecordValue(int64_t value);
  std::shared_ptr<Histogram> CreateSnapshot() const;
  void ResetWindow();
  uint64_t CurrentTimeGeneration() const;

  Histogram::Options options_;
  std::vector<std::shared_ptr<Histogram>> chunks_;
  std::vector<uint64_t> generations_;
  std::shared_ptr<Histogram> spare_;
  bool time_based_;
  uint64_t rotate_at_;
  uint64_t origin_;
  uint64_t current_generation_ = 0;
  uint64_t records_in_current_chunk_ = 0;
  size_t external_memory_ = 0;
  bool has_count_records_ = false;

  static v8::CFunction fast_record_;
};

// CRTP mixin for HandleWrap-based histograms with start/stop support.
// Provides: StartFlags enum, Start/Stop slow-path handlers, enabled_ flag,
// and InitTemplate (shared GetConstructorTemplate body).
// Derived must provide: fast_start_, fast_stop_ (static CFunction),
//   FastStart, FastStop, OnStart, OnStop.
template <typename Derived>
class HandleHistogramMixin {
 public:
  enum class StartFlags { NONE, RESET };

  static void Start(const v8::FunctionCallbackInfo<v8::Value>& args) {
    StartHandleHistogram<Derived>(args.This(), args[0]->IsTrue());
  }

  static void Stop(const v8::FunctionCallbackInfo<v8::Value>& args) {
    StopHandleHistogram<Derived>(args.This());
  }

 protected:
  static void InitTemplate(v8::Isolate* isolate,
                           v8::Local<v8::FunctionTemplate> tmpl,
                           uint32_t internal_field_count) {
    auto instance = tmpl->InstanceTemplate();
    instance->SetInternalFieldCount(internal_field_count);
    HistogramImpl::AddMethods(isolate, tmpl);
    SetFastMethod(isolate, instance, "start", Start, &Derived::fast_start_);
    SetFastMethod(isolate, instance, "stop", Stop, &Derived::fast_stop_);
  }

  bool enabled_ = false;
};

class IntervalHistogram final : public HandleWrap,
                                public HistogramImpl,
                                public HandleHistogramMixin<IntervalHistogram> {
 public:
  enum InternalFields {
    kInternalFieldCount = std::max<uint32_t>(
        HandleWrap::kInternalFieldCount, HistogramImpl::kInternalFieldCount),
  };

  using OnInterval = void (*)(Histogram&);

  static void RegisterExternalReferences(ExternalReferenceRegistry* registry);

  static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
      Environment* env);

  static BaseObjectPtr<IntervalHistogram> Create(
      Environment* env,
      uint64_t interval,
      OnInterval on_interval,
      std::shared_ptr<Histogram> histogram,
      AsyncWrap::ProviderType type = AsyncWrap::PROVIDER_ELDHISTOGRAM);

  IntervalHistogram(Environment* env,
                    v8::Local<v8::Object> wrap,
                    AsyncWrap::ProviderType type,
                    uint64_t interval,
                    OnInterval on_interval,
                    std::shared_ptr<Histogram> histogram);

  static void FastStart(v8::Local<v8::Value> receiver, bool reset);
  static void FastStop(v8::Local<v8::Value> receiver);

  BaseObject::TransferMode GetTransferMode() const override {
    return TransferMode::kCloneable;
  }
  std::unique_ptr<worker::TransferData> CloneForMessaging() const override;

  void MemoryInfo(MemoryTracker* tracker) const override;
  SET_MEMORY_INFO_NAME(IntervalHistogram)
  SET_SELF_SIZE(IntervalHistogram)

 private:
  static void TimerCB(uv_timer_t* handle);
  void OnStart(StartFlags flags = StartFlags::RESET);
  void OnStop();

  friend class HandleHistogramMixin<IntervalHistogram>;
  template <typename T>
  friend void StartHandleHistogram(v8::Local<v8::Value>, bool);
  template <typename T>
  friend void StopHandleHistogram(v8::Local<v8::Value>);

  uint64_t interval_ = 0;
  OnInterval on_interval_ = nullptr;
  uv_timer_t timer_;

  static v8::CFunction fast_start_;
  static v8::CFunction fast_stop_;
};

class IterationHistogram final
    : public HandleWrap,
      public HistogramImpl,
      public HandleHistogramMixin<IterationHistogram> {
 public:
  enum InternalFields {
    kInternalFieldCount = std::max<uint32_t>(
        HandleWrap::kInternalFieldCount, HistogramImpl::kInternalFieldCount),
  };

  static void RegisterExternalReferences(ExternalReferenceRegistry* registry);

  static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
      Environment* env);

  static BaseObjectPtr<IterationHistogram> Create(
      Environment* env,
      std::shared_ptr<Histogram> histogram,
      AsyncWrap::ProviderType type = AsyncWrap::PROVIDER_ELDHISTOGRAM);

  IterationHistogram(Environment* env,
                     v8::Local<v8::Object> wrap,
                     AsyncWrap::ProviderType type,
                     std::shared_ptr<Histogram> histogram);

  static void FastStart(v8::Local<v8::Value> receiver, bool reset);
  static void FastStop(v8::Local<v8::Value> receiver);

  BaseObject::TransferMode GetTransferMode() const override {
    return TransferMode::kCloneable;
  }
  std::unique_ptr<worker::TransferData> CloneForMessaging() const override;

  void Close(
      v8::Local<v8::Value> close_callback = v8::Local<v8::Value>()) override;

  void MemoryInfo(MemoryTracker* tracker) const override;
  SET_MEMORY_INFO_NAME(IterationHistogram)
  SET_SELF_SIZE(IterationHistogram)

 private:
  static void PrepareCB(uv_prepare_t* handle);
  static void CheckCB(uv_check_t* handle);
  void OnStart(StartFlags flags = StartFlags::RESET);
  void OnStop();

  friend class HandleHistogramMixin<IterationHistogram>;
  template <typename T>
  friend void StartHandleHistogram(v8::Local<v8::Value>, bool);
  template <typename T>
  friend void StopHandleHistogram(v8::Local<v8::Value>);

  uv_prepare_t prepare_handle_;
  uv_check_t check_handle_;
  uint64_t prepare_time_ = 0;
  uint64_t check_time_ = 0;
  int64_t timeout_ = 0;

  static v8::CFunction fast_start_;
  static v8::CFunction fast_stop_;
};

}  // namespace node

#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif  // SRC_HISTOGRAM_H_