#ifndef V8_LOGGING_COUNTERS_H_
#define V8_LOGGING_COUNTERS_H_
#include <memory>
#include <variant>
#include "include/v8-callbacks.h"
#include "src/base/atomic-utils.h"
#include "src/base/platform/elapsed-timer.h"
#include "src/base/platform/time.h"
#include "src/common/globals.h"
#include "src/logging/counters-definitions.h"
#include "src/logging/runtime-call-stats.h"
#include "src/objects/code-kind.h"
#include "src/objects/objects.h"
#include "src/utils/allocation.h"
namespace v8::internal {
class Counters;
class Isolate;
class StatsTable {
public:
StatsTable(const StatsTable&) = delete;
StatsTable& operator=(const StatsTable&) = delete;
void SetCounterFunction(CounterLookupCallback f);
void SetCreateHistogramFunction(CreateHistogramCallback f) {
create_histogram_function_ = f;
}
void SetAddHistogramSampleFunction(AddHistogramSampleCallback f) {
add_histogram_sample_function_ = f;
}
bool HasCounterFunction() const { return lookup_function_ != nullptr; }
int* FindLocation(const char* name) {
if (!lookup_function_) return nullptr;
return lookup_function_(name);
}
void* CreateHistogram(const char* name, int min, int max, size_t buckets) {
if (!create_histogram_function_) return nullptr;
return create_histogram_function_(name, min, max, buckets);
}
void AddHistogramSample(void* histogram, int sample) {
if (!add_histogram_sample_function_) return;
return add_histogram_sample_function_(histogram, sample);
}
private:
friend class Counters;
explicit StatsTable(Counters* counters);
CounterLookupCallback lookup_function_;
CreateHistogramCallback create_histogram_function_;
AddHistogramSampleCallback add_histogram_sample_function_;
};
class StatsCounter {
public:
const char* name() const { return name_; }
void Set(int value) { GetPtr()->store(value, std::memory_order_relaxed); }
int Get() { return GetPtr()->load(); }
void Increment(int value = 1) {
GetPtr()->fetch_add(value, std::memory_order_relaxed);
}
void Decrement(int value = 1) {
GetPtr()->fetch_sub(value, std::memory_order_relaxed);
}
V8_EXPORT_PRIVATE bool Enabled();
std::atomic<int>* GetInternalPointer() { return GetPtr(); }
private:
friend class Counters;
friend class CountersInitializer;
friend class StatsCounterResetter;
void Initialize(const char* name, Counters* counters) {
DCHECK_NULL(counters_);
DCHECK_NOT_NULL(counters);
DCHECK_EQ(0, memcmp(name, "c:V8.", 5));
counters_ = counters;
name_ = name;
}
V8_NOINLINE V8_EXPORT_PRIVATE std::atomic<int>* SetupPtrFromStatsTable();
void Reset() { ptr_.store(nullptr, std::memory_order_relaxed); }
std::atomic<int>* GetPtr() {
auto* ptr = ptr_.load(std::memory_order_acquire);
if (V8_LIKELY(ptr)) return ptr;
return SetupPtrFromStatsTable();
}
Counters* counters_ = nullptr;
const char* name_ = nullptr;
std::atomic<std::atomic<int>*> ptr_{nullptr};
};
class Histogram {
public:
V8_EXPORT_PRIVATE void AddSample(int sample);
bool Enabled() { return histogram_ != nullptr; }
const char* name() const { return name_; }
int min() const { return min_; }
int max() const { return max_; }
int num_buckets() const { return num_buckets_; }
void AssertReportsToCounters(Counters* expected_counters) {
DCHECK_EQ(counters_, expected_counters);
}
protected:
Histogram() = default;
Histogram(const Histogram&) = delete;
Histogram& operator=(const Histogram&) = delete;
void Initialize(const char* name, int min, int max, int num_buckets,
Counters* counters) {
name_ = name;
min_ = min;
max_ = max;
num_buckets_ = num_buckets;
histogram_ = nullptr;
counters_ = counters;
DCHECK_NOT_NULL(counters_);
}
Counters* counters() const { return counters_; }
void Reset() { histogram_ = nullptr; }
void EnsureCreated(bool create_new = true) {
if (create_new && histogram_.load(std::memory_order_acquire) == nullptr) {
base::MutexGuard Guard(&mutex_);
if (histogram_.load(std::memory_order_relaxed) == nullptr)
histogram_.store(CreateHistogram(), std::memory_order_release);
}
}
private:
friend class Counters;
friend class CountersInitializer;
friend class HistogramResetter;
V8_EXPORT_PRIVATE void* CreateHistogram() const;
const char* name_;
int min_;
int max_;
int num_buckets_;
std::atomic<void*> histogram_;
Counters* counters_;
base::Mutex mutex_;
};
class PercentageHistogram : public Histogram {};
class LegacyMemoryHistogram : public Histogram {};
enum class TimedHistogramResolution { MILLISECOND, MICROSECOND };
class TimedHistogram : public Histogram {
public:
void RecordAbandon(base::ElapsedTimer* timer, Isolate* isolate);
V8_EXPORT_PRIVATE void AddTimedSample(base::TimeDelta sample);
#ifdef DEBUG
bool ToggleRunningState(bool expected_is_running) const;
#endif
protected:
void Stop(base::ElapsedTimer* timer);
void LogStart(Isolate* isolate);
void LogEnd(Isolate* isolate);
friend class Counters;
friend class CountersInitializer;
TimedHistogramResolution resolution_;
TimedHistogram() = default;
TimedHistogram(const TimedHistogram&) = delete;
TimedHistogram& operator=(const TimedHistogram&) = delete;
void Initialize(const char* name, int min, int max,
TimedHistogramResolution resolution, int num_buckets,
Counters* counters) {
Histogram::Initialize(name, min, max, num_buckets, counters);
resolution_ = resolution;
}
};
class NestedTimedHistogramScope;
class PauseNestedTimedHistogramScope;
class NestedTimedHistogram : public TimedHistogram {
public:
NestedTimedHistogram(const char* name, int min, int max,
TimedHistogramResolution resolution, int num_buckets,
Counters* counters)
: NestedTimedHistogram() {
Initialize(name, min, max, resolution, num_buckets, counters);
}
private:
friend class Counters;
friend class NestedTimedHistogramScope;
friend class PauseNestedTimedHistogramScope;
inline NestedTimedHistogramScope* Enter(NestedTimedHistogramScope* next) {
NestedTimedHistogramScope* previous = current_;
current_ = next;
return previous;
}
inline void Leave(NestedTimedHistogramScope* previous) {
current_ = previous;
}
NestedTimedHistogramScope* current_ = nullptr;
NestedTimedHistogram() = default;
NestedTimedHistogram(const NestedTimedHistogram&) = delete;
NestedTimedHistogram& operator=(const NestedTimedHistogram&) = delete;
};
class AggregatableHistogramTimer : public Histogram {
public:
void Start() { time_ = base::TimeDelta(); }
void Stop() {
if (time_ != base::TimeDelta()) {
AddSample(static_cast<int>(time_.InMicroseconds()));
}
}
void Add(base::TimeDelta other) { time_ += other; }
private:
friend class Counters;
AggregatableHistogramTimer() = default;
AggregatableHistogramTimer(const AggregatableHistogramTimer&) = delete;
AggregatableHistogramTimer& operator=(const AggregatableHistogramTimer&) =
delete;
base::TimeDelta time_;
};
class V8_NODISCARD AggregatingHistogramTimerScope {
public:
explicit AggregatingHistogramTimerScope(AggregatableHistogramTimer* histogram)
: histogram_(histogram) {
histogram_->Start();
}
~AggregatingHistogramTimerScope() { histogram_->Stop(); }
private:
AggregatableHistogramTimer* histogram_;
};
class V8_NODISCARD AggregatedHistogramTimerScope {
public:
explicit AggregatedHistogramTimerScope(AggregatableHistogramTimer* histogram)
: histogram_(histogram) {
timer_.Start();
}
~AggregatedHistogramTimerScope() { histogram_->Add(timer_.Elapsed()); }
private:
base::ElapsedTimer timer_;
AggregatableHistogramTimer* histogram_;
};
template <typename Histogram>
class AggregatedMemoryHistogram {
public:
explicit AggregatedMemoryHistogram(Histogram* backing_histogram)
: AggregatedMemoryHistogram() {
backing_histogram_ = backing_histogram;
}
void AddSample(double current_ms, double current_value);
private:
friend class Counters;
AggregatedMemoryHistogram()
: is_initialized_(false),
start_ms_(0.0),
last_ms_(0.0),
aggregate_value_(0.0),
last_value_(0.0),
backing_histogram_(nullptr) {}
double Aggregate(double current_ms, double current_value);
bool is_initialized_;
double start_ms_;
double last_ms_;
double aggregate_value_;
double last_value_;
Histogram* backing_histogram_;
};
template <typename Histogram>
void AggregatedMemoryHistogram<Histogram>::AddSample(double current_ms,
double current_value) {
if (!is_initialized_) {
aggregate_value_ = current_value;
start_ms_ = current_ms;
last_value_ = current_value;
last_ms_ = current_ms;
is_initialized_ = true;
} else {
const double kEpsilon = 1e-6;
const int kMaxSamples = 1000;
if (current_ms < last_ms_ + kEpsilon) {
last_value_ = current_value;
} else {
double sample_interval_ms = v8_flags.histogram_interval;
double end_ms = start_ms_ + sample_interval_ms;
if (end_ms <= current_ms + kEpsilon) {
double slope = (current_value - last_value_) / (current_ms - last_ms_);
int i;
for (i = 0; i < kMaxSamples && end_ms <= current_ms + kEpsilon; i++) {
double end_value = last_value_ + (end_ms - last_ms_) * slope;
double sample_value;
if (i == 0) {
sample_value = Aggregate(end_ms, end_value);
} else {
sample_value = (last_value_ + end_value) / 2;
}
backing_histogram_->AddSample(static_cast<int>(sample_value + 0.5));
last_value_ = end_value;
last_ms_ = end_ms;
end_ms += sample_interval_ms;
}
if (i == kMaxSamples) {
aggregate_value_ = current_value;
start_ms_ = current_ms;
} else {
aggregate_value_ = last_value_;
start_ms_ = last_ms_;
}
}
aggregate_value_ = current_ms > start_ms_ + kEpsilon
? Aggregate(current_ms, current_value)
: aggregate_value_;
last_value_ = current_value;
last_ms_ = current_ms;
}
}
}
template <typename Histogram>
double AggregatedMemoryHistogram<Histogram>::Aggregate(double current_ms,
double current_value) {
double interval_ms = current_ms - start_ms_;
double value = (current_value + last_value_) / 2;
return aggregate_value_ * ((last_ms_ - start_ms_) / interval_ms) +
value * ((current_ms - last_ms_) / interval_ms);
}
class Counters : public std::enable_shared_from_this<Counters> {
public:
explicit Counters(Isolate* isolate);
void ResetCounterFunction(CounterLookupCallback f);
void ResetCreateHistogramFunction(CreateHistogramCallback f);
void SetAddHistogramSampleFunction(AddHistogramSampleCallback f) {
stats_table_.SetAddHistogramSampleFunction(f);
}
#define HR(name, caption, min, max, num_buckets) \
Histogram* name() { \
name##_.EnsureCreated(); \
return &name##_; \
}
HISTOGRAM_RANGE_LIST(HR)
#undef HR
#if V8_ENABLE_DRUMBRAKE
#define HR(name, caption, min, max, num_buckets) \
Histogram* name() { \
name##_.EnsureCreated(v8_flags.slow_histograms); \
return &name##_; \
}
HISTOGRAM_RANGE_LIST_SLOW(HR)
#undef HR
#endif
#define HT(name, caption, max, res) \
NestedTimedHistogram* name() { \
name##_.EnsureCreated(); \
return &name##_; \
}
NESTED_TIMED_HISTOGRAM_LIST(HT)
#undef HT
#define HT(name, caption, max, res) \
NestedTimedHistogram* name() { \
name##_.EnsureCreated(v8_flags.slow_histograms); \
return &name##_; \
}
NESTED_TIMED_HISTOGRAM_LIST_SLOW(HT)
#undef HT
#define HT(name, caption, max, res) \
TimedHistogram* name() { \
name##_.EnsureCreated(); \
return &name##_; \
}
TIMED_HISTOGRAM_LIST(HT)
#undef HT
#define AHT(name, caption) \
AggregatableHistogramTimer* name() { \
name##_.EnsureCreated(); \
return &name##_; \
}
AGGREGATABLE_HISTOGRAM_TIMER_LIST(AHT)
#undef AHT
#define HP(name, caption) \
PercentageHistogram* name() { \
name##_.EnsureCreated(); \
return &name##_; \
}
HISTOGRAM_PERCENTAGE_LIST(HP)
#undef HP
#define HM(name, caption) \
LegacyMemoryHistogram* name() { \
name##_.EnsureCreated(); \
return &name##_; \
}
HISTOGRAM_LEGACY_MEMORY_LIST(HM)
#undef HM
#define SC(name, caption) \
StatsCounter* name() { return &name##_; }
STATS_COUNTER_LIST(SC)
STATS_COUNTER_NATIVE_CODE_LIST(SC)
#undef SC
#if defined(V8_RUNTIME_CALL_STATS) || defined(HITRACE_RUNTIME_CALL_STATS)
RuntimeCallStats* runtime_call_stats() { return &runtime_call_stats_; }
WorkerThreadRuntimeCallStats* worker_thread_runtime_call_stats() {
return &worker_thread_runtime_call_stats_;
}
#else
RuntimeCallStats* runtime_call_stats() { return nullptr; }
WorkerThreadRuntimeCallStats* worker_thread_runtime_call_stats() {
return nullptr;
}
#endif
private:
friend class CountersVisitor;
friend class Histogram;
friend class NestedTimedHistogramScope;
friend class StatsCounter;
friend class StatsTable;
int* FindLocation(const char* name) {
return stats_table_.FindLocation(name);
}
void* CreateHistogram(const char* name, int min, int max, size_t buckets) {
return stats_table_.CreateHistogram(name, min, max, buckets);
}
void AddHistogramSample(void* histogram, int sample) {
stats_table_.AddHistogramSample(histogram, sample);
}
Isolate* isolate() { return isolate_; }
#define HR(name, caption, min, max, num_buckets) Histogram name##_;
HISTOGRAM_RANGE_LIST(HR)
#if V8_ENABLE_DRUMBRAKE
HISTOGRAM_RANGE_LIST_SLOW(HR)
#endif
#undef HR
#define HT(name, caption, max, res) NestedTimedHistogram name##_;
NESTED_TIMED_HISTOGRAM_LIST(HT)
NESTED_TIMED_HISTOGRAM_LIST_SLOW(HT)
#undef HT
#define HT(name, caption, max, res) TimedHistogram name##_;
TIMED_HISTOGRAM_LIST(HT)
#undef HT
#define AHT(name, caption) AggregatableHistogramTimer name##_;
AGGREGATABLE_HISTOGRAM_TIMER_LIST(AHT)
#undef AHT
#define HP(name, caption) PercentageHistogram name##_;
HISTOGRAM_PERCENTAGE_LIST(HP)
#undef HP
#define HM(name, caption) LegacyMemoryHistogram name##_;
HISTOGRAM_LEGACY_MEMORY_LIST(HM)
#undef HM
#define SC(name, caption) StatsCounter name##_;
STATS_COUNTER_LIST(SC)
STATS_COUNTER_NATIVE_CODE_LIST(SC)
#undef SC
#if defined(V8_RUNTIME_CALL_STATS) || defined(HITRACE_RUNTIME_CALL_STATS)
RuntimeCallStats runtime_call_stats_;
WorkerThreadRuntimeCallStats worker_thread_runtime_call_stats_;
#endif
Isolate* isolate_;
StatsTable stats_table_;
DISALLOW_IMPLICIT_CONSTRUCTORS(Counters);
};
class CountersVisitor {
public:
explicit CountersVisitor(Counters* counters) : counters_(counters) {}
void Start();
Counters* counters() { return counters_; }
protected:
virtual void VisitHistograms();
virtual void VisitStatsCounters();
virtual void VisitHistogram(Histogram* histogram, const char* caption) {}
virtual void VisitStatsCounter(StatsCounter* counter, const char* caption) {}
virtual void Visit(Histogram* histogram, const char* caption, int min,
int max, int num_buckets);
virtual void Visit(TimedHistogram* histogram, const char* caption, int max,
TimedHistogramResolution res);
virtual void Visit(NestedTimedHistogram* histogram, const char* caption,
int max, TimedHistogramResolution res);
virtual void Visit(AggregatableHistogramTimer* histogram,
const char* caption);
virtual void Visit(PercentageHistogram* histogram, const char* caption);
virtual void Visit(LegacyMemoryHistogram* histogram, const char* caption);
virtual void Visit(StatsCounter* counter, const char* caption);
private:
Counters* counters_;
};
class CountersInitializer : public CountersVisitor {
public:
using CountersVisitor::CountersVisitor;
protected:
void Visit(Histogram* histogram, const char* caption, int min, int max,
int num_buckets) final;
void Visit(TimedHistogram* histogram, const char* caption, int max,
TimedHistogramResolution res) final;
void Visit(NestedTimedHistogram* histogram, const char* caption, int max,
TimedHistogramResolution res) final;
void Visit(AggregatableHistogramTimer* histogram, const char* caption) final;
void Visit(PercentageHistogram* histogram, const char* caption) final;
void Visit(LegacyMemoryHistogram* histogram, const char* caption) final;
void Visit(StatsCounter* counter, const char* caption) final;
};
class StatsCounterResetter : public CountersVisitor {
public:
using CountersVisitor::CountersVisitor;
protected:
void VisitHistograms() final {}
void VisitStatsCounter(StatsCounter* counter, const char* caption) final;
};
class HistogramResetter : public CountersVisitor {
public:
using CountersVisitor::CountersVisitor;
protected:
void VisitStatsCounters() final {}
void VisitHistogram(Histogram* histogram, const char* caption) final;
};
class DelayedCounterUpdates {
public:
using GetHistogramFn = Histogram* (Counters::*)();
using GetTimedHistogramFn = TimedHistogram* (Counters::*)();
using GetStatsCounterFn = StatsCounter* (Counters::*)();
void Publish(Isolate* isolate) {
if (V8_LIKELY(!has_updates_.load(std::memory_order_relaxed))) return;
PublishImpl(isolate);
}
void AddSample(GetHistogramFn fn, int sample) {
base::MutexGuard mutex_guard{&mutex_};
outstanding_updates_.push_back(HistogramUpdate{fn, sample});
has_updates_.store(true, std::memory_order_relaxed);
}
void AddTimedSample(GetTimedHistogramFn fn, base::TimeDelta sample) {
base::MutexGuard mutex_guard{&mutex_};
outstanding_updates_.push_back(TimedHistogramUpdate{fn, sample});
has_updates_.store(true, std::memory_order_relaxed);
}
void AddIncrement(GetStatsCounterFn fn, int increment) {
base::MutexGuard mutex_guard{&mutex_};
outstanding_updates_.push_back(StatsCounterUpdate{fn, increment});
has_updates_.store(true, std::memory_order_relaxed);
}
void AddAll(DelayedCounterUpdates&& other) {
if (other.outstanding_updates_.empty()) return;
base::MutexGuard mutex_guard{&mutex_};
if (outstanding_updates_.empty()) {
outstanding_updates_.swap(other.outstanding_updates_);
} else {
outstanding_updates_.insert(outstanding_updates_.end(),
other.outstanding_updates_.begin(),
other.outstanding_updates_.end());
other.outstanding_updates_.clear();
}
has_updates_.store(true, std::memory_order_relaxed);
}
size_t EstimateCurrentMemoryConsumption() const {
base::MutexGuard mutex_guard{&mutex_};
return sizeof(this) + outstanding_updates_.capacity() *
sizeof(*outstanding_updates_.data());
}
size_t NumOutstandingUpdates() const {
base::MutexGuard mutex_guard{&mutex_};
return outstanding_updates_.size();
}
private:
V8_NOINLINE V8_PRESERVE_MOST void PublishImpl(Isolate*);
struct HistogramUpdate {
GetHistogramFn fn;
int sample;
};
struct TimedHistogramUpdate {
GetTimedHistogramFn fn;
base::TimeDelta sample;
};
struct StatsCounterUpdate {
GetStatsCounterFn fn;
int increment;
};
using AnyUpdate =
std::variant<HistogramUpdate, TimedHistogramUpdate, StatsCounterUpdate>;
std::atomic<bool> has_updates_;
mutable base::Mutex mutex_;
std::vector<AnyUpdate> outstanding_updates_;
};
}
#endif