Skip to content

Commit f5892cd

Browse files
authored
Merge pull request #1554 from filipe-norte-red/wpe-2.46-gc-heap-snapshot-improvements
[wpe-2.46] Improve memory usage during GCHeap snapshot
2 parents 553216b + d7ccbc6 commit f5892cd

3 files changed

Lines changed: 119 additions & 12 deletions

File tree

Source/JavaScriptCore/heap/HeapSnapshotBuilder.cpp

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,81 @@
4040

4141
namespace JSC {
4242

43+
namespace {
44+
45+
class FileOutputStringBuilder {
46+
WTF_MAKE_NONCOPYABLE(FileOutputStringBuilder);
47+
48+
FileSystem::PlatformFileHandle m_fileHandle;
49+
StringBuilder m_builder;
50+
OverflowPolicy m_overflowPolicy;
51+
bool m_overflowed { false };
52+
53+
public:
54+
FileOutputStringBuilder(FileSystem::PlatformFileHandle fileHandle, OverflowPolicy overflowPolicy)
55+
: m_fileHandle(WTFMove(fileHandle))
56+
, m_builder(overflowPolicy)
57+
, m_overflowPolicy(overflowPolicy)
58+
{
59+
}
60+
61+
~FileOutputStringBuilder()
62+
{
63+
flush(true);
64+
}
65+
66+
template<typename... StringTypes> void append(StringTypes... fragment)
67+
{
68+
m_builder.append(fragment...);
69+
flush();
70+
}
71+
72+
void appendQuotedJSONString(const String& string)
73+
{
74+
m_builder.appendQuotedJSONString(string);
75+
flush();
76+
}
77+
78+
void flush(bool force = false)
79+
{
80+
constexpr size_t kBufferLengthLimit = 4 * WTF::KB;
81+
if ((force && !m_builder.isEmpty()) || m_builder.length() >= kBufferLengthLimit)
82+
{
83+
CString utf8String = m_builder.toStringPreserveCapacity().utf8();
84+
auto ret = FileSystem::writeToFile(m_fileHandle, utf8String.span());
85+
86+
if (ret < 0)
87+
{
88+
// A negative return value does not necessarily mean we ran out of disk space, but due the
89+
// lack of a better indicator, we'll assume that is the case
90+
m_overflowed = true;
91+
92+
if (m_overflowPolicy == OverflowPolicy::CrashOnOverflow)
93+
{
94+
WTFLogAlways("Forcing crash (policy based) due lack of disk space in heap snapshot builder");
95+
CRASH();
96+
}
97+
}
98+
99+
m_builder.clear();
100+
m_builder.reserveCapacity(kBufferLengthLimit);
101+
}
102+
}
103+
104+
bool hasOverflowed() const
105+
{
106+
return m_overflowed || m_builder.hasOverflowed();
107+
}
108+
109+
void clear()
110+
{
111+
m_builder.clear();
112+
FileSystem::truncateFile(m_fileHandle, 0);
113+
}
114+
};
115+
116+
} // namespace
117+
43118
WTF_MAKE_TZONE_ALLOCATED_IMPL(HeapSnapshotBuilder);
44119

45120
NodeIdentifier HeapSnapshotBuilder::nextAvailableObjectIdentifier = 1;
@@ -335,6 +410,20 @@ String HeapSnapshotBuilder::descriptionForCell(JSCell *cell) const
335410
}
336411

