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
55namespace WebCore {
56
57namespace ProfilerAgentState {
58static const char userInitiatedProfiling[] = "userInitiatedProfiling";
59static const char profilerEnabled[] = "profilerEnabled";
60}
61
62static const char* const UserInitiatedProfileName = "org.webkit.profiles.user-initiated";
63static const char* const CPUProfileType = "CPU";
64static const char* const HeapProfileType = "HEAP";
65
66PassOwnPtr<InspectorProfilerAgent> InspectorProfilerAgent::create(InstrumentingAgents* instrumentingAgents, InspectorConsoleAgent* consoleAgent, Page* inspectedPage, InspectorState* inspectorState)
67{
68    return adoptPtr(new InspectorProfilerAgent(instrumentingAgents, consoleAgent, inspectedPage, inspectorState));
69}
70
71InspectorProfilerAgent::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
86InspectorProfilerAgent::~InspectorProfilerAgent()
87{
88    m_instrumentingAgents->setInspectorProfilerAgent(0);
89}
90
91void 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
100void 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
110void 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
118void InspectorProfilerAgent::collectGarbage(WebCore::ErrorString*)
119{
120    ScriptProfiler::collectGarbage();
121}
122
123PassRefPtr<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
132PassRefPtr<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
141void InspectorProfilerAgent::enable(ErrorString*)
142{
143    if (enabled())
144        return;
145    m_inspectorState->setBoolean(ProfilerAgentState::profilerEnabled, true);
146    enable(false);
147}
148
149void InspectorProfilerAgent::disable(ErrorString*)
150{
151    m_inspectorState->setBoolean(ProfilerAgentState::profilerEnabled, false);
152    disable();
153}
154
155void 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
165void 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
176String InspectorProfilerAgent::getCurrentUserInitiatedProfileName(bool incrementProfileNumber)
177{
178    if (incrementProfileNumber)
179        m_currentUserInitiatedProfileNumber = m_nextUserInitiatedProfileNumber++;
180
181    return makeString(UserInitiatedProfileName, '.', String::number(m_currentUserInitiatedProfileNumber));
182}
183
184void 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
194namespace {
195
196class OutputStream : public ScriptHeapSnapshot::OutputStream {
197public:
198    OutputStream(InspectorFrontend::Profiler* frontend, unsigned uid)
199        : m_frontend(frontend), m_uid(uid) { }
200    void Write(const String& chunk) { m_frontend->addHeapSnapshotChunk(m_uid, chunk); }
201    void Close() { m_frontend->finishHeapSnapshot(m_uid); }
202private:
203    InspectorFrontend::Profiler* m_frontend;
204    int m_uid;
205};
206
207} // namespace
208
209void 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
230void 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
241void 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
252void 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
260void InspectorProfilerAgent::setFrontend(InspectorFrontend* frontend)
261{
262    m_frontend = frontend->profiler();
263    restoreEnablement();
264}
265
266void InspectorProfilerAgent::clearFrontend()
267{
268    m_frontend = 0;
269    stopUserInitiatedProfiling();
270}
271
272void 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
283void InspectorProfilerAgent::restoreEnablement()
284{
285    if (m_inspectorState->getBoolean(ProfilerAgentState::profilerEnabled)) {
286        ErrorString error;
287        enable(&error);
288    }
289}
290
291void 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
312void 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
336namespace {
337
338class HeapSnapshotProgress: public ScriptProfiler::HeapSnapshotProgress {
339public:
340    explicit HeapSnapshotProgress(InspectorFrontend::Profiler* frontend)
341        : m_frontend(frontend) { }
342    void Start(int totalWork)
343    {
344        m_totalWork = totalWork;
345    }
346    void Worked(int workDone)
347    {
348        if (m_frontend)
349            m_frontend->reportHeapSnapshotProgress(workDone, m_totalWork);
350    }
351    void Done() { }
352    bool isCanceled() { return false; }
353private:
354    InspectorFrontend::Profiler* m_frontend;
355    int m_totalWork;
356};
357
358};
359
360void 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
374void 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