1/*
2 * Copyright (C) 2015-2016 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "InspectorHeapAgent.h"
28
29#include "HeapProfiler.h"
30#include "HeapSnapshot.h"
31#include "InjectedScript.h"
32#include "InjectedScriptManager.h"
33#include "InspectorEnvironment.h"
34#include "JSCInlines.h"
35#include "VM.h"
36#include <wtf/Stopwatch.h>
37
38using namespace JSC;
39
40namespace Inspector {
41
42InspectorHeapAgent::InspectorHeapAgent(AgentContext& context)
43 : InspectorAgentBase("Heap"_s)
44 , m_injectedScriptManager(context.injectedScriptManager)
45 , m_frontendDispatcher(std::make_unique<HeapFrontendDispatcher>(context.frontendRouter))
46 , m_backendDispatcher(HeapBackendDispatcher::create(context.backendDispatcher, this))
47 , m_environment(context.environment)
48{
49}
50
51void InspectorHeapAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
52{
53}
54
55void InspectorHeapAgent::willDestroyFrontendAndBackend(DisconnectReason)
56{
57 // Stop tracking without taking a snapshot.
58 m_tracking = false;
59
60 ErrorString ignored;
61 disable(ignored);
62}
63
64void InspectorHeapAgent::enable(ErrorString&)
65{
66 if (m_enabled)
67 return;
68
69 m_enabled = true;
70
71 m_environment.vm().heap.addObserver(this);
72}
73
74void InspectorHeapAgent::disable(ErrorString&)
75{
76 if (!m_enabled)
77 return;
78
79 m_enabled = false;
80
81 m_environment.vm().heap.removeObserver(this);
82
83 clearHeapSnapshots();
84}
85
86void InspectorHeapAgent::gc(ErrorString&)
87{
88 VM& vm = m_environment.vm();
89 JSLockHolder lock(vm);
90 sanitizeStackForVM(&vm);
91 vm.heap.collectNow(Sync, CollectionScope::Full);
92}
93
94void InspectorHeapAgent::snapshot(ErrorString&, double* timestamp, String* snapshotData)
95{
96 VM& vm = m_environment.vm();
97 JSLockHolder lock(vm);
98
99 HeapSnapshotBuilder snapshotBuilder(vm.ensureHeapProfiler());
100 snapshotBuilder.buildSnapshot();
101
102 *timestamp = m_environment.executionStopwatch()->elapsedTime().seconds();
103 *snapshotData = snapshotBuilder.json([&] (const HeapSnapshotNode& node) {
104 if (Structure* structure = node.cell->structure(vm)) {
105 if (JSGlobalObject* globalObject = structure->globalObject()) {
106 if (!m_environment.canAccessInspectedScriptState(globalObject->globalExec()))
107 return false;
108 }
109 }
110 return true;
111 });
112}
113
114void InspectorHeapAgent::startTracking(ErrorString& errorString)
115{
116 if (m_tracking)
117 return;
118
119 m_tracking = true;
120
121 double timestamp;
122 String snapshotData;
123 snapshot(errorString, &timestamp, &snapshotData);
124
125 m_frontendDispatcher->trackingStart(timestamp, snapshotData);
126}
127
128void InspectorHeapAgent::stopTracking(ErrorString& errorString)
129{
130 if (!m_tracking)
131 return;
132
133 m_tracking = false;
134
135 double timestamp;
136 String snapshotData;
137 snapshot(errorString, &timestamp, &snapshotData);
138
139 m_frontendDispatcher->trackingComplete(timestamp, snapshotData);
140}
141
142Optional<HeapSnapshotNode> InspectorHeapAgent::nodeForHeapObjectIdentifier(ErrorString& errorString, unsigned heapObjectIdentifier)
143{
144 HeapProfiler* heapProfiler = m_environment.vm().heapProfiler();
145 if (!heapProfiler) {
146 errorString = "No heap snapshot"_s;
147 return WTF::nullopt;
148 }
149
150 HeapSnapshot* snapshot = heapProfiler->mostRecentSnapshot();
151 if (!snapshot) {
152 errorString = "No heap snapshot"_s;
153 return WTF::nullopt;
154 }
155
156 const Optional<HeapSnapshotNode> optionalNode = snapshot->nodeForObjectIdentifier(heapObjectIdentifier);
157 if (!optionalNode) {
158 errorString = "No object for identifier, it may have been collected"_s;
159 return WTF::nullopt;
160 }
161
162 return optionalNode;
163}
164
165void InspectorHeapAgent::getPreview(ErrorString& errorString, int heapObjectId, Optional<String>& resultString, RefPtr<Protocol::Debugger::FunctionDetails>& functionDetails, RefPtr<Protocol::Runtime::ObjectPreview>& objectPreview)
166{
167 // Prevent the cell from getting collected as we look it up.
168 VM& vm = m_environment.vm();
169 JSLockHolder lock(vm);
170 DeferGC deferGC(vm.heap);
171
172 unsigned heapObjectIdentifier = static_cast<unsigned>(heapObjectId);
173 const Optional<HeapSnapshotNode> optionalNode = nodeForHeapObjectIdentifier(errorString, heapObjectIdentifier);
174 if (!optionalNode)
175 return;
176
177 // String preview.
178 JSCell* cell = optionalNode->cell;
179 if (cell->isString()) {
180 resultString = asString(cell)->tryGetValue();
181 return;
182 }
183
184 // FIXME: Provide preview information for Internal Objects? CodeBlock, Executable, etc.
185
186 Structure* structure = cell->structure(vm);
187 if (!structure) {
188 errorString = "Unable to get object details - Structure"_s;
189 return;
190 }
191
192 JSGlobalObject* globalObject = structure->globalObject();
193 if (!globalObject) {
194 errorString = "Unable to get object details - GlobalObject"_s;
195 return;
196 }
197
198 InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(globalObject->globalExec());
199 if (injectedScript.hasNoValue()) {
200 errorString = "Unable to get object details - InjectedScript"_s;
201 return;
202 }
203
204 // Function preview.
205 if (cell->inherits<JSFunction>(vm)) {
206 injectedScript.functionDetails(errorString, cell, functionDetails);
207 return;
208 }
209
210 // Object preview.
211 objectPreview = injectedScript.previewValue(cell);
212}
213
214void InspectorHeapAgent::getRemoteObject(ErrorString& errorString, int heapObjectId, const String* optionalObjectGroup, RefPtr<Protocol::Runtime::RemoteObject>& result)
215{
216 // Prevent the cell from getting collected as we look it up.
217 VM& vm = m_environment.vm();
218 JSLockHolder lock(vm);
219 DeferGC deferGC(vm.heap);
220
221 unsigned heapObjectIdentifier = static_cast<unsigned>(heapObjectId);
222 const Optional<HeapSnapshotNode> optionalNode = nodeForHeapObjectIdentifier(errorString, heapObjectIdentifier);
223 if (!optionalNode)
224 return;
225
226 JSCell* cell = optionalNode->cell;
227 Structure* structure = cell->structure(vm);
228 if (!structure) {
229 errorString = "Unable to get object details"_s;
230 return;
231 }
232
233 JSGlobalObject* globalObject = structure->globalObject();
234 if (!globalObject) {
235 errorString = "Unable to get object details"_s;
236 return;
237 }
238
239 InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(globalObject->globalExec());
240 if (injectedScript.hasNoValue()) {
241 errorString = "Unable to get object details - InjectedScript"_s;
242 return;
243 }
244
245 String objectGroup = optionalObjectGroup ? *optionalObjectGroup : String();
246 result = injectedScript.wrapObject(cell, objectGroup, true);
247}
248
249static Protocol::Heap::GarbageCollection::Type protocolTypeForHeapOperation(CollectionScope scope)
250{
251 switch (scope) {
252 case CollectionScope::Full:
253 return Protocol::Heap::GarbageCollection::Type::Full;
254 case CollectionScope::Eden:
255 return Protocol::Heap::GarbageCollection::Type::Partial;
256 }
257 ASSERT_NOT_REACHED();
258 return Protocol::Heap::GarbageCollection::Type::Full;
259}
260
261void InspectorHeapAgent::willGarbageCollect()
262{
263 if (!m_enabled)
264 return;
265
266 m_gcStartTime = m_environment.executionStopwatch()->elapsedTime();
267}
268
269void InspectorHeapAgent::didGarbageCollect(CollectionScope scope)
270{
271 if (!m_enabled) {
272 m_gcStartTime = Seconds::nan();
273 return;
274 }
275
276 if (std::isnan(m_gcStartTime)) {
277 // We were not enabled when the GC began.
278 return;
279 }
280
281 // FIXME: Include number of bytes freed by collection.
282
283 Seconds endTime = m_environment.executionStopwatch()->elapsedTime();
284 dispatchGarbageCollectedEvent(protocolTypeForHeapOperation(scope), m_gcStartTime, endTime);
285
286 m_gcStartTime = Seconds::nan();
287}
288
289void InspectorHeapAgent::clearHeapSnapshots()
290{
291 VM& vm = m_environment.vm();
292 JSLockHolder lock(vm);
293
294 if (HeapProfiler* heapProfiler = vm.heapProfiler()) {
295 heapProfiler->clearSnapshots();
296 HeapSnapshotBuilder::resetNextAvailableObjectIdentifier();
297 }
298}
299
300void InspectorHeapAgent::dispatchGarbageCollectedEvent(Protocol::Heap::GarbageCollection::Type type, Seconds startTime, Seconds endTime)
301{
302 auto protocolObject = Protocol::Heap::GarbageCollection::create()
303 .setType(type)
304 .setStartTime(startTime.seconds())
305 .setEndTime(endTime.seconds())
306 .release();
307
308 m_frontendDispatcher->garbageCollected(WTFMove(protocolObject));
309}
310
311} // namespace Inspector
312