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