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