1 /*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "config.h"
31 #include "InspectorProfilerAgent.h"
32
33 #if ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR)
34
35 #include "Console.h"
36 #include "InspectorConsoleAgent.h"
37 #include "InspectorFrontend.h"
38 #include "InspectorState.h"
39 #include "InspectorValues.h"
40 #include "InstrumentingAgents.h"
41 #include "KURL.h"
42 #include "Page.h"
43 #include "PageScriptDebugServer.h"
44 #include "ScriptHeapSnapshot.h"
45 #include "ScriptProfile.h"
46 #include "ScriptProfiler.h"
47 #include <wtf/OwnPtr.h>
48 #include <wtf/text/StringConcatenate.h>
49
50 #if USE(JSC)
51 #include "JSDOMWindow.h"
52 #endif
53
54 namespace WebCore {
55
56 namespace ProfilerAgentState {
57 static const char userInitiatedProfiling[] = "userInitiatedProfiling";
58 static const char profilerEnabled[] = "profilerEnabled";
59 }
60
61 static const char* const UserInitiatedProfileName = "org.webkit.profiles.user-initiated";
62 static const char* const CPUProfileType = "CPU";
63 static const char* const HeapProfileType = "HEAP";
64
create(InstrumentingAgents * instrumentingAgents,InspectorConsoleAgent * consoleAgent,Page * inspectedPage,InspectorState * inspectorState)65 PassOwnPtr<InspectorProfilerAgent> InspectorProfilerAgent::create(InstrumentingAgents* instrumentingAgents, InspectorConsoleAgent* consoleAgent, Page* inspectedPage, InspectorState* inspectorState)
66 {
67 return adoptPtr(new InspectorProfilerAgent(instrumentingAgents, consoleAgent, inspectedPage, inspectorState));
68 }
69
InspectorProfilerAgent(InstrumentingAgents * instrumentingAgents,InspectorConsoleAgent * consoleAgent,Page * inspectedPage,InspectorState * inspectorState)70 InspectorProfilerAgent::InspectorProfilerAgent(InstrumentingAgents* instrumentingAgents, InspectorConsoleAgent* consoleAgent, Page* inspectedPage, InspectorState* inspectorState)
71 : m_instrumentingAgents(instrumentingAgents)
72 , m_consoleAgent(consoleAgent)
73 , m_inspectedPage(inspectedPage)
74 , m_inspectorState(inspectorState)
75 , m_frontend(0)
76 , m_enabled(false)
77 , m_recordingUserInitiatedProfile(false)
78 , m_currentUserInitiatedProfileNumber(-1)
79 , m_nextUserInitiatedProfileNumber(1)
80 , m_nextUserInitiatedHeapSnapshotNumber(1)
81 {
82 m_instrumentingAgents->setInspectorProfilerAgent(this);
83 }
84
~InspectorProfilerAgent()85 InspectorProfilerAgent::~InspectorProfilerAgent()
86 {
87 m_instrumentingAgents->setInspectorProfilerAgent(0);
88 }
89
addProfile(PassRefPtr<ScriptProfile> prpProfile,unsigned lineNumber,const String & sourceURL)90 void InspectorProfilerAgent::addProfile(PassRefPtr<ScriptProfile> prpProfile, unsigned lineNumber, const String& sourceURL)
91 {
92 RefPtr<ScriptProfile> profile = prpProfile;
93 m_profiles.add(profile->uid(), profile);
94 if (m_frontend)
95 m_frontend->addProfileHeader(createProfileHeader(*profile));
96 addProfileFinishedMessageToConsole(profile, lineNumber, sourceURL);
97 }
98
addProfileFinishedMessageToConsole(PassRefPtr<ScriptProfile> prpProfile,unsigned lineNumber,const String & sourceURL)99 void InspectorProfilerAgent::addProfileFinishedMessageToConsole(PassRefPtr<ScriptProfile> prpProfile, unsigned lineNumber, const String& sourceURL)
100 {
101 if (!m_frontend)
102 return;
103 RefPtr<ScriptProfile> profile = prpProfile;
104 String title = profile->title();
105 String message = makeString("Profile \"webkit-profile://", CPUProfileType, '/', encodeWithURLEscapeSequences(title), '#', String::number(profile->uid()), "\" finished.");
106 m_consoleAgent->addMessageToConsole(JSMessageSource, LogMessageType, LogMessageLevel, message, lineNumber, sourceURL);
107 }
108
addStartProfilingMessageToConsole(const String & title,unsigned lineNumber,const String & sourceURL)109 void InspectorProfilerAgent::addStartProfilingMessageToConsole(const String& title, unsigned lineNumber, const String& sourceURL)
110 {
111 if (!m_frontend)
112 return;
113 String message = makeString("Profile \"webkit-profile://", CPUProfileType, '/', encodeWithURLEscapeSequences(title), "#0\" started.");
114 m_consoleAgent->addMessageToConsole(JSMessageSource, LogMessageType, LogMessageLevel, message, lineNumber, sourceURL);
115 }
116
collectGarbage(WebCore::ErrorString *)117 void InspectorProfilerAgent::collectGarbage(WebCore::ErrorString*)
118 {
119 ScriptProfiler::collectGarbage();
120 }
121
createProfileHeader(const ScriptProfile & profile)122 PassRefPtr<InspectorObject> InspectorProfilerAgent::createProfileHeader(const ScriptProfile& profile)
123 {
124 RefPtr<InspectorObject> header = InspectorObject::create();
125 header->setString("title", profile.title());
126 header->setNumber("uid", profile.uid());
127 header->setString("typeId", String(CPUProfileType));
128 return header;
129 }
130
createSnapshotHeader(const ScriptHeapSnapshot & snapshot)131 PassRefPtr<InspectorObject> InspectorProfilerAgent::createSnapshotHeader(const ScriptHeapSnapshot& snapshot)
132 {
133 RefPtr<InspectorObject> header = InspectorObject::create();
134 header->setString("title", snapshot.title());
135 header->setNumber("uid", snapshot.uid());
136 header->setString("typeId", String(HeapProfileType));
137 return header;
138 }
139
enable(ErrorString *)140 void InspectorProfilerAgent::enable(ErrorString*)
141 {
142 if (enabled())
143 return;
144 m_inspectorState->setBoolean(ProfilerAgentState::profilerEnabled, true);
145 enable(false);
146 }
147
disable(ErrorString *)148 void InspectorProfilerAgent::disable(ErrorString*)
149 {
150 m_inspectorState->setBoolean(ProfilerAgentState::profilerEnabled, false);
151 disable();
152 }
153
disable()154 void InspectorProfilerAgent::disable()
155 {
156 if (!m_enabled)
157 return;
158 m_enabled = false;
159 PageScriptDebugServer::shared().recompileAllJSFunctionsSoon();
160 if (m_frontend)
161 m_frontend->profilerWasDisabled();
162 }
163
enable(bool skipRecompile)164 void InspectorProfilerAgent::enable(bool skipRecompile)
165 {
166 if (m_enabled)
167 return;
168 m_enabled = true;
169 if (!skipRecompile)
170 PageScriptDebugServer::shared().recompileAllJSFunctionsSoon();
171 if (m_frontend)
172 m_frontend->profilerWasEnabled();
173 }
174
getCurrentUserInitiatedProfileName(bool incrementProfileNumber)175 String InspectorProfilerAgent::getCurrentUserInitiatedProfileName(bool incrementProfileNumber)
176 {
177 if (incrementProfileNumber)
178 m_currentUserInitiatedProfileNumber = m_nextUserInitiatedProfileNumber++;
179
180 return makeString(UserInitiatedProfileName, '.', String::number(m_currentUserInitiatedProfileNumber));
181 }
182
getProfileHeaders(ErrorString *,RefPtr<InspectorArray> * headers)183 void InspectorProfilerAgent::getProfileHeaders(ErrorString*, RefPtr<InspectorArray>* headers)
184 {
185 ProfilesMap::iterator profilesEnd = m_profiles.end();
186 for (ProfilesMap::iterator it = m_profiles.begin(); it != profilesEnd; ++it)
187 (*headers)->pushObject(createProfileHeader(*it->second));
188 HeapSnapshotsMap::iterator snapshotsEnd = m_snapshots.end();
189 for (HeapSnapshotsMap::iterator it = m_snapshots.begin(); it != snapshotsEnd; ++it)
190 (*headers)->pushObject(createSnapshotHeader(*it->second));
191 }
192
193 namespace {
194
195 class OutputStream : public ScriptHeapSnapshot::OutputStream {
196 public:
OutputStream(InspectorFrontend::Profiler * frontend,unsigned uid)197 OutputStream(InspectorFrontend::Profiler* frontend, unsigned uid)
198 : m_frontend(frontend), m_uid(uid) { }
Write(const String & chunk)199 void Write(const String& chunk) { m_frontend->addHeapSnapshotChunk(m_uid, chunk); }
Close()200 void Close() { m_frontend->finishHeapSnapshot(m_uid); }
201 private:
202 InspectorFrontend::Profiler* m_frontend;
203 int m_uid;
204 };
205
206 } // namespace
207
getProfile(ErrorString *,const String & type,unsigned uid,RefPtr<InspectorObject> * profileObject)208 void InspectorProfilerAgent::getProfile(ErrorString*, const String& type, unsigned uid, RefPtr<InspectorObject>* profileObject)
209 {
210 if (type == CPUProfileType) {
211 ProfilesMap::iterator it = m_profiles.find(uid);
212 if (it != m_profiles.end()) {
213 *profileObject = createProfileHeader(*it->second);
214 (*profileObject)->setObject("head", it->second->buildInspectorObjectForHead());
215 }
216 } else if (type == HeapProfileType) {
217 HeapSnapshotsMap::iterator it = m_snapshots.find(uid);
218 if (it != m_snapshots.end()) {
219 RefPtr<ScriptHeapSnapshot> snapshot = it->second;
220 *profileObject = createSnapshotHeader(*snapshot);
221 if (m_frontend) {
222 OutputStream stream(m_frontend, uid);
223 snapshot->writeJSON(&stream);
224 }
225 }
226 }
227 }
228
removeProfile(ErrorString *,const String & type,unsigned uid)229 void InspectorProfilerAgent::removeProfile(ErrorString*, const String& type, unsigned uid)
230 {
231 if (type == CPUProfileType) {
232 if (m_profiles.contains(uid))
233 m_profiles.remove(uid);
234 } else if (type == HeapProfileType) {
235 if (m_snapshots.contains(uid))
236 m_snapshots.remove(uid);
237 }
238 }
239
resetState()240 void InspectorProfilerAgent::resetState()
241 {
242 stopUserInitiatedProfiling();
243 m_profiles.clear();
244 m_snapshots.clear();
245 m_currentUserInitiatedProfileNumber = 1;
246 m_nextUserInitiatedProfileNumber = 1;
247 m_nextUserInitiatedHeapSnapshotNumber = 1;
248 resetFrontendProfiles();
249 }
250
resetFrontendProfiles()251 void InspectorProfilerAgent::resetFrontendProfiles()
252 {
253 if (m_frontend
254 && m_profiles.begin() == m_profiles.end()
255 && m_snapshots.begin() == m_snapshots.end())
256 m_frontend->resetProfiles();
257 }
258
setFrontend(InspectorFrontend * frontend)259 void InspectorProfilerAgent::setFrontend(InspectorFrontend* frontend)
260 {
261 m_frontend = frontend->profiler();
262 restoreEnablement();
263 }
264
clearFrontend()265 void InspectorProfilerAgent::clearFrontend()
266 {
267 m_frontend = 0;
268 stopUserInitiatedProfiling();
269 }
270
restore()271 void InspectorProfilerAgent::restore()
272 {
273 // Need to restore enablement state here as in setFrontend m_inspectorState wasn't loaded yet.
274 restoreEnablement();
275
276 // Revisit this.
277 resetFrontendProfiles();
278 if (m_inspectorState->getBoolean(ProfilerAgentState::userInitiatedProfiling))
279 startUserInitiatedProfiling();
280 }
281
restoreEnablement()282 void InspectorProfilerAgent::restoreEnablement()
283 {
284 if (m_inspectorState->getBoolean(ProfilerAgentState::profilerEnabled)) {
285 ErrorString error;
286 enable(&error);
287 }
288 }
289
startUserInitiatedProfiling()290 void InspectorProfilerAgent::startUserInitiatedProfiling()
291 {
292 if (m_recordingUserInitiatedProfile)
293 return;
294 if (!enabled()) {
295 enable(true);
296 PageScriptDebugServer::shared().recompileAllJSFunctions(0);
297 }
298 m_recordingUserInitiatedProfile = true;
299 String title = getCurrentUserInitiatedProfileName(true);
300 #if USE(JSC)
301 JSC::ExecState* scriptState = toJSDOMWindow(m_inspectedPage->mainFrame(), debuggerWorld())->globalExec();
302 #else
303 ScriptState* scriptState = 0;
304 #endif
305 ScriptProfiler::start(scriptState, title);
306 addStartProfilingMessageToConsole(title, 0, String());
307 toggleRecordButton(true);
308 m_inspectorState->setBoolean(ProfilerAgentState::userInitiatedProfiling, true);
309 }
310
stopUserInitiatedProfiling(bool ignoreProfile)311 void InspectorProfilerAgent::stopUserInitiatedProfiling(bool ignoreProfile)
312 {
313 if (!m_recordingUserInitiatedProfile)
314 return;
315 m_recordingUserInitiatedProfile = false;
316 String title = getCurrentUserInitiatedProfileName();
317 #if USE(JSC)
318 JSC::ExecState* scriptState = toJSDOMWindow(m_inspectedPage->mainFrame(), debuggerWorld())->globalExec();
319 #else
320 // Use null script state to avoid filtering by context security token.
321 // All functions from all iframes should be visible from Inspector UI.
322 ScriptState* scriptState = 0;
323 #endif
324 RefPtr<ScriptProfile> profile = ScriptProfiler::stop(scriptState, title);
325 if (profile) {
326 if (!ignoreProfile)
327 addProfile(profile, 0, String());
328 else
329 addProfileFinishedMessageToConsole(profile, 0, String());
330 }
331 toggleRecordButton(false);
332 m_inspectorState->setBoolean(ProfilerAgentState::userInitiatedProfiling, false);
333 }
334
335 namespace {
336
337 class HeapSnapshotProgress: public ScriptProfiler::HeapSnapshotProgress {
338 public:
HeapSnapshotProgress(InspectorFrontend::Profiler * frontend)339 explicit HeapSnapshotProgress(InspectorFrontend::Profiler* frontend)
340 : m_frontend(frontend) { }
Start(int totalWork)341 void Start(int totalWork)
342 {
343 m_totalWork = totalWork;
344 }
Worked(int workDone)345 void Worked(int workDone)
346 {
347 if (m_frontend)
348 m_frontend->reportHeapSnapshotProgress(workDone, m_totalWork);
349 }
Done()350 void Done() { }
isCanceled()351 bool isCanceled() { return false; }
352 private:
353 InspectorFrontend::Profiler* m_frontend;
354 int m_totalWork;
355 };
356
357 };
358
takeHeapSnapshot(ErrorString *,bool detailed)359 void InspectorProfilerAgent::takeHeapSnapshot(ErrorString*, bool detailed)
360 {
361 String title = makeString(UserInitiatedProfileName, '.', String::number(m_nextUserInitiatedHeapSnapshotNumber));
362 ++m_nextUserInitiatedHeapSnapshotNumber;
363
364 HeapSnapshotProgress progress(m_frontend);
365 RefPtr<ScriptHeapSnapshot> snapshot = ScriptProfiler::takeHeapSnapshot(title, detailed ? &progress : 0);
366 if (snapshot) {
367 m_snapshots.add(snapshot->uid(), snapshot);
368 if (m_frontend)
369 m_frontend->addProfileHeader(createSnapshotHeader(*snapshot));
370 }
371 }
372
toggleRecordButton(bool isProfiling)373 void InspectorProfilerAgent::toggleRecordButton(bool isProfiling)
374 {
375 if (m_frontend)
376 m_frontend->setRecordingProfile(isProfiling);
377 }
378
379 } // namespace WebCore
380
381 #endif // ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR)
382