Skip to content

Commit 690a837

Browse files
targosnodejs-github-bot
authored andcommitted
deps: V8: cherry-pick 3d59a3c2c164
Original commit message: Add option to report discarded allocations in sampling heap profiler A couple of customers have asked about using devtools to get information about temporary allocations, with the goal of reducing GC time and/or peak memory usage. Currently, the sampling heap profiler reports only objects which are still alive at the end of the profiling session. In this change, I propose adding configuration options when starting the sampling heap profiler so that it can optionally include information about objects which were discarded by the GC before the end of the profiling session. A user could run the sampling heap profiler in several different modes depending on their goals: 1. To find memory leaks or determine which functions contribute most to steady-state memory consumption, the current default mode is best. 2. To find functions which cause large temporary memory spikes or large GC pauses, the user can request data about both live objects and those collected by major GC. 3. To tune for minimal GC activity in latency-sensitive applications like real-time audio processing, the user can request data about every allocation, including objects collected by major or minor GC. 4. I'm not sure why anybody would want data about objects collected by minor GC and not objects collected by major GC, but it's also a valid flags combination. Change-Id: If55d5965a1de04fed3ae640a02ca369723f64fdf Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3868522 Reviewed-by: Michael Lippautz <[email protected]> Reviewed-by: Camillo Bruni <[email protected]> Reviewed-by: Simon Zünd <[email protected]> Commit-Queue: Seth Brenith <[email protected]> Cr-Commit-Position: refs/heads/main@{#83202} Refs: v8/v8@3d59a3c PR-URL: #44958 Reviewed-By: Jiawen Geng <[email protected]> Reviewed-By: Beth Griggs <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]> Reviewed-By: Richard Lau <[email protected]>
1 parent bab8b3a commit 690a837

10 files changed

+135
-9
lines changed

common.gypi

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
# Reset this number to 0 on major V8 upgrades.
3838
# Increment by one for each non-official patch applied to deps/v8.
39-
'v8_embedder_string': '-node.12',
39+
'v8_embedder_string': '-node.13',
4040

4141
##### V8 defaults for Node.js #####
4242

deps/v8/include/js_protocol.pdl

+16
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,22 @@ experimental domain HeapProfiler
766766
# Average sample interval in bytes. Poisson distribution is used for the intervals. The
767767
# default value is 32768 bytes.
768768
optional number samplingInterval
769+
# By default, the sampling heap profiler reports only objects which are
770+
# still alive when the profile is returned via getSamplingProfile or
771+
# stopSampling, which is useful for determining what functions contribute
772+
# the most to steady-state memory usage. This flag instructs the sampling
773+
# heap profiler to also include information about objects discarded by
774+
# major GC, which will show which functions cause large temporary memory
775+
# usage or long GC pauses.
776+
optional boolean includeObjectsCollectedByMajorGC
777+
# By default, the sampling heap profiler reports only objects which are
778+
# still alive when the profile is returned via getSamplingProfile or
779+
# stopSampling, which is useful for determining what functions contribute
780+
# the most to steady-state memory usage. This flag instructs the sampling
781+
# heap profiler to also include information about objects discarded by
782+
# minor GC, which is useful when tuning a latency-sensitive application
783+
# for minimal GC activity.
784+
optional boolean includeObjectsCollectedByMinorGC
769785

770786
command startTrackingHeapObjects
771787
parameters

deps/v8/include/v8-profiler.h

+4-4
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,8 @@ class V8_EXPORT HeapProfiler {
903903
enum SamplingFlags {
904904
kSamplingNoFlags = 0,
905905
kSamplingForceGC = 1 << 0,
906+
kSamplingIncludeObjectsCollectedByMajorGC = 1 << 1,
907+
kSamplingIncludeObjectsCollectedByMinorGC = 1 << 2,
906908
};
907909

908910
/**
@@ -1097,10 +1099,8 @@ class V8_EXPORT HeapProfiler {
10971099
* |stack_depth| parameter controls the maximum number of stack frames to be
10981100
* captured on each allocation.
10991101
*
1100-
* NOTE: This is a proof-of-concept at this point. Right now we only sample
1101-
* newspace allocations. Support for paged space allocation (e.g. pre-tenured
1102-
* objects, large objects, code objects, etc.) and native allocations
1103-
* doesn't exist yet, but is anticipated in the future.
1102+
* NOTE: Support for native allocations doesn't exist yet, but is anticipated
1103+
* in the future.
11041104
*
11051105
* Objects allocated before the sampling is started will not be included in
11061106
* the profile.

deps/v8/src/heap/heap.cc

+2
Original file line numberDiff line numberDiff line change
@@ -1819,6 +1819,8 @@ bool Heap::CollectGarbage(AllocationSpace space,
18191819

18201820
collector = SelectGarbageCollector(space, gc_reason, &collector_reason);
18211821

1822+
current_or_last_garbage_collector_ = collector;
1823+
18221824
if (collector == GarbageCollector::MARK_COMPACTOR &&
18231825
incremental_marking()->IsMinorMarking()) {
18241826
CollectGarbage(NEW_SPACE, GarbageCollectionReason::kFinalizeMinorMC);

deps/v8/src/heap/heap.h

+6
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,10 @@ class Heap {
14551455

14561456
bool is_current_gc_forced() const { return is_current_gc_forced_; }
14571457

1458+
GarbageCollector current_or_last_garbage_collector() const {
1459+
return current_or_last_garbage_collector_;
1460+
}
1461+
14581462
// Returns whether the currently in-progress GC should avoid increasing the
14591463
// ages on any objects that live for a set number of collections.
14601464
bool ShouldCurrentGCKeepAgesUnchanged() const {
@@ -2389,6 +2393,8 @@ class Heap {
23892393

23902394
bool is_current_gc_forced_ = false;
23912395
bool is_current_gc_for_heap_profiler_ = false;
2396+
GarbageCollector current_or_last_garbage_collector_ =
2397+
GarbageCollector::SCAVENGER;
23922398

23932399
ExternalStringTable external_string_table_;
23942400

deps/v8/src/inspector/v8-heap-profiler-agent-impl.cc

+23-3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ static const char allocationTrackingEnabled[] = "allocationTrackingEnabled";
2929
static const char samplingHeapProfilerEnabled[] = "samplingHeapProfilerEnabled";
3030
static const char samplingHeapProfilerInterval[] =
3131
"samplingHeapProfilerInterval";
32+
static const char samplingHeapProfilerFlags[] = "samplingHeapProfilerFlags";
3233
} // namespace HeapProfilerAgentState
3334

3435
class HeapSnapshotProgress final : public v8::ActivityControl {
@@ -208,7 +209,16 @@ void V8HeapProfilerAgentImpl::restore() {
208209
double samplingInterval = m_state->doubleProperty(
209210
HeapProfilerAgentState::samplingHeapProfilerInterval, -1);
210211
DCHECK_GE(samplingInterval, 0);
211-
startSampling(Maybe<double>(samplingInterval));
212+
int flags = m_state->integerProperty(
213+
HeapProfilerAgentState::samplingHeapProfilerFlags, 0);
214+
startSampling(
215+
Maybe<double>(samplingInterval),
216+
Maybe<bool>(
217+
flags &
218+
v8::HeapProfiler::kSamplingIncludeObjectsCollectedByMajorGC),
219+
Maybe<bool>(
220+
flags &
221+
v8::HeapProfiler::kSamplingIncludeObjectsCollectedByMinorGC));
212222
}
213223
}
214224

@@ -387,7 +397,9 @@ void V8HeapProfilerAgentImpl::stopTrackingHeapObjectsInternal() {
387397
}
388398

389399
Response V8HeapProfilerAgentImpl::startSampling(
390-
Maybe<double> samplingInterval) {
400+
Maybe<double> samplingInterval,
401+
Maybe<bool> includeObjectsCollectedByMajorGC,
402+
Maybe<bool> includeObjectsCollectedByMinorGC) {
391403
v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
392404
if (!profiler) return Response::ServerError("Cannot access v8 heap profiler");
393405
const unsigned defaultSamplingInterval = 1 << 15;
@@ -400,9 +412,17 @@ Response V8HeapProfilerAgentImpl::startSampling(
400412
samplingIntervalValue);
401413
m_state->setBoolean(HeapProfilerAgentState::samplingHeapProfilerEnabled,
402414
true);
415+
int flags = v8::HeapProfiler::kSamplingForceGC;
416+
if (includeObjectsCollectedByMajorGC.fromMaybe(false)) {
417+
flags |= v8::HeapProfiler::kSamplingIncludeObjectsCollectedByMajorGC;
418+
}
419+
if (includeObjectsCollectedByMinorGC.fromMaybe(false)) {
420+
flags |= v8::HeapProfiler::kSamplingIncludeObjectsCollectedByMinorGC;
421+
}
422+
m_state->setInteger(HeapProfilerAgentState::samplingHeapProfilerFlags, flags);
403423
profiler->StartSamplingHeapProfiler(
404424
static_cast<uint64_t>(samplingIntervalValue), 128,
405-
v8::HeapProfiler::kSamplingForceGC);
425+
static_cast<v8::HeapProfiler::SamplingFlags>(flags));
406426
return Response::Success();
407427
}
408428

deps/v8/src/inspector/v8-heap-profiler-agent-impl.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ class V8HeapProfilerAgentImpl : public protocol::HeapProfiler::Backend {
5656
Response getHeapObjectId(const String16& objectId,
5757
String16* heapSnapshotObjectId) override;
5858

59-
Response startSampling(Maybe<double> samplingInterval) override;
59+
Response startSampling(Maybe<double> samplingInterval,
60+
Maybe<bool> includeObjectsCollectedByMajorGC,
61+
Maybe<bool> includeObjectsCollectedByMinorGC) override;
6062
Response stopSampling(
6163
std::unique_ptr<protocol::HeapProfiler::SamplingHeapProfile>*) override;
6264
Response getSamplingProfile(

deps/v8/src/profiler/sampling-heap-profiler.cc

+13
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,19 @@ void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
9595
void SamplingHeapProfiler::OnWeakCallback(
9696
const WeakCallbackInfo<Sample>& data) {
9797
Sample* sample = data.GetParameter();
98+
Heap* heap = reinterpret_cast<Isolate*>(data.GetIsolate())->heap();
99+
bool is_minor_gc =
100+
heap->current_or_last_garbage_collector() == GarbageCollector::SCAVENGER;
101+
bool should_keep_sample =
102+
is_minor_gc
103+
? (sample->profiler->flags_ &
104+
v8::HeapProfiler::kSamplingIncludeObjectsCollectedByMinorGC)
105+
: (sample->profiler->flags_ &
106+
v8::HeapProfiler::kSamplingIncludeObjectsCollectedByMajorGC);
107+
if (should_keep_sample) {
108+
sample->global.Reset();
109+
return;
110+
}
98111
AllocationNode* node = sample->owner;
99112
DCHECK_GT(node->allocations_[sample->size], 0);
100113
node->allocations_[sample->size]--;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Checks sampling heap profiler methods.
2+
Retained size is less than 10KB: true
3+
Including major GC increases size: true
4+
Minor GC collected more: true
5+
Total allocation is greater than 100KB: true
6+
Successfully finished
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2017 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flags: --sampling-heap-profiler-suppress-randomness
6+
7+
(async function() {
8+
let {contextGroup, Protocol} = InspectorTest.start('Checks sampling heap profiler methods.');
9+
10+
contextGroup.addScript(`
11+
function generateTrash() {
12+
var arr = new Array(100);
13+
for (var i = 0; i < 3000; ++i) {
14+
var s = {a:i, b: new Array(100).fill(42)};
15+
arr[i % 100] = s;
16+
}
17+
return arr[30];
18+
}
19+
//# sourceURL=test.js`);
20+
21+
Protocol.HeapProfiler.enable();
22+
23+
await Protocol.HeapProfiler.startSampling({
24+
samplingInterval: 1e4,
25+
includeObjectsCollectedByMajorGC: false,
26+
includeObjectsCollectedByMinorGC: false,
27+
});
28+
await Protocol.Runtime.evaluate({ expression: 'generateTrash()' });
29+
const profile1 = await Protocol.HeapProfiler.stopSampling();
30+
const size1 = nodeSize(profile1.result.profile.head);
31+
InspectorTest.log('Retained size is less than 10KB:', size1 < 10000);
32+
33+
await Protocol.HeapProfiler.startSampling({
34+
samplingInterval: 100,
35+
includeObjectsCollectedByMajorGC: true,
36+
includeObjectsCollectedByMinorGC: false,
37+
});
38+
await Protocol.Runtime.evaluate({ expression: 'generateTrash()' });
39+
const profile2 = await Protocol.HeapProfiler.stopSampling();
40+
const size2 = nodeSize(profile2.result.profile.head);
41+
InspectorTest.log('Including major GC increases size:', size1 < size2);
42+
43+
await Protocol.HeapProfiler.startSampling({
44+
samplingInterval: 100,
45+
includeObjectsCollectedByMajorGC: true,
46+
includeObjectsCollectedByMinorGC: true,
47+
});
48+
await Protocol.Runtime.evaluate({ expression: 'generateTrash()' });
49+
const profile3 = await Protocol.HeapProfiler.stopSampling();
50+
const size3 = nodeSize(profile3.result.profile.head);
51+
InspectorTest.log('Minor GC collected more:', size3 > size2);
52+
InspectorTest.log('Total allocation is greater than 100KB:', size3 > 100000);
53+
54+
InspectorTest.log('Successfully finished');
55+
InspectorTest.completeTest();
56+
57+
function nodeSize(node) {
58+
return node.children.reduce((res, child) => res + nodeSize(child),
59+
node.callFrame.functionName === 'generateTrash' ? node.selfSize : 0);
60+
}
61+
})();

0 commit comments

Comments
 (0)