%PDF- %PDF-
| Direktori : /home2/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/heap/ |
| Current File : //home2/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/heap/gc-tracer.cc |
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/heap/gc-tracer.h"
#include <cstdarg>
#include "include/v8-metrics.h"
#include "src/base/atomic-utils.h"
#include "src/base/logging.h"
#include "src/base/optional.h"
#include "src/base/platform/time.h"
#include "src/base/strings.h"
#include "src/common/globals.h"
#include "src/execution/thread-id.h"
#include "src/heap/cppgc-js/cpp-heap.h"
#include "src/heap/cppgc/metric-recorder.h"
#include "src/heap/gc-tracer-inl.h"
#include "src/heap/heap-inl.h"
#include "src/heap/heap.h"
#include "src/heap/incremental-marking.h"
#include "src/heap/memory-balancer.h"
#include "src/heap/spaces.h"
#include "src/logging/counters.h"
#include "src/logging/metrics.h"
#include "src/logging/tracing-flags.h"
#include "src/tracing/tracing-category-observer.h"
namespace v8 {
namespace internal {
static size_t CountTotalHolesSize(Heap* heap) {
size_t holes_size = 0;
PagedSpaceIterator spaces(heap);
for (PagedSpace* space = spaces.Next(); space != nullptr;
space = spaces.Next()) {
DCHECK_GE(holes_size + space->Waste() + space->Available(), holes_size);
holes_size += space->Waste() + space->Available();
}
return holes_size;
}
namespace {
std::atomic<CollectionEpoch> global_epoch{0};
CollectionEpoch next_epoch() {
return global_epoch.fetch_add(1, std::memory_order_relaxed) + 1;
}
using BytesAndDuration = ::heap::base::BytesAndDuration;
double BoundedAverageSpeed(
const base::RingBuffer<BytesAndDuration>& buffer,
v8::base::Optional<v8::base::TimeDelta> selected_duration) {
constexpr size_t kMinNonEmptySpeedInBytesPerMs = 1;
constexpr size_t kMaxSpeedInBytesPerMs = GB;
return ::heap::base::AverageSpeed(
buffer, BytesAndDuration(), selected_duration,
kMinNonEmptySpeedInBytesPerMs, kMaxSpeedInBytesPerMs);
}
double BoundedAverageSpeed(const base::RingBuffer<BytesAndDuration>& buffer) {
return BoundedAverageSpeed(buffer, base::nullopt);
}
} // namespace
GCTracer::Event::Event(Type type, State state,
GarbageCollectionReason gc_reason,
const char* collector_reason)
: type(type),
state(state),
gc_reason(gc_reason),
collector_reason(collector_reason) {}
const char* ToString(GCTracer::Event::Type type, bool short_name) {
switch (type) {
case GCTracer::Event::Type::SCAVENGER:
return (short_name) ? "s" : "Scavenge";
case GCTracer::Event::Type::MARK_COMPACTOR:
case GCTracer::Event::Type::INCREMENTAL_MARK_COMPACTOR:
return (short_name) ? "mc" : "Mark-Compact";
case GCTracer::Event::Type::MINOR_MARK_SWEEPER:
case GCTracer::Event::Type::INCREMENTAL_MINOR_MARK_SWEEPER:
return (short_name) ? "mms" : "Minor Mark-Sweep";
case GCTracer::Event::Type::START:
return (short_name) ? "st" : "Start";
}
}
GCTracer::RecordGCPhasesInfo::RecordGCPhasesInfo(
Heap* heap, GarbageCollector collector, GarbageCollectionReason reason) {
if (Heap::IsYoungGenerationCollector(collector)) {
type_timer_ = nullptr;
type_priority_timer_ = nullptr;
if (!v8_flags.minor_ms) {
mode_ = Mode::Scavenger;
trace_event_name_ = "V8.GCScavenger";
} else {
mode_ = Mode::None;
trace_event_name_ = "V8.GCMinorMS";
}
} else {
DCHECK_EQ(GarbageCollector::MARK_COMPACTOR, collector);
Counters* counters = heap->isolate()->counters();
const bool in_background = heap->isolate()->IsIsolateInBackground();
const bool is_incremental = !heap->incremental_marking()->IsStopped();
mode_ = Mode::None;
// The following block selects histogram counters to emit. The trace event
// name should be changed when metrics are updated.
//
// Memory reducing GCs take priority over memory measurement GCs. They can
// happen at the same time when measuring memory is folded into a memory
// reducing GC.
if (is_incremental) {
if (heap->ShouldReduceMemory()) {
type_timer_ = counters->gc_finalize_incremental_memory_reducing();
type_priority_timer_ =
in_background
? counters->gc_finalize_incremental_memory_reducing_background()
: counters
->gc_finalize_incremental_memory_reducing_foreground();
trace_event_name_ = "V8.GCFinalizeMCReduceMemory";
} else if (reason == GarbageCollectionReason::kMeasureMemory) {
type_timer_ = counters->gc_finalize_incremental_memory_measure();
type_priority_timer_ =
in_background
? counters->gc_finalize_incremental_memory_measure_background()
: counters->gc_finalize_incremental_memory_measure_foreground();
trace_event_name_ = "V8.GCFinalizeMCMeasureMemory";
} else {
type_timer_ = counters->gc_finalize_incremental_regular();
type_priority_timer_ =
in_background
? counters->gc_finalize_incremental_regular_background()
: counters->gc_finalize_incremental_regular_foreground();
trace_event_name_ = "V8.GCFinalizeMC";
mode_ = Mode::Finalize;
}
} else {
trace_event_name_ = "V8.GCCompactor";
if (heap->ShouldReduceMemory()) {
type_timer_ = counters->gc_finalize_non_incremental_memory_reducing();
type_priority_timer_ =
in_background
? counters
->gc_finalize_non_incremental_memory_reducing_background()
: counters
->gc_finalize_non_incremental_memory_reducing_foreground();
} else if (reason == GarbageCollectionReason::kMeasureMemory) {
type_timer_ = counters->gc_finalize_non_incremental_memory_measure();
type_priority_timer_ =
in_background
? counters
->gc_finalize_non_incremental_memory_measure_background()
: counters
->gc_finalize_non_incremental_memory_measure_foreground();
} else {
type_timer_ = counters->gc_finalize_non_incremental_regular();
type_priority_timer_ =
in_background
? counters->gc_finalize_non_incremental_regular_background()
: counters->gc_finalize_non_incremental_regular_foreground();
}
}
}
}
GCTracer::GCTracer(Heap* heap, base::TimeTicks startup_time,
GarbageCollectionReason initial_gc_reason)
: heap_(heap),
current_(Event::Type::START, Event::State::NOT_RUNNING, initial_gc_reason,
nullptr),
previous_(current_),
allocation_time_(startup_time),
previous_mark_compact_end_time_(startup_time) {
// All accesses to incremental_marking_scope assume that incremental marking
// scopes come first.
static_assert(0 == Scope::FIRST_INCREMENTAL_SCOPE);
// We assume that MC_INCREMENTAL is the first scope so that we can properly
// map it to RuntimeCallStats.
static_assert(0 == Scope::MC_INCREMENTAL);
// Starting a new cycle will make the current event the previous event.
// Setting the current end time here allows us to refer back to a previous
// event's end time to compute time spent in mutator.
current_.end_time = previous_mark_compact_end_time_;
}
void GCTracer::ResetForTesting() {
this->~GCTracer();
new (this) GCTracer(heap_, base::TimeTicks::Now(),
GarbageCollectionReason::kTesting);
}
void GCTracer::StartObservablePause(base::TimeTicks time) {
DCHECK(!IsInObservablePause());
start_of_observable_pause_.emplace(time);
}
void GCTracer::UpdateCurrentEvent(GarbageCollectionReason gc_reason,
const char* collector_reason) {
// For incremental marking, the event has already been created and we just
// need to update a few fields.
DCHECK(current_.type == Event::Type::INCREMENTAL_MARK_COMPACTOR ||
current_.type == Event::Type::INCREMENTAL_MINOR_MARK_SWEEPER);
DCHECK_EQ(Event::State::ATOMIC, current_.state);
DCHECK(IsInObservablePause());
current_.gc_reason = gc_reason;
current_.collector_reason = collector_reason;
// TODO(chromium:1154636): The start_time of the current event contains
// currently the start time of the observable pause. This should be
// reconsidered.
current_.start_time = start_of_observable_pause_.value();
current_.reduce_memory = heap_->ShouldReduceMemory();
}
void GCTracer::StartCycle(GarbageCollector collector,
GarbageCollectionReason gc_reason,
const char* collector_reason, MarkingType marking) {
// We cannot start a new cycle while there's another one in its atomic pause.
DCHECK_NE(Event::State::ATOMIC, current_.state);
// We cannot start a new cycle while a young generation GC cycle has
// already interrupted a full GC cycle.
DCHECK(!young_gc_while_full_gc_);
young_gc_while_full_gc_ = current_.state != Event::State::NOT_RUNNING;
if (young_gc_while_full_gc_) {
// The cases for interruption are: Scavenger, MinorMS interrupting sweeping.
// In both cases we are fine with fetching background counters now and
// fixing them up later in StopAtomicPause().
FetchBackgroundCounters();
}
DCHECK_IMPLIES(young_gc_while_full_gc_,
Heap::IsYoungGenerationCollector(collector));
DCHECK_IMPLIES(young_gc_while_full_gc_,
!Event::IsYoungGenerationEvent(current_.type));
Event::Type type;
switch (collector) {
case GarbageCollector::SCAVENGER:
type = Event::Type::SCAVENGER;
break;
case GarbageCollector::MINOR_MARK_SWEEPER:
type = marking == MarkingType::kIncremental
? Event::Type::INCREMENTAL_MINOR_MARK_SWEEPER
: Event::Type::MINOR_MARK_SWEEPER;
break;
case GarbageCollector::MARK_COMPACTOR:
type = marking == MarkingType::kIncremental
? Event::Type::INCREMENTAL_MARK_COMPACTOR
: Event::Type::MARK_COMPACTOR;
break;
}
DCHECK_IMPLIES(!young_gc_while_full_gc_,
current_.state == Event::State::NOT_RUNNING);
DCHECK_EQ(Event::State::NOT_RUNNING, previous_.state);
previous_ = current_;
current_ = Event(type, Event::State::MARKING, gc_reason, collector_reason);
switch (marking) {
case MarkingType::kAtomic:
DCHECK(IsInObservablePause());
// TODO(chromium:1154636): The start_time of the current event contains
// currently the start time of the observable pause. This should be
// reconsidered.
current_.start_time = start_of_observable_pause_.value();
current_.reduce_memory = heap_->ShouldReduceMemory();
break;
case MarkingType::kIncremental:
// The current event will be updated later.
DCHECK_IMPLIES(Heap::IsYoungGenerationCollector(collector),
(v8_flags.minor_ms &&
collector == GarbageCollector::MINOR_MARK_SWEEPER));
DCHECK(!IsInObservablePause());
break;
}
if (Heap::IsYoungGenerationCollector(collector)) {
epoch_young_ = next_epoch();
} else {
epoch_full_ = next_epoch();
}
}
void GCTracer::StartAtomicPause() {
DCHECK_EQ(Event::State::MARKING, current_.state);
current_.state = Event::State::ATOMIC;
}
void GCTracer::StartInSafepoint(base::TimeTicks time) {
SampleAllocation(current_.start_time, heap_->NewSpaceAllocationCounter(),
heap_->OldGenerationAllocationCounter(),
heap_->EmbedderAllocationCounter());
current_.start_object_size = heap_->SizeOfObjects();
current_.start_memory_size = heap_->memory_allocator()->Size();
current_.start_holes_size = CountTotalHolesSize(heap_);
size_t new_space_size = (heap_->new_space() ? heap_->new_space()->Size() : 0);
size_t new_lo_space_size =
(heap_->new_lo_space() ? heap_->new_lo_space()->SizeOfObjects() : 0);
current_.young_object_size = new_space_size + new_lo_space_size;
current_.start_atomic_pause_time = time;
}
void GCTracer::StopInSafepoint(base::TimeTicks time) {
current_.end_object_size = heap_->SizeOfObjects();
current_.end_memory_size = heap_->memory_allocator()->Size();
current_.end_holes_size = CountTotalHolesSize(heap_);
current_.survived_young_object_size = heap_->SurvivedYoungObjectSize();
current_.end_atomic_pause_time = time;
// Do not include the GC pause for calculating the allocation rate. GC pause
// with heap verification can decrease the allocation rate significantly.
allocation_time_ = time;
if (v8_flags.memory_balancer) {
UpdateMemoryBalancerGCSpeed();
}
}
void GCTracer::StopObservablePause(GarbageCollector collector,
base::TimeTicks time) {
DCHECK(IsConsistentWithCollector(collector));
DCHECK(IsInObservablePause());
start_of_observable_pause_.reset();
// TODO(chromium:1154636): The end_time of the current event contains
// currently the end time of the observable pause. This should be
// reconsidered.
current_.end_time = time;
FetchBackgroundCounters();
const base::TimeDelta duration = current_.end_time - current_.start_time;
auto* long_task_stats = heap_->isolate()->GetCurrentLongTaskStats();
const bool is_young = Heap::IsYoungGenerationCollector(collector);
if (is_young) {
recorded_minor_gcs_total_.Push(
BytesAndDuration(current_.young_object_size, duration));
recorded_minor_gcs_survived_.Push(
BytesAndDuration(current_.survived_young_object_size, duration));
long_task_stats->gc_young_wall_clock_duration_us +=
duration.InMicroseconds();
} else {
DCHECK_EQ(0u, current_.incremental_marking_bytes);
DCHECK(current_.incremental_marking_duration.IsZero());
if (current_.type == Event::Type::INCREMENTAL_MARK_COMPACTOR) {
RecordIncrementalMarkingSpeed(incremental_marking_bytes_,
incremental_marking_duration_);
recorded_incremental_mark_compacts_.Push(
BytesAndDuration(current_.end_object_size, duration));
std::swap(current_.incremental_marking_bytes, incremental_marking_bytes_);
std::swap(current_.incremental_marking_duration,
incremental_marking_duration_);
for (int i = 0; i < Scope::NUMBER_OF_INCREMENTAL_SCOPES; i++) {
current_.incremental_scopes[i] = incremental_scopes_[i];
current_.scopes[i] = incremental_scopes_[i].duration;
new (&incremental_scopes_[i]) IncrementalInfos;
}
} else {
recorded_mark_compacts_.Push(
BytesAndDuration(current_.end_object_size, duration));
DCHECK_EQ(0u, incremental_marking_bytes_);
DCHECK(incremental_marking_duration_.IsZero());
}
RecordGCSumCounters();
combined_mark_compact_speed_cache_ = 0.0;
long_task_stats->gc_full_atomic_wall_clock_duration_us +=
duration.InMicroseconds();
RecordMutatorUtilization(current_.end_time,
duration + incremental_marking_duration_);
}
heap_->UpdateTotalGCTime(duration);
if (v8_flags.trace_gc_ignore_scavenger && is_young) return;
if (v8_flags.trace_gc_nvp) {
PrintNVP();
} else {
Print();
}
if (v8_flags.trace_gc) {
heap_->PrintShortHeapStatistics();
}
if (V8_UNLIKELY(TracingFlags::gc.load(std::memory_order_relaxed) &
v8::tracing::TracingCategoryObserver::ENABLED_BY_TRACING)) {
TRACE_GC_NOTE("V8.GC_HEAP_DUMP_STATISTICS");
std::stringstream heap_stats;
heap_->DumpJSONHeapStatistics(heap_stats);
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("v8.gc"), "V8.GC_Heap_Stats",
TRACE_EVENT_SCOPE_THREAD, "stats",
TRACE_STR_COPY(heap_stats.str().c_str()));
}
}
void GCTracer::UpdateMemoryBalancerGCSpeed() {
DCHECK(v8_flags.memory_balancer);
size_t major_gc_bytes = current_.start_object_size;
const base::TimeDelta atomic_pause_duration =
current_.end_atomic_pause_time - current_.start_atomic_pause_time;
const base::TimeDelta blocked_time_taken =
atomic_pause_duration + current_.incremental_marking_duration;
base::TimeDelta concurrent_gc_time;
{
base::MutexGuard guard(&background_scopes_mutex_);
concurrent_gc_time =
background_scopes_[Scope::MC_BACKGROUND_EVACUATE_COPY] +
background_scopes_[Scope::MC_BACKGROUND_EVACUATE_UPDATE_POINTERS] +
background_scopes_[Scope::MC_BACKGROUND_MARKING] +
background_scopes_[Scope::MC_BACKGROUND_SWEEPING];
}
const base::TimeDelta major_gc_duration =
blocked_time_taken + concurrent_gc_time;
const base::TimeDelta major_allocation_duration =
(current_.end_atomic_pause_time - previous_mark_compact_end_time_) -
blocked_time_taken;
CHECK_GE(major_allocation_duration, base::TimeDelta());
heap_->mb_->UpdateGCSpeed(major_gc_bytes, major_gc_duration);
}
void GCTracer::StopAtomicPause() {
DCHECK_EQ(Event::State::ATOMIC, current_.state);
current_.state = Event::State::SWEEPING;
}
void GCTracer::StopCycle(GarbageCollector collector) {
DCHECK_EQ(Event::State::SWEEPING, current_.state);
current_.state = Event::State::NOT_RUNNING;
DCHECK(IsConsistentWithCollector(collector));
FetchBackgroundCounters();
if (Heap::IsYoungGenerationCollector(collector)) {
ReportYoungCycleToRecorder();
// If a young generation GC interrupted an unfinished full GC cycle, restore
// the event corresponding to the full GC cycle.
if (young_gc_while_full_gc_) {
// Sweeping for full GC could have occured during the young GC. Copy over
// any sweeping scope values to the previous_ event. The full GC sweeping
// scopes are never reported by young cycles.
previous_.scopes[Scope::MC_SWEEP] += current_.scopes[Scope::MC_SWEEP];
previous_.scopes[Scope::MC_BACKGROUND_SWEEPING] +=
current_.scopes[Scope::MC_BACKGROUND_SWEEPING];
std::swap(current_, previous_);
young_gc_while_full_gc_ = false;
}
} else {
ReportFullCycleToRecorder();
heap_->isolate()->counters()->mark_compact_reason()->AddSample(
static_cast<int>(current_.gc_reason));
if (v8_flags.trace_gc_freelists) {
PrintIsolate(heap_->isolate(),
"FreeLists statistics before collection:\n");
heap_->PrintFreeListsStats();
}
}
}
void GCTracer::StopFullCycleIfNeeded() {
if (current_.state != Event::State::SWEEPING) return;
if (!notified_full_sweeping_completed_) return;
if (heap_->cpp_heap() && !notified_full_cppgc_completed_) return;
StopCycle(GarbageCollector::MARK_COMPACTOR);
notified_full_sweeping_completed_ = false;
notified_full_cppgc_completed_ = false;
full_cppgc_completed_during_minor_gc_ = false;
}
void GCTracer::StopYoungCycleIfNeeded() {
DCHECK(Event::IsYoungGenerationEvent(current_.type));
if (current_.state != Event::State::SWEEPING) return;
if ((current_.type == Event::Type::MINOR_MARK_SWEEPER ||
current_.type == Event::Type::INCREMENTAL_MINOR_MARK_SWEEPER) &&
!notified_young_sweeping_completed_)
return;
// Check if young cppgc was scheduled but hasn't completed yet.
if (heap_->cpp_heap() && notified_young_cppgc_running_ &&
!notified_young_cppgc_completed_)
return;
bool was_young_gc_while_full_gc_ = young_gc_while_full_gc_;
StopCycle(current_.type == Event::Type::SCAVENGER
? GarbageCollector::SCAVENGER
: GarbageCollector::MINOR_MARK_SWEEPER);
notified_young_sweeping_completed_ = false;
notified_young_cppgc_running_ = false;
notified_young_cppgc_completed_ = false;
if (was_young_gc_while_full_gc_) {
// Check if the full gc cycle is ready to be stopped.
StopFullCycleIfNeeded();
}
}
void GCTracer::NotifyFullSweepingCompleted() {
// Notifying twice that V8 sweeping is finished for the same cycle is possible
// only if Oilpan sweeping is still in progress.
DCHECK_IMPLIES(
notified_full_sweeping_completed_,
!notified_full_cppgc_completed_ || full_cppgc_completed_during_minor_gc_);
if (Event::IsYoungGenerationEvent(current_.type)) {
bool was_young_gc_while_full_gc = young_gc_while_full_gc_;
bool was_full_sweeping_notified = notified_full_sweeping_completed_;
NotifyYoungSweepingCompleted();
// NotifyYoungSweepingCompleted checks if the full cycle needs to be stopped
// as well. If full sweeping was already notified, nothing more needs to be
// done here.
if (!was_young_gc_while_full_gc || was_full_sweeping_notified) return;
}
DCHECK(!Event::IsYoungGenerationEvent(current_.type));
// Sweeping finalization can also be triggered from inside a full GC cycle's
// atomic pause.
DCHECK(current_.state == Event::State::SWEEPING ||
current_.state == Event::State::ATOMIC);
// Stop a full GC cycle only when both v8 and cppgc (if available) GCs have
// finished sweeping. This method is invoked by v8.
if (v8_flags.trace_gc_freelists) {
PrintIsolate(heap_->isolate(),
"FreeLists statistics after sweeping completed:\n");
heap_->PrintFreeListsStats();
}
notified_full_sweeping_completed_ = true;
StopFullCycleIfNeeded();
}
void GCTracer::NotifyYoungSweepingCompleted() {
if (!Event::IsYoungGenerationEvent(current_.type)) return;
if (v8_flags.verify_heap) {
// If heap verification is enabled, sweeping finalization can also be
// triggered from inside a full GC cycle's atomic pause.
DCHECK(current_.type == Event::Type::MINOR_MARK_SWEEPER ||
current_.type == Event::Type::INCREMENTAL_MINOR_MARK_SWEEPER);
DCHECK(current_.state == Event::State::SWEEPING ||
current_.state == Event::State::ATOMIC);
} else {
DCHECK(IsSweepingInProgress());
}
DCHECK(!notified_young_sweeping_completed_);
notified_young_sweeping_completed_ = true;
StopYoungCycleIfNeeded();
}
void GCTracer::NotifyFullCppGCCompleted() {
// Stop a full GC cycle only when both v8 and cppgc (if available) GCs have
// finished sweeping. This method is invoked by cppgc.
DCHECK(heap_->cpp_heap());
const auto* metric_recorder =
CppHeap::From(heap_->cpp_heap())->GetMetricRecorder();
USE(metric_recorder);
DCHECK(metric_recorder->FullGCMetricsReportPending());
DCHECK(!notified_full_cppgc_completed_);
notified_full_cppgc_completed_ = true;
// Cppgc sweeping may finalize during MinorMS sweeping. In that case, delay
// stopping the cycle until the nested MinorMS cycle is stopped.
if (Event::IsYoungGenerationEvent(current_.type)) {
DCHECK(young_gc_while_full_gc_);
full_cppgc_completed_during_minor_gc_ = true;
return;
}
StopFullCycleIfNeeded();
}
void GCTracer::NotifyYoungCppGCCompleted() {
// Stop a young GC cycle only when both v8 and cppgc (if available) GCs have
// finished sweeping. This method is invoked by cppgc.
DCHECK(heap_->cpp_heap());
DCHECK(notified_young_cppgc_running_);
const auto* metric_recorder =
CppHeap::From(heap_->cpp_heap())->GetMetricRecorder();
USE(metric_recorder);
DCHECK(metric_recorder->YoungGCMetricsReportPending());
DCHECK(!notified_young_cppgc_completed_);
notified_young_cppgc_completed_ = true;
StopYoungCycleIfNeeded();
}
void GCTracer::NotifyYoungCppGCRunning() {
DCHECK(!notified_young_cppgc_running_);
notified_young_cppgc_running_ = true;
}
void GCTracer::SampleAllocation(base::TimeTicks current,
size_t new_space_counter_bytes,
size_t old_generation_counter_bytes,
size_t embedder_counter_bytes) {
// This assumes that counters are unsigned integers so that the subtraction
// below works even if the new counter is less than the old counter.
size_t new_space_allocated_bytes =
new_space_counter_bytes - new_space_allocation_counter_bytes_;
size_t old_generation_allocated_bytes =
old_generation_counter_bytes - old_generation_allocation_counter_bytes_;
size_t embedder_allocated_bytes =
embedder_counter_bytes - embedder_allocation_counter_bytes_;
const base::TimeDelta allocation_duration = current - allocation_time_;
allocation_time_ = current;
new_space_allocation_counter_bytes_ = new_space_counter_bytes;
old_generation_allocation_counter_bytes_ = old_generation_counter_bytes;
embedder_allocation_counter_bytes_ = embedder_counter_bytes;
recorded_new_generation_allocations_.Push(
BytesAndDuration(new_space_allocated_bytes, allocation_duration));
recorded_old_generation_allocations_.Push(
BytesAndDuration(old_generation_allocated_bytes, allocation_duration));
recorded_embedder_generation_allocations_.Push(
BytesAndDuration(embedder_allocated_bytes, allocation_duration));
if (v8_flags.memory_balancer) {
heap_->mb_->UpdateAllocationRate(old_generation_allocated_bytes,
allocation_duration);
}
}
void GCTracer::NotifyMarkingStart() {
const auto marking_start = base::TimeTicks::Now();
uint16_t result = 1;
if (last_marking_start_time_.has_value()) {
const double diff_in_seconds = std::round(
(marking_start - last_marking_start_time_.value()).InSecondsF());
if (diff_in_seconds > UINT16_MAX) {
result = UINT16_MAX;
} else if (diff_in_seconds >= 1) {
result = static_cast<uint16_t>(diff_in_seconds);
}
}
DCHECK_GT(result, 0);
DCHECK_LE(result, UINT16_MAX);
code_flushing_increase_s_ = result;
last_marking_start_time_ = marking_start;
if (V8_UNLIKELY(v8_flags.trace_flush_code)) {
PrintIsolate(heap_->isolate(), "code flushing time: %d second(s)\n",
code_flushing_increase_s_);
}
}
uint16_t GCTracer::CodeFlushingIncrease() const {
return code_flushing_increase_s_;
}
void GCTracer::AddCompactionEvent(double duration,
size_t live_bytes_compacted) {
recorded_compactions_.Push(BytesAndDuration(
live_bytes_compacted, base::TimeDelta::FromMillisecondsD(duration)));
}
void GCTracer::AddSurvivalRatio(double promotion_ratio) {
recorded_survival_ratios_.Push(promotion_ratio);
}
void GCTracer::AddIncrementalMarkingStep(double duration, size_t bytes) {
if (bytes > 0) {
incremental_marking_bytes_ += bytes;
incremental_marking_duration_ +=
base::TimeDelta::FromMillisecondsD(duration);
}
ReportIncrementalMarkingStepToRecorder(duration);
}
void GCTracer::AddIncrementalSweepingStep(double duration) {
ReportIncrementalSweepingStepToRecorder(duration);
}
void GCTracer::Output(const char* format, ...) const {
if (v8_flags.trace_gc) {
va_list arguments;
va_start(arguments, format);
base::OS::VPrint(format, arguments);
va_end(arguments);
}
const int kBufferSize = 256;
char raw_buffer[kBufferSize];
base::Vector<char> buffer(raw_buffer, kBufferSize);
va_list arguments2;
va_start(arguments2, format);
base::VSNPrintF(buffer, format, arguments2);
va_end(arguments2);
heap_->AddToRingBuffer(buffer.begin());
}
void GCTracer::Print() const {
const base::TimeDelta duration = current_.end_time - current_.start_time;
const size_t kIncrementalStatsSize = 128;
char incremental_buffer[kIncrementalStatsSize] = {0};
if (current_.type == Event::Type::INCREMENTAL_MARK_COMPACTOR) {
base::OS::SNPrintF(
incremental_buffer, kIncrementalStatsSize,
" (+ %.1f ms in %d steps since start of marking, "
"biggest step %.1f ms, walltime since start of marking %.f ms)",
current_scope(Scope::MC_INCREMENTAL),
incremental_scope(Scope::MC_INCREMENTAL).steps,
incremental_scope(Scope::MC_INCREMENTAL).longest_step.InMillisecondsF(),
(current_.end_time - incremental_marking_start_time_)
.InMillisecondsF());
}
const double total_external_time =
current_scope(Scope::HEAP_EXTERNAL_WEAK_GLOBAL_HANDLES) +
current_scope(Scope::HEAP_EXTERNAL_EPILOGUE) +
current_scope(Scope::HEAP_EXTERNAL_PROLOGUE) +
current_scope(Scope::MC_INCREMENTAL_EXTERNAL_EPILOGUE) +
current_scope(Scope::MC_INCREMENTAL_EXTERNAL_PROLOGUE);
// Avoid PrintF as Output also appends the string to the tracing ring buffer
// that gets printed on OOM failures.
Output(
"[%d:%p] "
"%8.0f ms: "
"%s%s %.1f (%.1f) -> %.1f (%.1f) MB, "
"%.2f / %.2f ms %s (average mu = %.3f, current mu = %.3f) %s; %s\n",
base::OS::GetCurrentProcessId(),
reinterpret_cast<void*>(heap_->isolate()),
heap_->isolate()->time_millis_since_init(),
ToString(current_.type, false), current_.reduce_memory ? " (reduce)" : "",
static_cast<double>(current_.start_object_size) / MB,
static_cast<double>(current_.start_memory_size) / MB,
static_cast<double>(current_.end_object_size) / MB,
static_cast<double>(current_.end_memory_size) / MB,
duration.InMillisecondsF(), total_external_time, incremental_buffer,
AverageMarkCompactMutatorUtilization(),
CurrentMarkCompactMutatorUtilization(), ToString(current_.gc_reason),
current_.collector_reason != nullptr ? current_.collector_reason : "");
}
void GCTracer::PrintNVP() const {
const base::TimeDelta duration = current_.end_time - current_.start_time;
const base::TimeDelta spent_in_mutator =
current_.start_time - previous_.end_time;
size_t allocated_since_last_gc =
current_.start_object_size - previous_.end_object_size;
base::TimeDelta incremental_walltime_duration;
if (current_.type == Event::Type::INCREMENTAL_MARK_COMPACTOR) {
incremental_walltime_duration =
current_.end_time - incremental_marking_start_time_;
}
// Avoid data races when printing the background scopes.
base::MutexGuard guard(&background_scopes_mutex_);
switch (current_.type) {
case Event::Type::SCAVENGER:
heap_->isolate()->PrintWithTimestamp(
"pause=%.1f "
"mutator=%.1f "
"gc=%s "
"reduce_memory=%d "
"time_to_safepoint=%.2f "
"heap.prologue=%.2f "
"heap.epilogue=%.2f "
"heap.epilogue.reduce_new_space=%.2f "
"heap.external.prologue=%.2f "
"heap.external.epilogue=%.2f "
"heap.external_weak_global_handles=%.2f "
"fast_promote=%.2f "
"complete.sweep_array_buffers=%.2f "
"scavenge=%.2f "
"scavenge.free_remembered_set=%.2f "
"scavenge.roots=%.2f "
"scavenge.weak=%.2f "
"scavenge.weak_global_handles.identify=%.2f "
"scavenge.weak_global_handles.process=%.2f "
"scavenge.parallel=%.2f "
"scavenge.update_refs=%.2f "
"scavenge.sweep_array_buffers=%.2f "
"background.scavenge.parallel=%.2f "
"background.unmapper=%.2f "
"unmapper=%.2f "
"incremental.steps_count=%d "
"incremental.steps_took=%.1f "
"scavenge_throughput=%.f "
"total_size_before=%zu "
"total_size_after=%zu "
"holes_size_before=%zu "
"holes_size_after=%zu "
"allocated=%zu "
"promoted=%zu "
"new_space_survived=%zu "
"nodes_died_in_new=%d "
"nodes_copied_in_new=%d "
"nodes_promoted=%d "
"promotion_ratio=%.1f%% "
"average_survival_ratio=%.1f%% "
"promotion_rate=%.1f%% "
"new_space_survive_rate_=%.1f%% "
"new_space_allocation_throughput=%.1f "
"unmapper_chunks=%d\n",
duration.InMillisecondsF(), spent_in_mutator.InMillisecondsF(),
ToString(current_.type, true), current_.reduce_memory,
current_.scopes[Scope::TIME_TO_SAFEPOINT].InMillisecondsF(),
current_scope(Scope::HEAP_PROLOGUE),
current_scope(Scope::HEAP_EPILOGUE),
current_scope(Scope::HEAP_EPILOGUE_REDUCE_NEW_SPACE),
current_scope(Scope::HEAP_EXTERNAL_PROLOGUE),
current_scope(Scope::HEAP_EXTERNAL_EPILOGUE),
current_scope(Scope::HEAP_EXTERNAL_WEAK_GLOBAL_HANDLES),
current_scope(Scope::SCAVENGER_FAST_PROMOTE),
current_scope(Scope::SCAVENGER_COMPLETE_SWEEP_ARRAY_BUFFERS),
current_scope(Scope::SCAVENGER_SCAVENGE),
current_scope(Scope::SCAVENGER_FREE_REMEMBERED_SET),
current_scope(Scope::SCAVENGER_SCAVENGE_ROOTS),
current_scope(Scope::SCAVENGER_SCAVENGE_WEAK),
current_scope(Scope::SCAVENGER_SCAVENGE_WEAK_GLOBAL_HANDLES_IDENTIFY),
current_scope(Scope::SCAVENGER_SCAVENGE_WEAK_GLOBAL_HANDLES_PROCESS),
current_scope(Scope::SCAVENGER_SCAVENGE_PARALLEL),
current_scope(Scope::SCAVENGER_SCAVENGE_UPDATE_REFS),
current_scope(Scope::SCAVENGER_SWEEP_ARRAY_BUFFERS),
current_scope(Scope::SCAVENGER_BACKGROUND_SCAVENGE_PARALLEL),
current_scope(Scope::BACKGROUND_UNMAPPER),
current_scope(Scope::UNMAPPER),
incremental_scope(GCTracer::Scope::MC_INCREMENTAL).steps,
current_scope(Scope::MC_INCREMENTAL),
ScavengeSpeedInBytesPerMillisecond(), current_.start_object_size,
current_.end_object_size, current_.start_holes_size,
current_.end_holes_size, allocated_since_last_gc,
heap_->promoted_objects_size(),
heap_->new_space_surviving_object_size(),
heap_->nodes_died_in_new_space_, heap_->nodes_copied_in_new_space_,
heap_->nodes_promoted_, heap_->promotion_ratio_,
AverageSurvivalRatio(), heap_->promotion_rate_,
heap_->new_space_surviving_rate_,
NewSpaceAllocationThroughputInBytesPerMillisecond(),
heap_->memory_allocator()->unmapper()->NumberOfChunks());
break;
case Event::Type::MINOR_MARK_SWEEPER:
heap_->isolate()->PrintWithTimestamp(
"pause=%.1f "
"mutator=%.1f "
"gc=%s "
"reduce_memory=%d "
"minor_ms=%.2f "
"time_to_safepoint=%.2f "
"mark=%.2f "
"mark.incremental_seed=%.2f "
"mark.finish_incremental=%.2f "
"mark.seed=%.2f "
"mark.traced_handles=%.2f "
"mark.closure_parallel=%.2f "
"mark.closure=%.2f "
"mark.conservative_stack=%.2f "
"clear=%.2f "
"clear.string_forwarding_table=%.2f "
"clear.string_table=%.2f "
"clear.global_handles=%.2f "
"complete.sweep_array_buffers=%.2f "
"complete.sweeping=%.2f "
"sweep=%.2f "
"sweep.new=%.2f "
"sweep.new_lo=%.2f "
"sweep.update_string_table=%.2f "
"sweep.start_jobs=%.2f "
"sweep.array_buffers=%.2f "
"finish=%.2f "
"finish.ensure_capacity=%.2f "
"finish.sweep_array_buffers=%.2f "
"background.mark=%.2f "
"background.sweep=%.2f "
"background.sweep.array_buffers=%.2f "
"background.unmapper=%.2f "
"unmapper=%.2f "
"conservative_stack_scanning=%.2f "
"total_size_before=%zu "
"total_size_after=%zu "
"holes_size_before=%zu "
"holes_size_after=%zu "
"allocated=%zu "
"promoted=%zu "
"new_space_survived=%zu "
"nodes_died_in_new=%d "
"nodes_copied_in_new=%d "
"nodes_promoted=%d "
"promotion_ratio=%.1f%% "
"average_survival_ratio=%.1f%% "
"promotion_rate=%.1f%% "
"new_space_survive_rate_=%.1f%% "
"new_space_allocation_throughput=%.1f\n",
duration.InMillisecondsF(), spent_in_mutator.InMillisecondsF(), "mms",
current_.reduce_memory, current_scope(Scope::MINOR_MS),
current_scope(Scope::TIME_TO_SAFEPOINT),
current_scope(Scope::MINOR_MS_MARK),
current_scope(Scope::MINOR_MS_MARK_INCREMENTAL_SEED),
current_scope(Scope::MINOR_MS_MARK_FINISH_INCREMENTAL),
current_scope(Scope::MINOR_MS_MARK_SEED),
current_scope(Scope::MINOR_MS_MARK_TRACED_HANDLES),
current_scope(Scope::MINOR_MS_MARK_CLOSURE_PARALLEL),
current_scope(Scope::MINOR_MS_MARK_CLOSURE),
current_scope(Scope::MINOR_MS_MARK_CONSERVATIVE_STACK),
current_scope(Scope::MINOR_MS_CLEAR),
current_scope(Scope::MINOR_MS_CLEAR_STRING_FORWARDING_TABLE),
current_scope(Scope::MINOR_MS_CLEAR_STRING_TABLE),
current_scope(Scope::MINOR_MS_CLEAR_WEAK_GLOBAL_HANDLES),
current_scope(Scope::MINOR_MS_COMPLETE_SWEEP_ARRAY_BUFFERS),
current_scope(Scope::MINOR_MS_COMPLETE_SWEEPING),
current_scope(Scope::MINOR_MS_SWEEP),
current_scope(Scope::MINOR_MS_SWEEP_NEW),
current_scope(Scope::MINOR_MS_SWEEP_NEW_LO),
current_scope(Scope::MINOR_MS_SWEEP_UPDATE_STRING_TABLE),
current_scope(Scope::MINOR_MS_SWEEP_START_JOBS),
current_scope(Scope::YOUNG_ARRAY_BUFFER_SWEEP),
current_scope(Scope::MINOR_MS_FINISH),
current_scope(Scope::MINOR_MS_FINISH_ENSURE_CAPACITY),
current_scope(Scope::MINOR_MS_FINISH_SWEEP_ARRAY_BUFFERS),
current_scope(Scope::MINOR_MS_BACKGROUND_MARKING),
current_scope(Scope::MINOR_MS_BACKGROUND_SWEEPING),
current_scope(Scope::BACKGROUND_YOUNG_ARRAY_BUFFER_SWEEP),
current_scope(Scope::BACKGROUND_UNMAPPER),
current_scope(Scope::UNMAPPER),
current_scope(Scope::CONSERVATIVE_STACK_SCANNING),
current_.start_object_size, current_.end_object_size,
current_.start_holes_size, current_.end_holes_size,
allocated_since_last_gc, heap_->promoted_objects_size(),
heap_->new_space_surviving_object_size(),
heap_->nodes_died_in_new_space_, heap_->nodes_copied_in_new_space_,
heap_->nodes_promoted_, heap_->promotion_ratio_,
AverageSurvivalRatio(), heap_->promotion_rate_,
heap_->new_space_surviving_rate_,
NewSpaceAllocationThroughputInBytesPerMillisecond());
break;
case Event::Type::MARK_COMPACTOR:
case Event::Type::INCREMENTAL_MARK_COMPACTOR:
heap_->isolate()->PrintWithTimestamp(
"pause=%.1f "
"mutator=%.1f "
"gc=%s "
"reduce_memory=%d "
"time_to_safepoint=%.2f "
"heap.prologue=%.2f "
"heap.embedder_tracing_epilogue=%.2f "
"heap.epilogue=%.2f "
"heap.epilogue.reduce_new_space=%.2f "
"heap.external.prologue=%.1f "
"heap.external.epilogue=%.1f "
"heap.external.weak_global_handles=%.1f "
"clear=%1.f "
"clear.external_string_table=%.1f "
"clear.string_forwarding_table=%.1f "
"clear.weak_global_handles=%.1f "
"clear.dependent_code=%.1f "
"clear.maps=%.1f "
"clear.slots_buffer=%.1f "
"clear.weak_collections=%.1f "
"clear.weak_lists=%.1f "
"clear.weak_references=%.1f "
"clear.join_job=%.1f "
"complete.sweep_array_buffers=%.1f "
"complete.sweeping=%.1f "
"epilogue=%.1f "
"evacuate=%.1f "
"evacuate.candidates=%.1f "
"evacuate.clean_up=%.1f "
"evacuate.copy=%.1f "
"evacuate.prologue=%.1f "
"evacuate.epilogue=%.1f "
"evacuate.rebalance=%.1f "
"evacuate.update_pointers=%.1f "
"evacuate.update_pointers.to_new_roots=%.1f "
"evacuate.update_pointers.slots.main=%.1f "
"evacuate.update_pointers.weak=%.1f "
"finish=%.1f "
"finish.sweep_array_buffers=%.1f "
"mark=%.1f "
"mark.finish_incremental=%.1f "
"mark.roots=%.1f "
"mark.full_closure_parallel=%.1f "
"mark.full_closure=%.1f "
"mark.ephemeron.marking=%.1f "
"mark.ephemeron.linear=%.1f "
"mark.embedder_prologue=%.1f "
"mark.embedder_tracing=%.1f "
"prologue=%.1f "
"sweep=%.1f "
"sweep.code=%.1f "
"sweep.map=%.1f "
"sweep.new=%.1f "
"sweep.new_lo=%.1f "
"sweep.old=%.1f "
"sweep.start_jobs=%.1f "
"incremental=%.1f "
"incremental.finalize=%.1f "
"incremental.finalize.external.prologue=%.1f "
"incremental.finalize.external.epilogue=%.1f "
"incremental.layout_change=%.1f "
"incremental.sweep_array_buffers=%.1f "
"incremental.sweeping=%.1f "
"incremental.embedder_prologue=%.1f "
"incremental.embedder_tracing=%.1f "
"incremental_wrapper_tracing_longest_step=%.1f "
"incremental_longest_step=%.1f "
"incremental_steps_count=%d "
"incremental_marking_throughput=%.f "
"incremental_walltime_duration=%.f "
"background.mark=%.1f "
"background.sweep=%.1f "
"background.evacuate.copy=%.1f "
"background.evacuate.update_pointers=%.1f "
"background.unmapper=%.1f "
"unmapper=%.1f "
"conservative_stack_scanning=%.2f "
"total_size_before=%zu "
"total_size_after=%zu "
"holes_size_before=%zu "
"holes_size_after=%zu "
"allocated=%zu "
"promoted=%zu "
"new_space_survived=%zu "
"nodes_died_in_new=%d "
"nodes_copied_in_new=%d "
"nodes_promoted=%d "
"promotion_ratio=%.1f%% "
"average_survival_ratio=%.1f%% "
"promotion_rate=%.1f%% "
"new_space_survive_rate=%.1f%% "
"new_space_allocation_throughput=%.1f "
"unmapper_chunks=%d "
"compaction_speed=%.f\n",
duration.InMillisecondsF(), spent_in_mutator.InMillisecondsF(),
ToString(current_.type, true), current_.reduce_memory,
current_scope(Scope::TIME_TO_SAFEPOINT),
current_scope(Scope::HEAP_PROLOGUE),
current_scope(Scope::HEAP_EMBEDDER_TRACING_EPILOGUE),
current_scope(Scope::HEAP_EPILOGUE),
current_scope(Scope::HEAP_EPILOGUE_REDUCE_NEW_SPACE),
current_scope(Scope::HEAP_EXTERNAL_PROLOGUE),
current_scope(Scope::HEAP_EXTERNAL_EPILOGUE),
current_scope(Scope::HEAP_EXTERNAL_WEAK_GLOBAL_HANDLES),
current_scope(Scope::MC_CLEAR),
current_scope(Scope::MC_CLEAR_EXTERNAL_STRING_TABLE),
current_scope(Scope::MC_CLEAR_STRING_FORWARDING_TABLE),
current_scope(Scope::MC_CLEAR_WEAK_GLOBAL_HANDLES),
current_scope(Scope::MC_CLEAR_DEPENDENT_CODE),
current_scope(Scope::MC_CLEAR_MAPS),
current_scope(Scope::MC_CLEAR_SLOTS_BUFFER),
current_scope(Scope::MC_CLEAR_WEAK_COLLECTIONS),
current_scope(Scope::MC_CLEAR_WEAK_LISTS),
current_scope(Scope::MC_CLEAR_WEAK_REFERENCES),
current_scope(Scope::MC_CLEAR_JOIN_JOB),
current_scope(Scope::MC_COMPLETE_SWEEP_ARRAY_BUFFERS),
current_scope(Scope::MC_COMPLETE_SWEEPING),
current_scope(Scope::MC_EPILOGUE), current_scope(Scope::MC_EVACUATE),
current_scope(Scope::MC_EVACUATE_CANDIDATES),
current_scope(Scope::MC_EVACUATE_CLEAN_UP),
current_scope(Scope::MC_EVACUATE_COPY),
current_scope(Scope::MC_EVACUATE_PROLOGUE),
current_scope(Scope::MC_EVACUATE_EPILOGUE),
current_scope(Scope::MC_EVACUATE_REBALANCE),
current_scope(Scope::MC_EVACUATE_UPDATE_POINTERS),
current_scope(Scope::MC_EVACUATE_UPDATE_POINTERS_TO_NEW_ROOTS),
current_scope(Scope::MC_EVACUATE_UPDATE_POINTERS_SLOTS_MAIN),
current_scope(Scope::MC_EVACUATE_UPDATE_POINTERS_WEAK),
current_scope(Scope::MC_FINISH),
current_scope(Scope::MC_FINISH_SWEEP_ARRAY_BUFFERS),
current_scope(Scope::MC_MARK),
current_scope(Scope::MC_MARK_FINISH_INCREMENTAL),
current_scope(Scope::MC_MARK_ROOTS),
current_scope(Scope::MC_MARK_FULL_CLOSURE_PARALLEL),
current_scope(Scope::MC_MARK_FULL_CLOSURE),
current_scope(Scope::MC_MARK_WEAK_CLOSURE_EPHEMERON_MARKING),
current_scope(Scope::MC_MARK_WEAK_CLOSURE_EPHEMERON_LINEAR),
current_scope(Scope::MC_MARK_EMBEDDER_PROLOGUE),
current_scope(Scope::MC_MARK_EMBEDDER_TRACING),
current_scope(Scope::MC_PROLOGUE), current_scope(Scope::MC_SWEEP),
current_scope(Scope::MC_SWEEP_CODE),
current_scope(Scope::MC_SWEEP_MAP),
current_scope(Scope::MC_SWEEP_NEW),
current_scope(Scope::MC_SWEEP_NEW_LO),
current_scope(Scope::MC_SWEEP_OLD),
current_scope(Scope::MC_SWEEP_START_JOBS),
current_scope(Scope::MC_INCREMENTAL),
current_scope(Scope::MC_INCREMENTAL_FINALIZE),
current_scope(Scope::MC_INCREMENTAL_EXTERNAL_PROLOGUE),
current_scope(Scope::MC_INCREMENTAL_EXTERNAL_EPILOGUE),
current_scope(Scope::MC_INCREMENTAL_LAYOUT_CHANGE),
current_scope(Scope::MC_INCREMENTAL_START),
current_scope(Scope::MC_INCREMENTAL_SWEEPING),
current_scope(Scope::MC_INCREMENTAL_EMBEDDER_PROLOGUE),
current_scope(Scope::MC_INCREMENTAL_EMBEDDER_TRACING),
incremental_scope(Scope::MC_INCREMENTAL_EMBEDDER_TRACING)
.longest_step.InMillisecondsF(),
incremental_scope(Scope::MC_INCREMENTAL)
.longest_step.InMillisecondsF(),
incremental_scope(Scope::MC_INCREMENTAL).steps,
IncrementalMarkingSpeedInBytesPerMillisecond(),
incremental_walltime_duration.InMillisecondsF(),
current_scope(Scope::MC_BACKGROUND_MARKING),
current_scope(Scope::MC_BACKGROUND_SWEEPING),
current_scope(Scope::MC_BACKGROUND_EVACUATE_COPY),
current_scope(Scope::MC_BACKGROUND_EVACUATE_UPDATE_POINTERS),
current_scope(Scope::BACKGROUND_UNMAPPER),
current_scope(Scope::UNMAPPER),
current_scope(Scope::CONSERVATIVE_STACK_SCANNING),
current_.start_object_size, current_.end_object_size,
current_.start_holes_size, current_.end_holes_size,
allocated_since_last_gc, heap_->promoted_objects_size(),
heap_->new_space_surviving_object_size(),
heap_->nodes_died_in_new_space_, heap_->nodes_copied_in_new_space_,
heap_->nodes_promoted_, heap_->promotion_ratio_,
AverageSurvivalRatio(), heap_->promotion_rate_,
heap_->new_space_surviving_rate_,
NewSpaceAllocationThroughputInBytesPerMillisecond(),
heap_->memory_allocator()->unmapper()->NumberOfChunks(),
CompactionSpeedInBytesPerMillisecond());
break;
case Event::Type::START:
break;
default:
UNREACHABLE();
}
}
void GCTracer::RecordIncrementalMarkingSpeed(size_t bytes,
base::TimeDelta duration) {
if (duration.IsZero() || bytes == 0) return;
double current_speed =
static_cast<double>(bytes) / duration.InMillisecondsF();
if (recorded_incremental_marking_speed_ == 0) {
recorded_incremental_marking_speed_ = current_speed;
} else {
recorded_incremental_marking_speed_ =
(recorded_incremental_marking_speed_ + current_speed) / 2;
}
}
void GCTracer::RecordTimeToIncrementalMarkingTask(
base::TimeDelta time_to_task) {
if (!average_time_to_incremental_marking_task_.has_value()) {
average_time_to_incremental_marking_task_.emplace(time_to_task);
} else {
average_time_to_incremental_marking_task_ =
(average_time_to_incremental_marking_task_.value() + time_to_task) / 2;
}
}
base::Optional<base::TimeDelta> GCTracer::AverageTimeToIncrementalMarkingTask()
const {
return average_time_to_incremental_marking_task_;
}
void GCTracer::RecordEmbedderSpeed(size_t bytes, double duration) {
if (duration == 0 || bytes == 0) return;
double current_speed = bytes / duration;
if (recorded_embedder_speed_ == 0.0) {
recorded_embedder_speed_ = current_speed;
} else {
recorded_embedder_speed_ = (recorded_embedder_speed_ + current_speed) / 2;
}
}
void GCTracer::RecordMutatorUtilization(base::TimeTicks mark_compact_end_time,
base::TimeDelta mark_compact_duration) {
const base::TimeDelta total_duration =
mark_compact_end_time - previous_mark_compact_end_time_;
DCHECK_GE(total_duration, base::TimeDelta());
const base::TimeDelta mutator_duration =
total_duration - mark_compact_duration;
DCHECK_GE(mutator_duration, base::TimeDelta());
if (average_mark_compact_duration_ == 0 && average_mutator_duration_ == 0) {
// This is the first event with mutator and mark-compact durations.
average_mark_compact_duration_ = mark_compact_duration.InMillisecondsF();
average_mutator_duration_ = mutator_duration.InMillisecondsF();
} else {
average_mark_compact_duration_ = (average_mark_compact_duration_ +
mark_compact_duration.InMillisecondsF()) /
2;
average_mutator_duration_ =
(average_mutator_duration_ + mutator_duration.InMillisecondsF()) / 2;
}
current_mark_compact_mutator_utilization_ =
!total_duration.IsZero() ? mutator_duration.InMillisecondsF() /
total_duration.InMillisecondsF()
: 0;
previous_mark_compact_end_time_ = mark_compact_end_time;
}
double GCTracer::AverageMarkCompactMutatorUtilization() const {
double average_total_duration =
average_mark_compact_duration_ + average_mutator_duration_;
if (average_total_duration == 0) return 1.0;
return average_mutator_duration_ / average_total_duration;
}
double GCTracer::CurrentMarkCompactMutatorUtilization() const {
return current_mark_compact_mutator_utilization_;
}
double GCTracer::IncrementalMarkingSpeedInBytesPerMillisecond() const {
if (recorded_incremental_marking_speed_ != 0) {
return recorded_incremental_marking_speed_;
}
if (!incremental_marking_duration_.IsZero()) {
return incremental_marking_bytes_ /
incremental_marking_duration_.InMillisecondsF();
}
return kConservativeSpeedInBytesPerMillisecond;
}
double GCTracer::EmbedderSpeedInBytesPerMillisecond() const {
// Note: Returning 0 is ok here as callers check for whether embedder speeds
// have been recorded at all.
return recorded_embedder_speed_;
}
double GCTracer::ScavengeSpeedInBytesPerMillisecond(
ScavengeSpeedMode mode) const {
if (mode == kForAllObjects) {
return BoundedAverageSpeed(recorded_minor_gcs_total_);
} else {
return BoundedAverageSpeed(recorded_minor_gcs_survived_);
}
}
double GCTracer::CompactionSpeedInBytesPerMillisecond() const {
return BoundedAverageSpeed(recorded_compactions_);
}
double GCTracer::MarkCompactSpeedInBytesPerMillisecond() const {
return BoundedAverageSpeed(recorded_mark_compacts_);
}
double GCTracer::FinalIncrementalMarkCompactSpeedInBytesPerMillisecond() const {
return BoundedAverageSpeed(recorded_incremental_mark_compacts_);
}
double GCTracer::CombinedMarkCompactSpeedInBytesPerMillisecond() {
const double kMinimumMarkingSpeed = 0.5;
if (combined_mark_compact_speed_cache_ > 0)
return combined_mark_compact_speed_cache_;
// MarkCompact speed is more stable than incremental marking speed, because
// there might not be many incremental marking steps because of concurrent
// marking.
combined_mark_compact_speed_cache_ = MarkCompactSpeedInBytesPerMillisecond();
if (combined_mark_compact_speed_cache_ > 0)
return combined_mark_compact_speed_cache_;
double speed1 = IncrementalMarkingSpeedInBytesPerMillisecond();
double speed2 = FinalIncrementalMarkCompactSpeedInBytesPerMillisecond();
if (speed1 < kMinimumMarkingSpeed || speed2 < kMinimumMarkingSpeed) {
// No data for the incremental marking speed.
// Return the non-incremental mark-compact speed.
combined_mark_compact_speed_cache_ =
MarkCompactSpeedInBytesPerMillisecond();
} else {
// Combine the speed of incremental step and the speed of the final step.
// 1 / (1 / speed1 + 1 / speed2) = speed1 * speed2 / (speed1 + speed2).
combined_mark_compact_speed_cache_ = speed1 * speed2 / (speed1 + speed2);
}
return combined_mark_compact_speed_cache_;
}
double GCTracer::CombineSpeedsInBytesPerMillisecond(double default_speed,
double optional_speed) {
constexpr double kMinimumSpeed = 0.5;
if (optional_speed < kMinimumSpeed) {
return default_speed;
}
return default_speed * optional_speed / (default_speed + optional_speed);
}
double GCTracer::NewSpaceAllocationThroughputInBytesPerMillisecond(
base::Optional<base::TimeDelta> selected_duration) const {
return BoundedAverageSpeed(
recorded_new_generation_allocations_,
selected_duration);
}
double GCTracer::OldGenerationAllocationThroughputInBytesPerMillisecond(
base::Optional<base::TimeDelta> selected_duration) const {
return BoundedAverageSpeed(
recorded_old_generation_allocations_,
selected_duration);
}
double GCTracer::EmbedderAllocationThroughputInBytesPerMillisecond(
base::Optional<base::TimeDelta> selected_duration) const {
return BoundedAverageSpeed(
recorded_embedder_generation_allocations_,
selected_duration);
}
double GCTracer::AllocationThroughputInBytesPerMillisecond(
base::Optional<base::TimeDelta> selected_duration) const {
return NewSpaceAllocationThroughputInBytesPerMillisecond(selected_duration) +
OldGenerationAllocationThroughputInBytesPerMillisecond(
selected_duration);
}
double GCTracer::CurrentAllocationThroughputInBytesPerMillisecond() const {
return AllocationThroughputInBytesPerMillisecond(kThroughputTimeFrame);
}
double GCTracer::CurrentOldGenerationAllocationThroughputInBytesPerMillisecond()
const {
return OldGenerationAllocationThroughputInBytesPerMillisecond(
kThroughputTimeFrame);
}
double GCTracer::CurrentEmbedderAllocationThroughputInBytesPerMillisecond()
const {
return EmbedderAllocationThroughputInBytesPerMillisecond(
kThroughputTimeFrame);
}
double GCTracer::AverageSurvivalRatio() const {
if (recorded_survival_ratios_.Empty()) return 0.0;
double sum = recorded_survival_ratios_.Reduce(
[](double a, double b) { return a + b; }, 0.0);
return sum / recorded_survival_ratios_.Size();
}
bool GCTracer::SurvivalEventsRecorded() const {
return !recorded_survival_ratios_.Empty();
}
void GCTracer::ResetSurvivalEvents() { recorded_survival_ratios_.Clear(); }
void GCTracer::NotifyIncrementalMarkingStart() {
incremental_marking_start_time_ = base::TimeTicks::Now();
}
void GCTracer::FetchBackgroundCounters() {
base::MutexGuard guard(&background_scopes_mutex_);
for (int i = Scope::FIRST_BACKGROUND_SCOPE; i <= Scope::LAST_BACKGROUND_SCOPE;
i++) {
current_.scopes[i] += background_scopes_[i];
background_scopes_[i] = base::TimeDelta();
}
}
namespace {
V8_INLINE int TruncateToMs(base::TimeDelta delta) {
return static_cast<int>(delta.InMilliseconds());
}
} // namespace
void GCTracer::RecordGCPhasesHistograms(RecordGCPhasesInfo::Mode mode) {
Counters* counters = heap_->isolate()->counters();
if (mode == RecordGCPhasesInfo::Mode::Finalize) {
DCHECK_EQ(Scope::FIRST_TOP_MC_SCOPE, Scope::MC_CLEAR);
counters->gc_finalize_clear()->AddSample(
TruncateToMs(current_.scopes[Scope::MC_CLEAR]));
counters->gc_finalize_epilogue()->AddSample(
TruncateToMs(current_.scopes[Scope::MC_EPILOGUE]));
counters->gc_finalize_evacuate()->AddSample(
TruncateToMs(current_.scopes[Scope::MC_EVACUATE]));
counters->gc_finalize_finish()->AddSample(
TruncateToMs(current_.scopes[Scope::MC_FINISH]));
counters->gc_finalize_mark()->AddSample(
TruncateToMs(current_.scopes[Scope::MC_MARK]));
counters->gc_finalize_prologue()->AddSample(
TruncateToMs(current_.scopes[Scope::MC_PROLOGUE]));
counters->gc_finalize_sweep()->AddSample(
TruncateToMs(current_.scopes[Scope::MC_SWEEP]));
if (!incremental_marking_duration_.IsZero()) {
heap_->isolate()->counters()->incremental_marking_sum()->AddSample(
TruncateToMs(incremental_marking_duration_));
}
const base::TimeDelta overall_marking_time =
incremental_marking_duration_ + current_.scopes[Scope::MC_MARK];
heap_->isolate()->counters()->gc_marking_sum()->AddSample(
TruncateToMs(overall_marking_time));
DCHECK_EQ(Scope::LAST_TOP_MC_SCOPE, Scope::MC_SWEEP);
} else if (mode == RecordGCPhasesInfo::Mode::Scavenger) {
counters->gc_scavenger_scavenge_main()->AddSample(
TruncateToMs(current_.scopes[Scope::SCAVENGER_SCAVENGE_PARALLEL]));
counters->gc_scavenger_scavenge_roots()->AddSample(
TruncateToMs(current_.scopes[Scope::SCAVENGER_SCAVENGE_ROOTS]));
}
}
void GCTracer::RecordGCSumCounters() {
const base::TimeDelta atomic_pause_duration =
current_.scopes[Scope::MARK_COMPACTOR];
const base::TimeDelta incremental_marking =
incremental_scopes_[Scope::MC_INCREMENTAL_LAYOUT_CHANGE].duration +
incremental_scopes_[Scope::MC_INCREMENTAL_START].duration +
incremental_marking_duration_ +
incremental_scopes_[Scope::MC_INCREMENTAL_FINALIZE].duration;
const base::TimeDelta incremental_sweeping =
incremental_scopes_[Scope::MC_INCREMENTAL_SWEEPING].duration;
const base::TimeDelta overall_duration =
atomic_pause_duration + incremental_marking + incremental_sweeping;
const base::TimeDelta atomic_marking_duration =
current_.scopes[Scope::MC_PROLOGUE] + current_.scopes[Scope::MC_MARK];
const base::TimeDelta marking_duration =
atomic_marking_duration + incremental_marking;
base::TimeDelta background_duration;
base::TimeDelta marking_background_duration;
{
base::MutexGuard guard(&background_scopes_mutex_);
background_duration =
background_scopes_[Scope::MC_BACKGROUND_EVACUATE_COPY] +
background_scopes_[Scope::MC_BACKGROUND_EVACUATE_UPDATE_POINTERS] +
background_scopes_[Scope::MC_BACKGROUND_MARKING] +
background_scopes_[Scope::MC_BACKGROUND_SWEEPING];
marking_background_duration =
background_scopes_[Scope::MC_BACKGROUND_MARKING];
}
// Emit trace event counters.
TRACE_EVENT_INSTANT2(
TRACE_DISABLED_BY_DEFAULT("v8.gc"), "V8.GCMarkCompactorSummary",
TRACE_EVENT_SCOPE_THREAD, "duration", overall_duration.InMillisecondsF(),
"background_duration", background_duration.InMillisecondsF());
TRACE_EVENT_INSTANT2(
TRACE_DISABLED_BY_DEFAULT("v8.gc"), "V8.GCMarkCompactorMarkingSummary",
TRACE_EVENT_SCOPE_THREAD, "duration", marking_duration.InMillisecondsF(),
"background_duration", marking_background_duration.InMillisecondsF());
}
namespace {
void CopyTimeMetrics(
::v8::metrics::GarbageCollectionPhases& metrics,
const cppgc::internal::MetricRecorder::GCCycle::IncrementalPhases&
cppgc_metrics) {
// Allow for uninitialized values (-1), in case incremental marking/sweeping
// were not used.
DCHECK_LE(-1, cppgc_metrics.mark_duration_us);
metrics.mark_wall_clock_duration_in_us = cppgc_metrics.mark_duration_us;
DCHECK_LE(-1, cppgc_metrics.sweep_duration_us);
metrics.sweep_wall_clock_duration_in_us = cppgc_metrics.sweep_duration_us;
// The total duration is initialized, even if both incremental
// marking and sweeping were not used.
metrics.total_wall_clock_duration_in_us =
std::max(INT64_C(0), metrics.mark_wall_clock_duration_in_us) +
std::max(INT64_C(0), metrics.sweep_wall_clock_duration_in_us);
}
void CopyTimeMetrics(
::v8::metrics::GarbageCollectionPhases& metrics,
const cppgc::internal::MetricRecorder::GCCycle::Phases& cppgc_metrics) {
DCHECK_NE(-1, cppgc_metrics.compact_duration_us);
metrics.compact_wall_clock_duration_in_us = cppgc_metrics.compact_duration_us;
DCHECK_NE(-1, cppgc_metrics.mark_duration_us);
metrics.mark_wall_clock_duration_in_us = cppgc_metrics.mark_duration_us;
DCHECK_NE(-1, cppgc_metrics.sweep_duration_us);
metrics.sweep_wall_clock_duration_in_us = cppgc_metrics.sweep_duration_us;
DCHECK_NE(-1, cppgc_metrics.weak_duration_us);
metrics.weak_wall_clock_duration_in_us = cppgc_metrics.weak_duration_us;
metrics.total_wall_clock_duration_in_us =
metrics.compact_wall_clock_duration_in_us +
metrics.mark_wall_clock_duration_in_us +
metrics.sweep_wall_clock_duration_in_us +
metrics.weak_wall_clock_duration_in_us;
}
void CopySizeMetrics(
::v8::metrics::GarbageCollectionSizes& metrics,
const cppgc::internal::MetricRecorder::GCCycle::Sizes& cppgc_metrics) {
DCHECK_NE(-1, cppgc_metrics.after_bytes);
metrics.bytes_after = cppgc_metrics.after_bytes;
DCHECK_NE(-1, cppgc_metrics.before_bytes);
metrics.bytes_before = cppgc_metrics.before_bytes;
DCHECK_NE(-1, cppgc_metrics.freed_bytes);
metrics.bytes_freed = cppgc_metrics.freed_bytes;
}
::v8::metrics::Recorder::ContextId GetContextId(
v8::internal::Isolate* isolate) {
DCHECK_NOT_NULL(isolate);
if (isolate->context().is_null())
return v8::metrics::Recorder::ContextId::Empty();
HandleScope scope(isolate);
return isolate->GetOrRegisterRecorderContextId(isolate->native_context());
}
template <typename EventType>
void FlushBatchedEvents(
v8::metrics::GarbageCollectionBatchedEvents<EventType>& batched_events,
Isolate* isolate) {
DCHECK_NOT_NULL(isolate->metrics_recorder());
DCHECK(!batched_events.events.empty());
isolate->metrics_recorder()->AddMainThreadEvent(std::move(batched_events),
GetContextId(isolate));
batched_events = {};
}
} // namespace
void GCTracer::ReportFullCycleToRecorder() {
DCHECK(!Event::IsYoungGenerationEvent(current_.type));
DCHECK_EQ(Event::State::NOT_RUNNING, current_.state);
auto* cpp_heap = v8::internal::CppHeap::From(heap_->cpp_heap());
DCHECK_IMPLIES(cpp_heap,
cpp_heap->GetMetricRecorder()->FullGCMetricsReportPending());
const std::shared_ptr<metrics::Recorder>& recorder =
heap_->isolate()->metrics_recorder();
DCHECK_NOT_NULL(recorder);
if (!recorder->HasEmbedderRecorder()) {
incremental_mark_batched_events_ = {};
incremental_sweep_batched_events_ = {};
if (cpp_heap) {
cpp_heap->GetMetricRecorder()->ClearCachedEvents();
}
return;
}
if (!incremental_mark_batched_events_.events.empty()) {
FlushBatchedEvents(incremental_mark_batched_events_, heap_->isolate());
}
if (!incremental_sweep_batched_events_.events.empty()) {
FlushBatchedEvents(incremental_sweep_batched_events_, heap_->isolate());
}
v8::metrics::GarbageCollectionFullCycle event;
event.reason = static_cast<int>(current_.gc_reason);
// Managed C++ heap statistics:
if (cpp_heap) {
cpp_heap->GetMetricRecorder()->FlushBatchedIncrementalEvents();
const base::Optional<cppgc::internal::MetricRecorder::GCCycle>
optional_cppgc_event =
cpp_heap->GetMetricRecorder()->ExtractLastFullGcEvent();
DCHECK(optional_cppgc_event.has_value());
DCHECK(!cpp_heap->GetMetricRecorder()->FullGCMetricsReportPending());
const cppgc::internal::MetricRecorder::GCCycle& cppgc_event =
optional_cppgc_event.value();
DCHECK_EQ(cppgc_event.type,
cppgc::internal::MetricRecorder::GCCycle::Type::kMajor);
CopyTimeMetrics(event.total_cpp, cppgc_event.total);
CopyTimeMetrics(event.main_thread_cpp, cppgc_event.main_thread);
CopyTimeMetrics(event.main_thread_atomic_cpp,
cppgc_event.main_thread_atomic);
CopyTimeMetrics(event.main_thread_incremental_cpp,
cppgc_event.main_thread_incremental);
CopySizeMetrics(event.objects_cpp, cppgc_event.objects);
CopySizeMetrics(event.memory_cpp, cppgc_event.memory);
DCHECK_NE(-1, cppgc_event.collection_rate_in_percent);
event.collection_rate_cpp_in_percent =
cppgc_event.collection_rate_in_percent;
DCHECK_NE(-1, cppgc_event.efficiency_in_bytes_per_us);
event.efficiency_cpp_in_bytes_per_us =
cppgc_event.efficiency_in_bytes_per_us;
DCHECK_NE(-1, cppgc_event.main_thread_efficiency_in_bytes_per_us);
event.main_thread_efficiency_cpp_in_bytes_per_us =
cppgc_event.main_thread_efficiency_in_bytes_per_us;
}
// Unified heap statistics:
const base::TimeDelta atomic_pause_duration =
current_.scopes[Scope::MARK_COMPACTOR];
const base::TimeDelta incremental_marking =
current_.incremental_scopes[Scope::MC_INCREMENTAL_LAYOUT_CHANGE]
.duration +
current_.incremental_scopes[Scope::MC_INCREMENTAL_START].duration +
current_.incremental_marking_duration +
current_.incremental_scopes[Scope::MC_INCREMENTAL_FINALIZE].duration;
const base::TimeDelta incremental_sweeping =
current_.incremental_scopes[Scope::MC_INCREMENTAL_SWEEPING].duration;
const base::TimeDelta overall_duration =
atomic_pause_duration + incremental_marking + incremental_sweeping;
const base::TimeDelta marking_background_duration =
current_.scopes[Scope::MC_BACKGROUND_MARKING];
const base::TimeDelta sweeping_background_duration =
current_.scopes[Scope::MC_BACKGROUND_SWEEPING];
const base::TimeDelta compact_background_duration =
current_.scopes[Scope::MC_BACKGROUND_EVACUATE_COPY] +
current_.scopes[Scope::MC_BACKGROUND_EVACUATE_UPDATE_POINTERS];
const base::TimeDelta background_duration = marking_background_duration +
sweeping_background_duration +
compact_background_duration;
const base::TimeDelta atomic_marking_duration =
current_.scopes[Scope::MC_PROLOGUE] + current_.scopes[Scope::MC_MARK];
const base::TimeDelta marking_duration =
atomic_marking_duration + incremental_marking;
const base::TimeDelta weak_duration = current_.scopes[Scope::MC_CLEAR];
const base::TimeDelta compact_duration = current_.scopes[Scope::MC_EVACUATE] +
current_.scopes[Scope::MC_FINISH] +
current_.scopes[Scope::MC_EPILOGUE];
const base::TimeDelta atomic_sweeping_duration =
current_.scopes[Scope::MC_SWEEP];
const base::TimeDelta sweeping_duration =
atomic_sweeping_duration + incremental_sweeping;
event.main_thread_atomic.total_wall_clock_duration_in_us =
atomic_pause_duration.InMicroseconds();
event.main_thread.total_wall_clock_duration_in_us =
overall_duration.InMicroseconds();
event.total.total_wall_clock_duration_in_us =
(overall_duration + background_duration).InMicroseconds();
event.main_thread_atomic.mark_wall_clock_duration_in_us =
atomic_marking_duration.InMicroseconds();
event.main_thread.mark_wall_clock_duration_in_us =
marking_duration.InMicroseconds();
event.total.mark_wall_clock_duration_in_us =
(marking_duration + marking_background_duration).InMicroseconds();
event.main_thread_atomic.weak_wall_clock_duration_in_us =
event.main_thread.weak_wall_clock_duration_in_us =
event.total.weak_wall_clock_duration_in_us =
weak_duration.InMicroseconds();
event.main_thread_atomic.compact_wall_clock_duration_in_us =
event.main_thread.compact_wall_clock_duration_in_us =
compact_duration.InMicroseconds();
event.total.compact_wall_clock_duration_in_us =
(compact_duration + compact_background_duration).InMicroseconds();
event.main_thread_atomic.sweep_wall_clock_duration_in_us =
atomic_sweeping_duration.InMicroseconds();
event.main_thread.sweep_wall_clock_duration_in_us =
sweeping_duration.InMicroseconds();
event.total.sweep_wall_clock_duration_in_us =
(sweeping_duration + sweeping_background_duration).InMicroseconds();
if (current_.type == Event::Type::INCREMENTAL_MARK_COMPACTOR) {
event.main_thread_incremental.mark_wall_clock_duration_in_us =
incremental_marking.InMicroseconds();
event.incremental_marking_start_stop_wall_clock_duration_in_us =
(current_.start_time - incremental_marking_start_time_)
.InMicroseconds();
} else {
DCHECK(incremental_marking.IsZero());
event.main_thread_incremental.mark_wall_clock_duration_in_us = -1;
}
// TODO(chromium:1154636): We always report the value of incremental sweeping,
// even if it is zero.
event.main_thread_incremental.sweep_wall_clock_duration_in_us =
incremental_sweeping.InMicroseconds();
// TODO(chromium:1154636): Populate the following:
// - event.objects
// - event.memory
// - event.collection_rate_in_percent
// - event.efficiency_in_bytes_per_us
// - event.main_thread_efficiency_in_bytes_per_us
recorder->AddMainThreadEvent(event, GetContextId(heap_->isolate()));
}
void GCTracer::ReportIncrementalMarkingStepToRecorder(double v8_duration) {
DCHECK_EQ(Event::Type::INCREMENTAL_MARK_COMPACTOR, current_.type);
static constexpr int kMaxBatchedEvents =
CppHeap::MetricRecorderAdapter::kMaxBatchedEvents;
const std::shared_ptr<metrics::Recorder>& recorder =
heap_->isolate()->metrics_recorder();
DCHECK_NOT_NULL(recorder);
if (!recorder->HasEmbedderRecorder()) return;
incremental_mark_batched_events_.events.emplace_back();
if (heap_->cpp_heap()) {
const base::Optional<
cppgc::internal::MetricRecorder::MainThreadIncrementalMark>
cppgc_event = v8::internal::CppHeap::From(heap_->cpp_heap())
->GetMetricRecorder()
->ExtractLastIncrementalMarkEvent();
if (cppgc_event.has_value()) {
DCHECK_NE(-1, cppgc_event.value().duration_us);
incremental_mark_batched_events_.events.back()
.cpp_wall_clock_duration_in_us = cppgc_event.value().duration_us;
}
}
incremental_mark_batched_events_.events.back().wall_clock_duration_in_us =
static_cast<int64_t>(v8_duration *
base::Time::kMicrosecondsPerMillisecond);
if (incremental_mark_batched_events_.events.size() == kMaxBatchedEvents) {
FlushBatchedEvents(incremental_mark_batched_events_, heap_->isolate());
}
}
void GCTracer::ReportIncrementalSweepingStepToRecorder(double v8_duration) {
static constexpr int kMaxBatchedEvents =
CppHeap::MetricRecorderAdapter::kMaxBatchedEvents;
const std::shared_ptr<metrics::Recorder>& recorder =
heap_->isolate()->metrics_recorder();
DCHECK_NOT_NULL(recorder);
if (!recorder->HasEmbedderRecorder()) return;
incremental_sweep_batched_events_.events.emplace_back();
incremental_sweep_batched_events_.events.back().wall_clock_duration_in_us =
static_cast<int64_t>(v8_duration *
base::Time::kMicrosecondsPerMillisecond);
if (incremental_sweep_batched_events_.events.size() == kMaxBatchedEvents) {
FlushBatchedEvents(incremental_sweep_batched_events_, heap_->isolate());
}
}
void GCTracer::ReportYoungCycleToRecorder() {
DCHECK(Event::IsYoungGenerationEvent(current_.type));
DCHECK_EQ(Event::State::NOT_RUNNING, current_.state);
const std::shared_ptr<metrics::Recorder>& recorder =
heap_->isolate()->metrics_recorder();
DCHECK_NOT_NULL(recorder);
if (!recorder->HasEmbedderRecorder()) return;
v8::metrics::GarbageCollectionYoungCycle event;
// Reason:
event.reason = static_cast<int>(current_.gc_reason);
#if defined(CPPGC_YOUNG_GENERATION)
// Managed C++ heap statistics:
auto* cpp_heap = v8::internal::CppHeap::From(heap_->cpp_heap());
if (cpp_heap && cpp_heap->generational_gc_supported()) {
auto* metric_recorder = cpp_heap->GetMetricRecorder();
const base::Optional<cppgc::internal::MetricRecorder::GCCycle>
optional_cppgc_event = metric_recorder->ExtractLastYoungGcEvent();
// We bail out from Oilpan's young GC if the full GC is already in progress.
// Check here if the young generation event was reported.
if (optional_cppgc_event) {
DCHECK(!metric_recorder->YoungGCMetricsReportPending());
const cppgc::internal::MetricRecorder::GCCycle& cppgc_event =
optional_cppgc_event.value();
DCHECK_EQ(cppgc_event.type,
cppgc::internal::MetricRecorder::GCCycle::Type::kMinor);
CopyTimeMetrics(event.total_cpp, cppgc_event.total);
CopySizeMetrics(event.objects_cpp, cppgc_event.objects);
CopySizeMetrics(event.memory_cpp, cppgc_event.memory);
DCHECK_NE(-1, cppgc_event.collection_rate_in_percent);
event.collection_rate_cpp_in_percent =
cppgc_event.collection_rate_in_percent;
DCHECK_NE(-1, cppgc_event.efficiency_in_bytes_per_us);
event.efficiency_cpp_in_bytes_per_us =
cppgc_event.efficiency_in_bytes_per_us;
DCHECK_NE(-1, cppgc_event.main_thread_efficiency_in_bytes_per_us);
event.main_thread_efficiency_cpp_in_bytes_per_us =
cppgc_event.main_thread_efficiency_in_bytes_per_us;
}
}
#endif // defined(CPPGC_YOUNG_GENERATION)
// Total:
const base::TimeDelta total_wall_clock_duration =
current_.scopes[Scope::SCAVENGER] +
current_.scopes[Scope::MINOR_MARK_SWEEPER] +
current_.scopes[Scope::SCAVENGER_BACKGROUND_SCAVENGE_PARALLEL] +
current_.scopes[Scope::MINOR_MS_BACKGROUND_MARKING];
// TODO(chromium:1154636): Consider adding BACKGROUND_YOUNG_ARRAY_BUFFER_SWEEP
// (both for the case of the scavenger and the minor mark-sweeper), and
// BACKGROUND_UNMAPPER (for the case of the minor mark-sweeper).
event.total_wall_clock_duration_in_us =
total_wall_clock_duration.InMicroseconds();
// MainThread:
const base::TimeDelta main_thread_wall_clock_duration =
current_.scopes[Scope::SCAVENGER] +
current_.scopes[Scope::MINOR_MARK_SWEEPER];
event.main_thread_wall_clock_duration_in_us =
main_thread_wall_clock_duration.InMicroseconds();
// Collection Rate:
if (current_.young_object_size == 0) {
event.collection_rate_in_percent = 0;
} else {
event.collection_rate_in_percent =
static_cast<double>(current_.survived_young_object_size) /
current_.young_object_size;
}
// Efficiency:
//
// It's possible that time durations are rounded/clamped to zero, in which
// case we report infinity efficiency.
const double freed_bytes = static_cast<double>(
current_.young_object_size - current_.survived_young_object_size);
event.efficiency_in_bytes_per_us =
total_wall_clock_duration.IsZero()
? std::numeric_limits<double>::infinity()
: freed_bytes / total_wall_clock_duration.InMicroseconds();
event.main_thread_efficiency_in_bytes_per_us =
main_thread_wall_clock_duration.IsZero()
? std::numeric_limits<double>::infinity()
: freed_bytes / main_thread_wall_clock_duration.InMicroseconds();
recorder->AddMainThreadEvent(event, GetContextId(heap_->isolate()));
}
GarbageCollector GCTracer::GetCurrentCollector() const {
switch (current_.type) {
case Event::Type::SCAVENGER:
return GarbageCollector::SCAVENGER;
case Event::Type::MARK_COMPACTOR:
case Event::Type::INCREMENTAL_MARK_COMPACTOR:
return GarbageCollector::MARK_COMPACTOR;
case Event::Type::MINOR_MARK_SWEEPER:
case Event::Type::INCREMENTAL_MINOR_MARK_SWEEPER:
return GarbageCollector::MINOR_MARK_SWEEPER;
case Event::Type::START:
UNREACHABLE();
}
}
#ifdef DEBUG
bool GCTracer::IsInObservablePause() const {
return start_of_observable_pause_.has_value();
}
bool GCTracer::IsInAtomicPause() const {
return current_.state == Event::State::ATOMIC;
}
bool GCTracer::IsConsistentWithCollector(GarbageCollector collector) const {
switch (collector) {
case GarbageCollector::SCAVENGER:
return current_.type == Event::Type::SCAVENGER;
case GarbageCollector::MARK_COMPACTOR:
return current_.type == Event::Type::MARK_COMPACTOR ||
current_.type == Event::Type::INCREMENTAL_MARK_COMPACTOR;
case GarbageCollector::MINOR_MARK_SWEEPER:
return current_.type == Event::Type::MINOR_MARK_SWEEPER ||
current_.type == Event::Type::INCREMENTAL_MINOR_MARK_SWEEPER;
}
}
bool GCTracer::IsSweepingInProgress() const {
return (current_.type == Event::Type::MARK_COMPACTOR ||
current_.type == Event::Type::INCREMENTAL_MARK_COMPACTOR ||
current_.type == Event::Type::MINOR_MARK_SWEEPER ||
current_.type == Event::Type::INCREMENTAL_MINOR_MARK_SWEEPER) &&
current_.state == Event::State::SWEEPING;
}
#endif
} // namespace internal
} // namespace v8