Home | History | Annotate | Download | only in inspector
      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 
     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 
     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 
     86 InspectorProfilerAgent::~InspectorProfilerAgent()
     87 {
     88     m_instrumentingAgents->setInspectorProfilerAgent(0);
     89 }
     90 
     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 
    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 
    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 
    118 void InspectorProfilerAgent::collectGarbage(WebCore::ErrorString*)
    119 {
    120     ScriptProfiler::collectGarbage();
    121 }
    122 
    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 
    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 
    141 void InspectorProfilerAgent::enable(ErrorString*)
    142 {
    143     if (enabled())
    144         return;
    145     m_inspectorState->setBoolean(ProfilerAgentState::profilerEnabled, true);
    146     enable(false);
    147 }
    148 
    149 void InspectorProfilerAgent::disable(ErrorString*)
    150 {
    151     m_inspectorState->setBoolean(ProfilerAgentState::profilerEnabled, false);
    152     disable();
    153 }
    154 
    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 
    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 
    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 
    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:
    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); }
    202 private:
    203     InspectorFrontend::Profiler* m_frontend;
    204     int m_uid;
    205 };
    206 
    207 } // namespace
    208 
    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 
    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 
    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 
    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 
    260 void InspectorProfilerAgent::setFrontend(InspectorFrontend* frontend)
    261 {
    262     m_frontend = frontend->profiler();
    263     restoreEnablement();
    264 }
    265 
    266 void InspectorProfilerAgent::clearFrontend()
    267 {
    268     m_frontend = 0;
    269     stopUserInitiatedProfiling();
    270 }
    271 
    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 
    283 void InspectorProfilerAgent::restoreEnablement()
    284 {
    285     if (m_inspectorState->getBoolean(ProfilerAgentState::profilerEnabled)) {
    286         ErrorString error;
    287         enable(&error);
    288     }
    289 }
    290 
    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 
    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:
    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; }
    353 private:
    354     InspectorFrontend::Profiler* m_frontend;
    355     int m_totalWork;
    356 };
    357 
    358 };
    359 
    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 
    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