337412
String HeapSnapshotBuilder::json(Function<bool (const HeapSnapshotNode&)> allowNodeCallback)
413+
{
414+
StringBuilder json(m_overflowPolicy);
415+
writeJson(WTFMove(allowNodeCallback), json);
416+
return json.toString();
417+
}
418+
419+
void HeapSnapshotBuilder::writeJsonToFile(FileSystem::PlatformFileHandle fileHandle)
420+
{
421+
FileOutputStringBuilder json(fileHandle, m_overflowPolicy);
422+
writeJson([] (const HeapSnapshotNode&) { return true; }, json);
423+
}
424+
425+
template<typename FileOutputStringBuilder>
426+
void HeapSnapshotBuilder::writeJson(Function<bool (const HeapSnapshotNode&)>&& allowNodeCallback, FileOutputStringBuilder &json)
338427
{
339428
VM& vm = m_profiler.vm();
340429
DeferGCForAWhile deferGC(vm);
@@ -356,8 +445,6 @@ String HeapSnapshotBuilder::json(Function<bool (const HeapSnapshotNode&)> allowN
356445
UncheckedKeyHashMap<UniquedStringImpl*, unsigned> edgeNameIndexes;
357446
unsigned nextEdgeNameIndex = 0;
358447

359-
StringBuilder json(m_overflowPolicy);
360-
361448
auto appendNodeJSON = [&] (const HeapSnapshotNode& node) {
362449
// Let the client decide if they want to allow or disallow certain nodes.
363450
if (!allowNodeCallback(node))
@@ -633,9 +720,9 @@ String HeapSnapshotBuilder::json(Function<bool (const HeapSnapshotNode&)> allowN
633720
json.append('}');
634721
if (json.hasOverflowed()) {
635722
m_hasOverflowed = true;
636-
return { };
723+
724+
json.clear();
637725
}
638-
return json.toString();
639726
}
640727

641728
} // namespace JSC

Source/JavaScriptCore/heap/HeapSnapshotBuilder.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <wtf/OverflowPolicy.h>
3434
#include <wtf/TZoneMalloc.h>
3535
#include <wtf/Vector.h>
36+
#include <wtf/FileSystem.h>
3637

3738
namespace JSC {
3839

@@ -132,6 +133,7 @@ class JS_EXPORT_PRIVATE HeapSnapshotBuilder final : public HeapAnalyzer {
132133

133134
String json();
134135
String json(Function<bool (const HeapSnapshotNode&)> allowNodeCallback);
136+
void writeJsonToFile(FileSystem::PlatformFileHandle fileHandle);
135137

136138
bool hasOverflowed() const { return m_hasOverflowed; }
137139

@@ -145,6 +147,9 @@ class JS_EXPORT_PRIVATE HeapSnapshotBuilder final : public HeapAnalyzer {
145147

146148
String descriptionForCell(JSCell*) const;
147149

150+
template<typename OutputStringBuilder>
151+
void writeJson(Function<bool (const HeapSnapshotNode&)>&& allowNodeCallback, OutputStringBuilder &json);
152+
148153
struct RootData {
149154
ASCIILiteral reachabilityFromOpaqueRootReasons;
150155
RootMarkReason markReason { RootMarkReason::None };

Source/WebCore/bindings/js/GCController.cpp

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,28 +141,43 @@ void GCController::deleteAllLinkedCode(DeleteAllCodeEffort effort)
141141

142142
void GCController::dumpHeapForVM(VM& vm)
143143
{
144-
auto [tempFilePath, fileHandle] = FileSystem::openTemporaryFile("GCHeap"_s);
144+
static const char* sHeapDumpDirOverride = []() {
145+
return getenv("WEBKIT_HEAP_SNAPSHOT_DIR");
146+
}();
147+
148+
String tempFilePath;
149+
FileSystem::PlatformFileHandle fileHandle;
150+
151+
if (sHeapDumpDirOverride) {
152+
char buf[32];
153+
time_t now = time(nullptr);
154+
struct tm* tstruct = localtime(&now);
155+
std::strftime(buf, sizeof(buf), "%Y%m%d_%H%M%S", tstruct);
156+
157+
tempFilePath = FileSystem::fileSystemRepresentation(
158+
FileSystem::pathByAppendingComponent(WTF::String::fromUTF8(sHeapDumpDirOverride),
159+
makeString("GCHeap_"_s, WTF::String::fromUTF8(buf)))).span();
160+
fileHandle = FileSystem::openFile(tempFilePath, FileSystem::FileOpenMode::ReadWrite);
161+
} else {
162+
std::tie(tempFilePath, fileHandle) = FileSystem::openTemporaryFile("GCHeap"_s);
163+
}
164+
145165
if (!FileSystem::isHandleValid(fileHandle)) {
146-
WTFLogAlways("Dumping GC heap failed to open temporary file");
166+
WTFLogAlways("Dumping GC heap failed to open temporary file: %s", tempFilePath.utf8().data());
147167
return;
148168
}
149169

150170
JSLockHolder lock(vm);
151171
sanitizeStackForVM(vm);
152172

153-
String jsonData;
154173
{
155174
DeferGCForAWhile deferGC(vm); // Prevent concurrent GC from interfering with the full GC that the snapshot does.
156175

157176
HeapSnapshotBuilder snapshotBuilder(vm.ensureHeapProfiler(), HeapSnapshotBuilder::SnapshotType::GCDebuggingSnapshot);
158177
snapshotBuilder.buildSnapshot();
159-
160-
jsonData = snapshotBuilder.json();
178+
snapshotBuilder.writeJsonToFile(fileHandle);
161179
}
162180

163-
CString utf8String = jsonData.utf8();
164-
165-
FileSystem::writeToFile(fileHandle, utf8String.span());
166181
FileSystem::closeFile(fileHandle);
167182
WTFLogAlways("Dumped GC heap to %s%s", tempFilePath.utf8().data(), isMainThread() ? ""_s : " for Worker");
168183
}

0 commit comments

Comments
 (0)