1 // Copyright 2016 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 #include "src/inspector/v8-heap-profiler-agent-impl.h"
6
7 #include "include/v8-inspector.h"
8 #include "include/v8-profiler.h"
9 #include "include/v8-version.h"
10 #include "src/base/platform/mutex.h"
11 #include "src/inspector/injected-script.h"
12 #include "src/inspector/inspected-context.h"
13 #include "src/inspector/protocol/Protocol.h"
14 #include "src/inspector/string-util.h"
15 #include "src/inspector/v8-debugger.h"
16 #include "src/inspector/v8-inspector-impl.h"
17 #include "src/inspector/v8-inspector-session-impl.h"
18
19 namespace v8_inspector {
20
21 namespace {
22
23 namespace HeapProfilerAgentState {
24 static const char heapProfilerEnabled[] = "heapProfilerEnabled";
25 static const char heapObjectsTrackingEnabled[] = "heapObjectsTrackingEnabled";
26 static const char allocationTrackingEnabled[] = "allocationTrackingEnabled";
27 static const char samplingHeapProfilerEnabled[] = "samplingHeapProfilerEnabled";
28 static const char samplingHeapProfilerInterval[] =
29 "samplingHeapProfilerInterval";
30 } // namespace HeapProfilerAgentState
31
32 class HeapSnapshotProgress final : public v8::ActivityControl {
33 public:
HeapSnapshotProgress(protocol::HeapProfiler::Frontend * frontend)34 explicit HeapSnapshotProgress(protocol::HeapProfiler::Frontend* frontend)
35 : m_frontend(frontend) {}
ReportProgressValue(int done,int total)36 ControlOption ReportProgressValue(int done, int total) override {
37 m_frontend->reportHeapSnapshotProgress(done, total,
38 protocol::Maybe<bool>());
39 if (done >= total) {
40 m_frontend->reportHeapSnapshotProgress(total, total, true);
41 }
42 m_frontend->flush();
43 return kContinue;
44 }
45
46 private:
47 protocol::HeapProfiler::Frontend* m_frontend;
48 };
49
50 class GlobalObjectNameResolver final
51 : public v8::HeapProfiler::ObjectNameResolver {
52 public:
GlobalObjectNameResolver(V8InspectorSessionImpl * session)53 explicit GlobalObjectNameResolver(V8InspectorSessionImpl* session)
54 : m_offset(0), m_strings(10000), m_session(session) {}
55
GetName(v8::Local<v8::Object> object)56 const char* GetName(v8::Local<v8::Object> object) override {
57 InspectedContext* context = m_session->inspector()->getContext(
58 m_session->contextGroupId(),
59 InspectedContext::contextId(object->CreationContext()));
60 if (!context) return "";
61 String16 name = context->origin();
62 size_t length = name.length();
63 if (m_offset + length + 1 >= m_strings.size()) return "";
64 for (size_t i = 0; i < length; ++i) {
65 UChar ch = name[i];
66 m_strings[m_offset + i] = ch > 0xFF ? '?' : static_cast<char>(ch);
67 }
68 m_strings[m_offset + length] = '\0';
69 char* result = &*m_strings.begin() + m_offset;
70 m_offset += length + 1;
71 return result;
72 }
73
74 private:
75 size_t m_offset;
76 std::vector<char> m_strings;
77 V8InspectorSessionImpl* m_session;
78 };
79
80 class HeapSnapshotOutputStream final : public v8::OutputStream {
81 public:
HeapSnapshotOutputStream(protocol::HeapProfiler::Frontend * frontend)82 explicit HeapSnapshotOutputStream(protocol::HeapProfiler::Frontend* frontend)
83 : m_frontend(frontend) {}
EndOfStream()84 void EndOfStream() override {}
GetChunkSize()85 int GetChunkSize() override { return 102400; }
WriteAsciiChunk(char * data,int size)86 WriteResult WriteAsciiChunk(char* data, int size) override {
87 m_frontend->addHeapSnapshotChunk(String16(data, size));
88 m_frontend->flush();
89 return kContinue;
90 }
91
92 private:
93 protocol::HeapProfiler::Frontend* m_frontend;
94 };
95
objectByHeapObjectId(v8::Isolate * isolate,int id)96 v8::Local<v8::Object> objectByHeapObjectId(v8::Isolate* isolate, int id) {
97 v8::HeapProfiler* profiler = isolate->GetHeapProfiler();
98 v8::Local<v8::Value> value = profiler->FindObjectById(id);
99 if (value.IsEmpty() || !value->IsObject()) return v8::Local<v8::Object>();
100 return value.As<v8::Object>();
101 }
102
103 class InspectableHeapObject final : public V8InspectorSession::Inspectable {
104 public:
InspectableHeapObject(int heapObjectId)105 explicit InspectableHeapObject(int heapObjectId)
106 : m_heapObjectId(heapObjectId) {}
get(v8::Local<v8::Context> context)107 v8::Local<v8::Value> get(v8::Local<v8::Context> context) override {
108 return objectByHeapObjectId(context->GetIsolate(), m_heapObjectId);
109 }
110
111 private:
112 int m_heapObjectId;
113 };
114
115 class HeapStatsStream final : public v8::OutputStream {
116 public:
HeapStatsStream(protocol::HeapProfiler::Frontend * frontend)117 explicit HeapStatsStream(protocol::HeapProfiler::Frontend* frontend)
118 : m_frontend(frontend) {}
119
EndOfStream()120 void EndOfStream() override {}
121
WriteAsciiChunk(char * data,int size)122 WriteResult WriteAsciiChunk(char* data, int size) override {
123 DCHECK(false);
124 return kAbort;
125 }
126
WriteHeapStatsChunk(v8::HeapStatsUpdate * updateData,int count)127 WriteResult WriteHeapStatsChunk(v8::HeapStatsUpdate* updateData,
128 int count) override {
129 DCHECK_GT(count, 0);
130 auto statsDiff = std::make_unique<protocol::Array<int>>();
131 for (int i = 0; i < count; ++i) {
132 statsDiff->emplace_back(updateData[i].index);
133 statsDiff->emplace_back(updateData[i].count);
134 statsDiff->emplace_back(updateData[i].size);
135 }
136 m_frontend->heapStatsUpdate(std::move(statsDiff));
137 return kContinue;
138 }
139
140 private:
141 protocol::HeapProfiler::Frontend* m_frontend;
142 };
143
144 } // namespace
145
146 struct V8HeapProfilerAgentImpl::AsyncGC {
147 v8::base::Mutex m_mutex;
148 bool m_canceled = false;
149 bool m_pending = false;
150 std::vector<std::unique_ptr<CollectGarbageCallback>> m_pending_callbacks;
151 };
152
153 class V8HeapProfilerAgentImpl::GCTask : public v8::Task {
154 public:
GCTask(v8::Isolate * isolate,std::shared_ptr<AsyncGC> async_gc)155 GCTask(v8::Isolate* isolate, std::shared_ptr<AsyncGC> async_gc)
156 : m_isolate(isolate), m_async_gc(async_gc) {}
157
Run()158 void Run() override {
159 std::shared_ptr<AsyncGC> async_gc = m_async_gc.lock();
160 if (!async_gc) return;
161 v8::base::MutexGuard lock(&async_gc->m_mutex);
162 if (async_gc->m_canceled) return;
163 v8::debug::ForceGarbageCollection(
164 m_isolate, v8::EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
165 for (auto& callback : async_gc->m_pending_callbacks) {
166 callback->sendSuccess();
167 }
168 async_gc->m_pending_callbacks.clear();
169 }
170
171 private:
172 v8::Isolate* m_isolate;
173 std::weak_ptr<AsyncGC> m_async_gc;
174 };
175
V8HeapProfilerAgentImpl(V8InspectorSessionImpl * session,protocol::FrontendChannel * frontendChannel,protocol::DictionaryValue * state)176 V8HeapProfilerAgentImpl::V8HeapProfilerAgentImpl(
177 V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel,
178 protocol::DictionaryValue* state)
179 : m_session(session),
180 m_isolate(session->inspector()->isolate()),
181 m_frontend(frontendChannel),
182 m_state(state),
183 m_hasTimer(false),
184 m_async_gc(std::make_shared<AsyncGC>()) {}
185
~V8HeapProfilerAgentImpl()186 V8HeapProfilerAgentImpl::~V8HeapProfilerAgentImpl() {
187 v8::base::MutexGuard lock(&m_async_gc->m_mutex);
188 m_async_gc->m_canceled = true;
189 m_async_gc->m_pending_callbacks.clear();
190 }
191
restore()192 void V8HeapProfilerAgentImpl::restore() {
193 if (m_state->booleanProperty(HeapProfilerAgentState::heapProfilerEnabled,
194 false))
195 m_frontend.resetProfiles();
196 if (m_state->booleanProperty(
197 HeapProfilerAgentState::heapObjectsTrackingEnabled, false))
198 startTrackingHeapObjectsInternal(m_state->booleanProperty(
199 HeapProfilerAgentState::allocationTrackingEnabled, false));
200 if (m_state->booleanProperty(
201 HeapProfilerAgentState::samplingHeapProfilerEnabled, false)) {
202 double samplingInterval = m_state->doubleProperty(
203 HeapProfilerAgentState::samplingHeapProfilerInterval, -1);
204 DCHECK_GE(samplingInterval, 0);
205 startSampling(Maybe<double>(samplingInterval));
206 }
207 }
208
collectGarbage(std::unique_ptr<CollectGarbageCallback> callback)209 void V8HeapProfilerAgentImpl::collectGarbage(
210 std::unique_ptr<CollectGarbageCallback> callback) {
211 v8::base::MutexGuard lock(&m_async_gc->m_mutex);
212 m_async_gc->m_pending_callbacks.push_back(std::move(callback));
213 if (!m_async_gc->m_pending) {
214 v8::debug::GetCurrentPlatform()
215 ->GetForegroundTaskRunner(m_isolate)
216 ->PostNonNestableTask(std::make_unique<GCTask>(m_isolate, m_async_gc));
217 }
218 }
219
startTrackingHeapObjects(Maybe<bool> trackAllocations)220 Response V8HeapProfilerAgentImpl::startTrackingHeapObjects(
221 Maybe<bool> trackAllocations) {
222 m_state->setBoolean(HeapProfilerAgentState::heapObjectsTrackingEnabled, true);
223 bool allocationTrackingEnabled = trackAllocations.fromMaybe(false);
224 m_state->setBoolean(HeapProfilerAgentState::allocationTrackingEnabled,
225 allocationTrackingEnabled);
226 startTrackingHeapObjectsInternal(allocationTrackingEnabled);
227 return Response::Success();
228 }
229
stopTrackingHeapObjects(Maybe<bool> reportProgress,Maybe<bool> treatGlobalObjectsAsRoots)230 Response V8HeapProfilerAgentImpl::stopTrackingHeapObjects(
231 Maybe<bool> reportProgress, Maybe<bool> treatGlobalObjectsAsRoots) {
232 requestHeapStatsUpdate();
233 takeHeapSnapshot(std::move(reportProgress),
234 std::move(treatGlobalObjectsAsRoots));
235 stopTrackingHeapObjectsInternal();
236 return Response::Success();
237 }
238
enable()239 Response V8HeapProfilerAgentImpl::enable() {
240 m_state->setBoolean(HeapProfilerAgentState::heapProfilerEnabled, true);
241 return Response::Success();
242 }
243
disable()244 Response V8HeapProfilerAgentImpl::disable() {
245 stopTrackingHeapObjectsInternal();
246 if (m_state->booleanProperty(
247 HeapProfilerAgentState::samplingHeapProfilerEnabled, false)) {
248 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
249 if (profiler) profiler->StopSamplingHeapProfiler();
250 }
251 m_isolate->GetHeapProfiler()->ClearObjectIds();
252 m_state->setBoolean(HeapProfilerAgentState::heapProfilerEnabled, false);
253 return Response::Success();
254 }
255
takeHeapSnapshot(Maybe<bool> reportProgress,Maybe<bool> treatGlobalObjectsAsRoots)256 Response V8HeapProfilerAgentImpl::takeHeapSnapshot(
257 Maybe<bool> reportProgress, Maybe<bool> treatGlobalObjectsAsRoots) {
258 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
259 if (!profiler) return Response::ServerError("Cannot access v8 heap profiler");
260 std::unique_ptr<HeapSnapshotProgress> progress;
261 if (reportProgress.fromMaybe(false))
262 progress.reset(new HeapSnapshotProgress(&m_frontend));
263
264 GlobalObjectNameResolver resolver(m_session);
265 const v8::HeapSnapshot* snapshot = profiler->TakeHeapSnapshot(
266 progress.get(), &resolver, treatGlobalObjectsAsRoots.fromMaybe(true));
267 if (!snapshot) return Response::ServerError("Failed to take heap snapshot");
268 HeapSnapshotOutputStream stream(&m_frontend);
269 snapshot->Serialize(&stream);
270 const_cast<v8::HeapSnapshot*>(snapshot)->Delete();
271 return Response::Success();
272 }
273
getObjectByHeapObjectId(const String16 & heapSnapshotObjectId,Maybe<String16> objectGroup,std::unique_ptr<protocol::Runtime::RemoteObject> * result)274 Response V8HeapProfilerAgentImpl::getObjectByHeapObjectId(
275 const String16& heapSnapshotObjectId, Maybe<String16> objectGroup,
276 std::unique_ptr<protocol::Runtime::RemoteObject>* result) {
277 bool ok;
278 int id = heapSnapshotObjectId.toInteger(&ok);
279 if (!ok) return Response::ServerError("Invalid heap snapshot object id");
280
281 v8::HandleScope handles(m_isolate);
282 v8::Local<v8::Object> heapObject = objectByHeapObjectId(m_isolate, id);
283 if (heapObject.IsEmpty())
284 return Response::ServerError("Object is not available");
285
286 if (!m_session->inspector()->client()->isInspectableHeapObject(heapObject))
287 return Response::ServerError("Object is not available");
288
289 *result = m_session->wrapObject(heapObject->CreationContext(), heapObject,
290 objectGroup.fromMaybe(""), false);
291 if (!*result) return Response::ServerError("Object is not available");
292 return Response::Success();
293 }
294
addInspectedHeapObject(const String16 & inspectedHeapObjectId)295 Response V8HeapProfilerAgentImpl::addInspectedHeapObject(
296 const String16& inspectedHeapObjectId) {
297 bool ok;
298 int id = inspectedHeapObjectId.toInteger(&ok);
299 if (!ok) return Response::ServerError("Invalid heap snapshot object id");
300
301 v8::HandleScope handles(m_isolate);
302 v8::Local<v8::Object> heapObject = objectByHeapObjectId(m_isolate, id);
303 if (heapObject.IsEmpty())
304 return Response::ServerError("Object is not available");
305
306 if (!m_session->inspector()->client()->isInspectableHeapObject(heapObject))
307 return Response::ServerError("Object is not available");
308 m_session->addInspectedObject(
309 std::unique_ptr<InspectableHeapObject>(new InspectableHeapObject(id)));
310 return Response::Success();
311 }
312
getHeapObjectId(const String16 & objectId,String16 * heapSnapshotObjectId)313 Response V8HeapProfilerAgentImpl::getHeapObjectId(
314 const String16& objectId, String16* heapSnapshotObjectId) {
315 v8::HandleScope handles(m_isolate);
316 v8::Local<v8::Value> value;
317 v8::Local<v8::Context> context;
318 Response response =
319 m_session->unwrapObject(objectId, &value, &context, nullptr);
320 if (!response.IsSuccess()) return response;
321 if (value->IsUndefined()) return Response::InternalError();
322
323 v8::SnapshotObjectId id = m_isolate->GetHeapProfiler()->GetObjectId(value);
324 *heapSnapshotObjectId = String16::fromInteger(static_cast<size_t>(id));
325 return Response::Success();
326 }
327
requestHeapStatsUpdate()328 void V8HeapProfilerAgentImpl::requestHeapStatsUpdate() {
329 HeapStatsStream stream(&m_frontend);
330 v8::SnapshotObjectId lastSeenObjectId =
331 m_isolate->GetHeapProfiler()->GetHeapStats(&stream);
332 m_frontend.lastSeenObjectId(
333 lastSeenObjectId, m_session->inspector()->client()->currentTimeMS());
334 }
335
336 // static
onTimer(void * data)337 void V8HeapProfilerAgentImpl::onTimer(void* data) {
338 reinterpret_cast<V8HeapProfilerAgentImpl*>(data)->requestHeapStatsUpdate();
339 }
340
startTrackingHeapObjectsInternal(bool trackAllocations)341 void V8HeapProfilerAgentImpl::startTrackingHeapObjectsInternal(
342 bool trackAllocations) {
343 m_isolate->GetHeapProfiler()->StartTrackingHeapObjects(trackAllocations);
344 if (!m_hasTimer) {
345 m_hasTimer = true;
346 m_session->inspector()->client()->startRepeatingTimer(
347 0.05, &V8HeapProfilerAgentImpl::onTimer, reinterpret_cast<void*>(this));
348 }
349 }
350
stopTrackingHeapObjectsInternal()351 void V8HeapProfilerAgentImpl::stopTrackingHeapObjectsInternal() {
352 if (m_hasTimer) {
353 m_session->inspector()->client()->cancelTimer(
354 reinterpret_cast<void*>(this));
355 m_hasTimer = false;
356 }
357 m_isolate->GetHeapProfiler()->StopTrackingHeapObjects();
358 m_state->setBoolean(HeapProfilerAgentState::heapObjectsTrackingEnabled,
359 false);
360 m_state->setBoolean(HeapProfilerAgentState::allocationTrackingEnabled, false);
361 }
362
startSampling(Maybe<double> samplingInterval)363 Response V8HeapProfilerAgentImpl::startSampling(
364 Maybe<double> samplingInterval) {
365 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
366 if (!profiler) return Response::ServerError("Cannot access v8 heap profiler");
367 const unsigned defaultSamplingInterval = 1 << 15;
368 double samplingIntervalValue =
369 samplingInterval.fromMaybe(defaultSamplingInterval);
370 m_state->setDouble(HeapProfilerAgentState::samplingHeapProfilerInterval,
371 samplingIntervalValue);
372 m_state->setBoolean(HeapProfilerAgentState::samplingHeapProfilerEnabled,
373 true);
374 profiler->StartSamplingHeapProfiler(
375 static_cast<uint64_t>(samplingIntervalValue), 128,
376 v8::HeapProfiler::kSamplingForceGC);
377 return Response::Success();
378 }
379
380 namespace {
381 std::unique_ptr<protocol::HeapProfiler::SamplingHeapProfileNode>
buildSampingHeapProfileNode(v8::Isolate * isolate,const v8::AllocationProfile::Node * node)382 buildSampingHeapProfileNode(v8::Isolate* isolate,
383 const v8::AllocationProfile::Node* node) {
384 auto children = std::make_unique<
385 protocol::Array<protocol::HeapProfiler::SamplingHeapProfileNode>>();
386 for (const auto* child : node->children)
387 children->emplace_back(buildSampingHeapProfileNode(isolate, child));
388 size_t selfSize = 0;
389 for (const auto& allocation : node->allocations)
390 selfSize += allocation.size * allocation.count;
391 std::unique_ptr<protocol::Runtime::CallFrame> callFrame =
392 protocol::Runtime::CallFrame::create()
393 .setFunctionName(toProtocolString(isolate, node->name))
394 .setScriptId(String16::fromInteger(node->script_id))
395 .setUrl(toProtocolString(isolate, node->script_name))
396 .setLineNumber(node->line_number - 1)
397 .setColumnNumber(node->column_number - 1)
398 .build();
399 std::unique_ptr<protocol::HeapProfiler::SamplingHeapProfileNode> result =
400 protocol::HeapProfiler::SamplingHeapProfileNode::create()
401 .setCallFrame(std::move(callFrame))
402 .setSelfSize(selfSize)
403 .setChildren(std::move(children))
404 .setId(node->node_id)
405 .build();
406 return result;
407 }
408 } // namespace
409
stopSampling(std::unique_ptr<protocol::HeapProfiler::SamplingHeapProfile> * profile)410 Response V8HeapProfilerAgentImpl::stopSampling(
411 std::unique_ptr<protocol::HeapProfiler::SamplingHeapProfile>* profile) {
412 Response result = getSamplingProfile(profile);
413 if (result.IsSuccess()) {
414 m_isolate->GetHeapProfiler()->StopSamplingHeapProfiler();
415 m_state->setBoolean(HeapProfilerAgentState::samplingHeapProfilerEnabled,
416 false);
417 }
418 return result;
419 }
420
getSamplingProfile(std::unique_ptr<protocol::HeapProfiler::SamplingHeapProfile> * profile)421 Response V8HeapProfilerAgentImpl::getSamplingProfile(
422 std::unique_ptr<protocol::HeapProfiler::SamplingHeapProfile>* profile) {
423 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
424 // Need a scope as v8::AllocationProfile contains Local handles.
425 v8::HandleScope scope(m_isolate);
426 std::unique_ptr<v8::AllocationProfile> v8Profile(
427 profiler->GetAllocationProfile());
428 if (!v8Profile)
429 return Response::ServerError("V8 sampling heap profiler was not started.");
430 v8::AllocationProfile::Node* root = v8Profile->GetRootNode();
431 auto samples = std::make_unique<
432 protocol::Array<protocol::HeapProfiler::SamplingHeapProfileSample>>();
433 for (const auto& sample : v8Profile->GetSamples()) {
434 samples->emplace_back(
435 protocol::HeapProfiler::SamplingHeapProfileSample::create()
436 .setSize(sample.size * sample.count)
437 .setNodeId(sample.node_id)
438 .setOrdinal(static_cast<double>(sample.sample_id))
439 .build());
440 }
441 *profile = protocol::HeapProfiler::SamplingHeapProfile::create()
442 .setHead(buildSampingHeapProfileNode(m_isolate, root))
443 .setSamples(std::move(samples))
444 .build();
445 return Response::Success();
446 }
447
448 } // namespace v8_inspector
449