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 "src/inspector/injected-script.h"
8 #include "src/inspector/inspected-context.h"
9 #include "src/inspector/protocol/Protocol.h"
10 #include "src/inspector/string-util.h"
11 #include "src/inspector/v8-debugger.h"
12 #include "src/inspector/v8-inspector-impl.h"
13 #include "src/inspector/v8-inspector-session-impl.h"
14
15 #include "include/v8-inspector.h"
16 #include "include/v8-profiler.h"
17 #include "include/v8-version.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 }
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 std::unique_ptr<protocol::Array<int>> statsDiff =
131 protocol::Array<int>::create();
132 for (int i = 0; i < count; ++i) {
133 statsDiff->addItem(updateData[i].index);
134 statsDiff->addItem(updateData[i].count);
135 statsDiff->addItem(updateData[i].size);
136 }
137 m_frontend->heapStatsUpdate(std::move(statsDiff));
138 return kContinue;
139 }
140
141 private:
142 protocol::HeapProfiler::Frontend* m_frontend;
143 };
144
145 } // namespace
146
V8HeapProfilerAgentImpl(V8InspectorSessionImpl * session,protocol::FrontendChannel * frontendChannel,protocol::DictionaryValue * state)147 V8HeapProfilerAgentImpl::V8HeapProfilerAgentImpl(
148 V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel,
149 protocol::DictionaryValue* state)
150 : m_session(session),
151 m_isolate(session->inspector()->isolate()),
152 m_frontend(frontendChannel),
153 m_state(state),
154 m_hasTimer(false) {}
155
~V8HeapProfilerAgentImpl()156 V8HeapProfilerAgentImpl::~V8HeapProfilerAgentImpl() {}
157
restore()158 void V8HeapProfilerAgentImpl::restore() {
159 if (m_state->booleanProperty(HeapProfilerAgentState::heapProfilerEnabled,
160 false))
161 m_frontend.resetProfiles();
162 if (m_state->booleanProperty(
163 HeapProfilerAgentState::heapObjectsTrackingEnabled, false))
164 startTrackingHeapObjectsInternal(m_state->booleanProperty(
165 HeapProfilerAgentState::allocationTrackingEnabled, false));
166 if (m_state->booleanProperty(
167 HeapProfilerAgentState::samplingHeapProfilerEnabled, false)) {
168 double samplingInterval = m_state->doubleProperty(
169 HeapProfilerAgentState::samplingHeapProfilerInterval, -1);
170 DCHECK_GE(samplingInterval, 0);
171 startSampling(Maybe<double>(samplingInterval));
172 }
173 }
174
collectGarbage()175 Response V8HeapProfilerAgentImpl::collectGarbage() {
176 m_isolate->LowMemoryNotification();
177 return Response::OK();
178 }
179
startTrackingHeapObjects(Maybe<bool> trackAllocations)180 Response V8HeapProfilerAgentImpl::startTrackingHeapObjects(
181 Maybe<bool> trackAllocations) {
182 m_state->setBoolean(HeapProfilerAgentState::heapObjectsTrackingEnabled, true);
183 bool allocationTrackingEnabled = trackAllocations.fromMaybe(false);
184 m_state->setBoolean(HeapProfilerAgentState::allocationTrackingEnabled,
185 allocationTrackingEnabled);
186 startTrackingHeapObjectsInternal(allocationTrackingEnabled);
187 return Response::OK();
188 }
189
stopTrackingHeapObjects(Maybe<bool> reportProgress)190 Response V8HeapProfilerAgentImpl::stopTrackingHeapObjects(
191 Maybe<bool> reportProgress) {
192 requestHeapStatsUpdate();
193 takeHeapSnapshot(std::move(reportProgress));
194 stopTrackingHeapObjectsInternal();
195 return Response::OK();
196 }
197
enable()198 Response V8HeapProfilerAgentImpl::enable() {
199 m_state->setBoolean(HeapProfilerAgentState::heapProfilerEnabled, true);
200 return Response::OK();
201 }
202
disable()203 Response V8HeapProfilerAgentImpl::disable() {
204 stopTrackingHeapObjectsInternal();
205 if (m_state->booleanProperty(
206 HeapProfilerAgentState::samplingHeapProfilerEnabled, false)) {
207 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
208 if (profiler) profiler->StopSamplingHeapProfiler();
209 }
210 m_isolate->GetHeapProfiler()->ClearObjectIds();
211 m_state->setBoolean(HeapProfilerAgentState::heapProfilerEnabled, false);
212 return Response::OK();
213 }
214
takeHeapSnapshot(Maybe<bool> reportProgress)215 Response V8HeapProfilerAgentImpl::takeHeapSnapshot(Maybe<bool> reportProgress) {
216 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
217 if (!profiler) return Response::Error("Cannot access v8 heap profiler");
218 std::unique_ptr<HeapSnapshotProgress> progress;
219 if (reportProgress.fromMaybe(false))
220 progress.reset(new HeapSnapshotProgress(&m_frontend));
221
222 GlobalObjectNameResolver resolver(m_session);
223 const v8::HeapSnapshot* snapshot =
224 profiler->TakeHeapSnapshot(progress.get(), &resolver);
225 if (!snapshot) return Response::Error("Failed to take heap snapshot");
226 HeapSnapshotOutputStream stream(&m_frontend);
227 snapshot->Serialize(&stream);
228 const_cast<v8::HeapSnapshot*>(snapshot)->Delete();
229 return Response::OK();
230 }
231
getObjectByHeapObjectId(const String16 & heapSnapshotObjectId,Maybe<String16> objectGroup,std::unique_ptr<protocol::Runtime::RemoteObject> * result)232 Response V8HeapProfilerAgentImpl::getObjectByHeapObjectId(
233 const String16& heapSnapshotObjectId, Maybe<String16> objectGroup,
234 std::unique_ptr<protocol::Runtime::RemoteObject>* result) {
235 bool ok;
236 int id = heapSnapshotObjectId.toInteger(&ok);
237 if (!ok) return Response::Error("Invalid heap snapshot object id");
238
239 v8::HandleScope handles(m_isolate);
240 v8::Local<v8::Object> heapObject = objectByHeapObjectId(m_isolate, id);
241 if (heapObject.IsEmpty()) return Response::Error("Object is not available");
242
243 if (!m_session->inspector()->client()->isInspectableHeapObject(heapObject))
244 return Response::Error("Object is not available");
245
246 *result = m_session->wrapObject(heapObject->CreationContext(), heapObject,
247 objectGroup.fromMaybe(""), false);
248 if (!*result) return Response::Error("Object is not available");
249 return Response::OK();
250 }
251
addInspectedHeapObject(const String16 & inspectedHeapObjectId)252 Response V8HeapProfilerAgentImpl::addInspectedHeapObject(
253 const String16& inspectedHeapObjectId) {
254 bool ok;
255 int id = inspectedHeapObjectId.toInteger(&ok);
256 if (!ok) return Response::Error("Invalid heap snapshot object id");
257
258 v8::HandleScope handles(m_isolate);
259 v8::Local<v8::Object> heapObject = objectByHeapObjectId(m_isolate, id);
260 if (heapObject.IsEmpty()) return Response::Error("Object is not available");
261
262 if (!m_session->inspector()->client()->isInspectableHeapObject(heapObject))
263 return Response::Error("Object is not available");
264 m_session->addInspectedObject(
265 std::unique_ptr<InspectableHeapObject>(new InspectableHeapObject(id)));
266 return Response::OK();
267 }
268
getHeapObjectId(const String16 & objectId,String16 * heapSnapshotObjectId)269 Response V8HeapProfilerAgentImpl::getHeapObjectId(
270 const String16& objectId, String16* heapSnapshotObjectId) {
271 v8::HandleScope handles(m_isolate);
272 v8::Local<v8::Value> value;
273 v8::Local<v8::Context> context;
274 Response response =
275 m_session->unwrapObject(objectId, &value, &context, nullptr);
276 if (!response.isSuccess()) return response;
277 if (value->IsUndefined()) return Response::InternalError();
278
279 v8::SnapshotObjectId id = m_isolate->GetHeapProfiler()->GetObjectId(value);
280 *heapSnapshotObjectId = String16::fromInteger(static_cast<size_t>(id));
281 return Response::OK();
282 }
283
requestHeapStatsUpdate()284 void V8HeapProfilerAgentImpl::requestHeapStatsUpdate() {
285 HeapStatsStream stream(&m_frontend);
286 v8::SnapshotObjectId lastSeenObjectId =
287 m_isolate->GetHeapProfiler()->GetHeapStats(&stream);
288 m_frontend.lastSeenObjectId(
289 lastSeenObjectId, m_session->inspector()->client()->currentTimeMS());
290 }
291
292 // static
onTimer(void * data)293 void V8HeapProfilerAgentImpl::onTimer(void* data) {
294 reinterpret_cast<V8HeapProfilerAgentImpl*>(data)->requestHeapStatsUpdate();
295 }
296
startTrackingHeapObjectsInternal(bool trackAllocations)297 void V8HeapProfilerAgentImpl::startTrackingHeapObjectsInternal(
298 bool trackAllocations) {
299 m_isolate->GetHeapProfiler()->StartTrackingHeapObjects(trackAllocations);
300 if (!m_hasTimer) {
301 m_hasTimer = true;
302 m_session->inspector()->client()->startRepeatingTimer(
303 0.05, &V8HeapProfilerAgentImpl::onTimer, reinterpret_cast<void*>(this));
304 }
305 }
306
stopTrackingHeapObjectsInternal()307 void V8HeapProfilerAgentImpl::stopTrackingHeapObjectsInternal() {
308 if (m_hasTimer) {
309 m_session->inspector()->client()->cancelTimer(
310 reinterpret_cast<void*>(this));
311 m_hasTimer = false;
312 }
313 m_isolate->GetHeapProfiler()->StopTrackingHeapObjects();
314 m_state->setBoolean(HeapProfilerAgentState::heapObjectsTrackingEnabled,
315 false);
316 m_state->setBoolean(HeapProfilerAgentState::allocationTrackingEnabled, false);
317 }
318
startSampling(Maybe<double> samplingInterval)319 Response V8HeapProfilerAgentImpl::startSampling(
320 Maybe<double> samplingInterval) {
321 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
322 if (!profiler) return Response::Error("Cannot access v8 heap profiler");
323 const unsigned defaultSamplingInterval = 1 << 15;
324 double samplingIntervalValue =
325 samplingInterval.fromMaybe(defaultSamplingInterval);
326 m_state->setDouble(HeapProfilerAgentState::samplingHeapProfilerInterval,
327 samplingIntervalValue);
328 m_state->setBoolean(HeapProfilerAgentState::samplingHeapProfilerEnabled,
329 true);
330 profiler->StartSamplingHeapProfiler(
331 static_cast<uint64_t>(samplingIntervalValue), 128,
332 v8::HeapProfiler::kSamplingForceGC);
333 return Response::OK();
334 }
335
336 namespace {
337 std::unique_ptr<protocol::HeapProfiler::SamplingHeapProfileNode>
buildSampingHeapProfileNode(const v8::AllocationProfile::Node * node)338 buildSampingHeapProfileNode(const v8::AllocationProfile::Node* node) {
339 auto children = protocol::Array<
340 protocol::HeapProfiler::SamplingHeapProfileNode>::create();
341 for (const auto* child : node->children)
342 children->addItem(buildSampingHeapProfileNode(child));
343 size_t selfSize = 0;
344 for (const auto& allocation : node->allocations)
345 selfSize += allocation.size * allocation.count;
346 std::unique_ptr<protocol::Runtime::CallFrame> callFrame =
347 protocol::Runtime::CallFrame::create()
348 .setFunctionName(toProtocolString(node->name))
349 .setScriptId(String16::fromInteger(node->script_id))
350 .setUrl(toProtocolString(node->script_name))
351 .setLineNumber(node->line_number - 1)
352 .setColumnNumber(node->column_number - 1)
353 .build();
354 std::unique_ptr<protocol::HeapProfiler::SamplingHeapProfileNode> result =
355 protocol::HeapProfiler::SamplingHeapProfileNode::create()
356 .setCallFrame(std::move(callFrame))
357 .setSelfSize(selfSize)
358 .setChildren(std::move(children))
359 .build();
360 return result;
361 }
362 } // namespace
363
stopSampling(std::unique_ptr<protocol::HeapProfiler::SamplingHeapProfile> * profile)364 Response V8HeapProfilerAgentImpl::stopSampling(
365 std::unique_ptr<protocol::HeapProfiler::SamplingHeapProfile>* profile) {
366 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
367 if (!profiler) return Response::Error("Cannot access v8 heap profiler");
368 v8::HandleScope scope(
369 m_isolate); // Allocation profile contains Local handles.
370 std::unique_ptr<v8::AllocationProfile> v8Profile(
371 profiler->GetAllocationProfile());
372 profiler->StopSamplingHeapProfiler();
373 m_state->setBoolean(HeapProfilerAgentState::samplingHeapProfilerEnabled,
374 false);
375 if (!v8Profile)
376 return Response::Error("Cannot access v8 sampled heap profile.");
377 v8::AllocationProfile::Node* root = v8Profile->GetRootNode();
378 *profile = protocol::HeapProfiler::SamplingHeapProfile::create()
379 .setHead(buildSampingHeapProfileNode(root))
380 .build();
381 return Response::OK();
382 }
383
384 } // namespace v8_inspector
385