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 "ScriptController.h"
45 #include "ScriptHeapSnapshot.h"
46 #include "ScriptProfile.h"
47 #include "ScriptProfiler.h"
48 #include <wtf/OwnPtr.h>
49 #include <wtf/text/StringConcatenate.h>
50
51 #if USE(JSC)
52 #include "JSDOMWindow.h"
53 #endif
54
55 namespace WebCore {
56
57 namespace ProfilerAgentState {
58 static const char userInitiatedProfiling[] = "userInitiatedProfiling";
59 static const char profilerEnabled[] = "profilerEnabled";
60 }
61
62 static const char* const UserInitiatedProfileName = "org.webkit.profiles.user-initiated";
63 static const char* const CPUProfileType = "CPU";
64 static const char* const HeapProfileType = "HEAP";
65
create(InstrumentingAgents * instrumentingAgents,InspectorConsoleAgent * consoleAgent,Page * inspectedPage,InspectorState * inspectorState)66 PassOwnPtr<InspectorProfilerAgent> InspectorProfilerAgent::create(InstrumentingAgents* instrumentingAgents, InspectorConsoleAgent* consoleAgent, Page* inspectedPage, InspectorState* inspectorState)
67 {
68 return adoptPtr(new InspectorProfilerAgent(instrumentingAgents, consoleAgent, inspectedPage, inspectorState));
69 }
70
InspectorProfilerAgent(InstrumentingAgents * instrumentingAgents,InspectorConsoleAgent * consoleAgent,Page * inspectedPage,InspectorState * inspectorState)71 InspectorProfilerAgent::InspectorProfilerAgent(InstrumentingAgents* instrumentingAgents, InspectorConsoleAgent* consoleAgent, Page* inspectedPage, InspectorState* inspectorState)
72 : m_instrumentingAgents(instrumentingAgents)
73 , m_consoleAgent(consoleAgent)
74 , m_inspectedPage(inspectedPage)
75 , m_inspectorState(inspectorState)
76 , m_frontend(0)
77 , m_enabled(false)
78 , m_recordingUserInitiatedProfile(false)
79 , m_currentUserInitiatedProfileNumber(-1)
80 , m_nextUserInitiatedProfileNumber(1)
81 , m_nextUserInitiatedHeapSnapshotNumber(1)
82 {
83 m_instrumentingAgents->setInspectorProfilerAgent(this);
84 }
85
~InspectorProfilerAgent()86 InspectorProfilerAgent::~InspectorProfilerAgent()
87 {
88 m_instrumentingAgents->setInspectorProfilerAgent(0);
89 }
90
addProfile(PassRefPtr<ScriptProfile> prpProfile,unsigned lineNumber,const String & sourceURL)91 void InspectorProfilerAgent::addProfile(PassRefPtr<ScriptProfile> prpProfile, unsigned lineNumber, const String& sourceURL)
92 {
93 RefPtr<ScriptProfile> profile = prpProfile;
94 m_profiles.add(profile->uid(), profile);
95 if (m_frontend)
96 m_frontend->addProfileHeader(createProfileHeader(*profile));
97 addProfileFinishedMessageToConsole(profile, lineNumber, sourceURL);
98 }
99
addProfileFinishedMessageToConsole(PassRefPtr<ScriptProfile> prpProfile,unsigned lineNumber,const String & sourceURL)100 void InspectorProfilerAgent::addProfileFinishedMessageToConsole(PassRefPtr<ScriptProfile> prpProfile, unsigned lineNumber, const String& sourceURL)
101 {
102 if (!m_frontend)
103 return;
104 RefPtr<ScriptProfile> profile = prpProfile;
105 String title = profile->title();
106 String message = makeString("Profile \"webkit-profile://", CPUProfileType, '/', encodeWithURLEscapeSequences(title), '#', String::number(profile->uid()), "\" finished.");
107 m_consoleAgent->addMessageToConsole(JSMessageSource, LogMessageType, LogMessageLevel, message, lineNumber, sourceURL);
108 }
109
addStartProfilingMessageToConsole(const String & title,unsigned lineNumber,const String & sourceURL)110 void InspectorProfilerAgent::addStartProfilingMessageToConsole(const String& title, unsigned lineNumber, const String& sourceURL)
111 {
112 if (!m_frontend)
113 return;
114 String message = makeString("Profile \"webkit-profile://", CPUProfileType, '/', encodeWithURLEscapeSequences(title), "#0\" started.");
115 m_consoleAgent->addMessageToConsole(JSMessageSource, LogMessageType, LogMessageLevel, message, lineNumber, sourceURL);
116 }
117
collectGarbage(WebCore::ErrorString *)118 void InspectorProfilerAgent::collectGarbage(WebCore::ErrorString*)
119 {
120 ScriptProfiler::collectGarbage();
121 }
122
createProfileHeader(const ScriptProfile & profile)123 PassRefPtr<InspectorObject> InspectorProfilerAgent::createProfileHeader(const ScriptProfile& profile)
124 {
125 RefPtr<InspectorObject> header = InspectorObject::create();
126 header->setString("title", profile.title());
127 header->setNumber("uid", profile.uid());
128 header->setString("typeId", String(CPUProfileType));
129 return header;
130 }
131
createSnapshotHeader(const ScriptHeapSnapshot & snapshot)132 PassRefPtr<InspectorObject> InspectorProfilerAgent::createSnapshotHeader(const ScriptHeapSnapshot& snapshot)
133 {
134 RefPtr<InspectorObject> header = InspectorObject::create();
135 header->setString("title", snapshot.title());
136 header->setNumber("uid", snapshot.uid());
137 header->setString("typeId", String(HeapProfileType));
138 return header;
139 }
140
enable(ErrorString *)141 void InspectorProfilerAgent::enable(ErrorString*)
142 {
143 if (enabled())
144 return;
145 m_inspectorState->setBoolean(ProfilerAgentState::profilerEnabled, true);
146 enable(false);
147 }
148
disable(ErrorString *)149 void InspectorProfilerAgent::disable(ErrorString*)
150 {
151 m_inspectorState->setBoolean(ProfilerAgentState::profilerEnabled, false);
152 disable();
153 }
154
disable()155 void InspectorProfilerAgent::disable()
156 {
157 if (!m_enabled)
158 return;
159 m_enabled = false;
160 PageScriptDebugServer::shared().recompileAllJSFunctionsSoon();
161 if (m_frontend)
162 m_frontend->profilerWasDisabled();
163 }
164
enable(bool skipRecompile)165 void InspectorProfilerAgent::enable(bool skipRecompile)
166 {
167 if (m_enabled)
168 return;
169 m_enabled = true;
170 if (!skipRecompile)
171 PageScriptDebugServer::shared().recompileAllJSFunctionsSoon();
172 if (m_frontend)
173 m_frontend->profilerWasEnabled();
174 }
175
getCurrentUserInitiatedProfileName(bool incrementProfileNumber)176 String InspectorProfilerAgent::getCurrentUserInitiatedProfileName(bool incrementProfileNumber)
177 {
178 if (incrementProfileNumber)
179 m_currentUserInitiatedProfileNumber = m_nextUserInitiatedProfileNumber++;
180
181 return makeString(UserInitiatedProfileName, '.', String::number(m_currentUserInitiatedProfileNumber));
182 }
183
getProfileHeaders(ErrorString *,RefPtr<InspectorArray> * headers)184 void InspectorProfilerAgent::getProfileHeaders(ErrorString*, RefPtr<InspectorArray>* headers)
185 {
186 ProfilesMap::iterator profilesEnd = m_profiles.end();
187 for (ProfilesMap::iterator it = m_profiles.begin(); it != profilesEnd; ++it)
188 (*headers)->pushObject(createProfileHeader(*it->second));
189 HeapSnapshotsMap::iterator snapshotsEnd = m_snapshots.end();
190 for (HeapSnapshotsMap::iterator it = m_snapshots.begin(); it != snapshotsEnd; ++it)
191 (*headers)->pushObject(createSnapshotHeader(*it->second));
192 }
193
194 namespace {
195
196 class OutputStream : public ScriptHeapSnapshot::OutputStream {
197 public:
OutputStream(InspectorFrontend::Profiler * frontend,unsigned uid)198 OutputStream(InspectorFrontend::Profiler* frontend, unsigned uid)
199 : m_frontend(frontend), m_uid(uid) { }
Write(const String & chunk)200 void Write(const String& chunk) { m_frontend->addHeapSnapshotChunk(m_uid, chunk); }
Close()201 void Close() { m_frontend->finishHeapSnapshot(m_uid); }
202 private:
203 InspectorFrontend::Profiler* m_frontend;
204 int m_uid;
205 };
206
207 } // namespace
208
getProfile(ErrorString *,const String & type,unsigned uid,RefPtr<InspectorObject> * profileObject)209 void InspectorProfilerAgent::getProfile(ErrorString*, const String& type, unsigned uid, RefPtr<InspectorObject>* profileObject)
210 {
211 if (type == CPUProfileType) {
212 ProfilesMap::iterator it = m_profiles.find(uid);
213 if (it != m_profiles.end()) {
214 *profileObject = createProfileHeader(*it->second);
215 (*profileObject)->setObject("head", it->second->buildInspectorObjectForHead());
216 }
217 } else if (type == HeapProfileType) {
218 HeapSnapshotsMap::iterator it = m_snapshots.find(uid);
219 if (it != m_snapshots.end()) {
220 RefPtr<ScriptHeapSnapshot> snapshot = it->second;
221 *profileObject = createSnapshotHeader(*snapshot);
222 if (m_frontend) {
223 OutputStream stream(m_frontend, uid);
224 snapshot->writeJSON(&stream);
225 }
226 }
227 }
228 }
229
removeProfile(ErrorString *,const String & type,unsigned uid)230 void InspectorProfilerAgent::removeProfile(ErrorString*, const String& type, unsigned uid)
231 {
232 if (type == CPUProfileType) {
233 if (m_profiles.contains(uid))
234 m_profiles.remove(uid);
235 } else if (type == HeapProfileType) {
236 if (m_snapshots.contains(uid))
237 m_snapshots.remove(uid);
238 }
239 }
240
resetState()241 void InspectorProfilerAgent::resetState()
242 {
243 stopUserInitiatedProfiling();
244 m_profiles.clear();
245 m_snapshots.clear();
246 m_currentUserInitiatedProfileNumber = 1;
247 m_nextUserInitiatedProfileNumber = 1;
248 m_nextUserInitiatedHeapSnapshotNumber = 1;
249 resetFrontendProfiles();
250 }
251
resetFrontendProfiles()252 void InspectorProfilerAgent::resetFrontendProfiles()
253 {
254 if (m_frontend
255 && m_profiles.begin() == m_profiles.end()
256 && m_snapshots.begin() == m_snapshots.end())
257 m_frontend->resetProfiles();
258 }
259
setFrontend(InspectorFrontend * frontend)260 void InspectorProfilerAgent::setFrontend(InspectorFrontend* frontend)
261 {
262 m_frontend = frontend->profiler();
263 restoreEnablement();
264 }
265
clearFrontend()266 void InspectorProfilerAgent::clearFrontend()
267 {
268 m_frontend = 0;
269 stopUserInitiatedProfiling();
270 }
271
restore()272 void InspectorProfilerAgent::restore()
273 {
274 // Need to restore enablement state here as in setFrontend m_inspectorState wasn't loaded yet.
275 restoreEnablement();
276
277 // Revisit this.
278 resetFrontendProfiles();
279 if (m_inspectorState->getBoolean(ProfilerAgentState::userInitiatedProfiling))
280 startUserInitiatedProfiling();
281 }
282
restoreEnablement()283 void InspectorProfilerAgent::restoreEnablement()
284 {
285 if (m_inspectorState->getBoolean(ProfilerAgentState::profilerEnabled)) {
286 ErrorString error;
287 enable(&error);
288 }
289 }
290
startUserInitiatedProfiling()291 void InspectorProfilerAgent::startUserInitiatedProfiling()
292 {
293 if (m_recordingUserInitiatedProfile)
294 return;
295 if (!enabled()) {
296 enable(true);
297 PageScriptDebugServer::shared().recompileAllJSFunctions(0);
298 }
299 m_recordingUserInitiatedProfile = true;
300 String title = getCurrentUserInitiatedProfileName(true);
301 #if USE(JSC)
302 JSC::ExecState* scriptState = toJSDOMWindow(m_inspectedPage->mainFrame(), debuggerWorld())->globalExec();
303 #else
304 ScriptState* scriptState = 0;
305 #endif
306 ScriptProfiler::start(scriptState, title);
307 addStartProfilingMessageToConsole(title, 0, String());
308 toggleRecordButton(true);
309 m_inspectorState->setBoolean(ProfilerAgentState::userInitiatedProfiling, true);
310 }
311
stopUserInitiatedProfiling(bool ignoreProfile)312 void InspectorProfilerAgent::stopUserInitiatedProfiling(bool ignoreProfile)
313 {
314 if (!m_recordingUserInitiatedProfile)
315 return;
316 m_recordingUserInitiatedProfile = false;
317 String title = getCurrentUserInitiatedProfileName();
318 #if USE(JSC)
319 JSC::ExecState* scriptState = toJSDOMWindow(m_inspectedPage->mainFrame(), debuggerWorld())->globalExec();
320 #else
321 // Use null script state to avoid filtering by context security token.
322 // All functions from all iframes should be visible from Inspector UI.
323 ScriptState* scriptState = 0;
324 #endif
325 RefPtr<ScriptProfile> profile = ScriptProfiler::stop(scriptState, title);
326 if (profile) {
327 if (!ignoreProfile)
328 addProfile(profile, 0, String());
329 else
330 addProfileFinishedMessageToConsole(profile, 0, String());
331 }
332 toggleRecordButton(false);
333 m_inspectorState->setBoolean(ProfilerAgentState::userInitiatedProfiling, false);
334 }
335
336 namespace {
337
338 class HeapSnapshotProgress: public ScriptProfiler::HeapSnapshotProgress {
339 public:
HeapSnapshotProgress(InspectorFrontend::Profiler * frontend)340 explicit HeapSnapshotProgress(InspectorFrontend::Profiler* frontend)
341 : m_frontend(frontend) { }
Start(int totalWork)342 void Start(int totalWork)
343 {
344 m_totalWork = totalWork;
345 }
Worked(int workDone)346 void Worked(int workDone)
347 {
348 if (m_frontend)
349 m_frontend->reportHeapSnapshotProgress(workDone, m_totalWork);
350 }
Done()351 void Done() { }
isCanceled()352 bool isCanceled() { return false; }
353 private:
354 InspectorFrontend::Profiler* m_frontend;
355 int m_totalWork;
356 };
357
358 };
359
takeHeapSnapshot(ErrorString *,bool detailed)360 void InspectorProfilerAgent::takeHeapSnapshot(ErrorString*, bool detailed)
361 {
362 String title = makeString(UserInitiatedProfileName, '.', String::number(m_nextUserInitiatedHeapSnapshotNumber));
363 ++m_nextUserInitiatedHeapSnapshotNumber;
364
365 HeapSnapshotProgress progress(m_frontend);
366 RefPtr<ScriptHeapSnapshot> snapshot = ScriptProfiler::takeHeapSnapshot(title, detailed ? &progress : 0);
367 if (snapshot) {
368 m_snapshots.add(snapshot->uid(), snapshot);
369 if (m_frontend)
370 m_frontend->addProfileHeader(createSnapshotHeader(*snapshot));
371 }
372 }
373
toggleRecordButton(bool isProfiling)374 void InspectorProfilerAgent::toggleRecordButton(bool isProfiling)
375 {
376 if (m_frontend)
377 m_frontend->setRecordingProfile(isProfiling);
378 }
379
380 } // namespace WebCore
381
382 #endif // ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR)
383