/* * Copyright (C) 2010 Apple Inc. All rights reserved. * Copyright (C) 2010 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "InspectorProfilerAgent.h" #if ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR) #include "Console.h" #include "InspectorConsoleAgent.h" #include "InspectorFrontend.h" #include "InspectorState.h" #include "InspectorValues.h" #include "InstrumentingAgents.h" #include "KURL.h" #include "Page.h" #include "PageScriptDebugServer.h" #include "ScriptController.h" #include "ScriptHeapSnapshot.h" #include "ScriptProfile.h" #include "ScriptProfiler.h" #include #include #if USE(JSC) #include "JSDOMWindow.h" #endif namespace WebCore { namespace ProfilerAgentState { static const char userInitiatedProfiling[] = "userInitiatedProfiling"; static const char profilerEnabled[] = "profilerEnabled"; } static const char* const UserInitiatedProfileName = "org.webkit.profiles.user-initiated"; static const char* const CPUProfileType = "CPU"; static const char* const HeapProfileType = "HEAP"; PassOwnPtr InspectorProfilerAgent::create(InstrumentingAgents* instrumentingAgents, InspectorConsoleAgent* consoleAgent, Page* inspectedPage, InspectorState* inspectorState) { return adoptPtr(new InspectorProfilerAgent(instrumentingAgents, consoleAgent, inspectedPage, inspectorState)); } InspectorProfilerAgent::InspectorProfilerAgent(InstrumentingAgents* instrumentingAgents, InspectorConsoleAgent* consoleAgent, Page* inspectedPage, InspectorState* inspectorState) : m_instrumentingAgents(instrumentingAgents) , m_consoleAgent(consoleAgent) , m_inspectedPage(inspectedPage) , m_inspectorState(inspectorState) , m_frontend(0) , m_enabled(false) , m_recordingUserInitiatedProfile(false) , m_currentUserInitiatedProfileNumber(-1) , m_nextUserInitiatedProfileNumber(1) , m_nextUserInitiatedHeapSnapshotNumber(1) { m_instrumentingAgents->setInspectorProfilerAgent(this); } InspectorProfilerAgent::~InspectorProfilerAgent() { m_instrumentingAgents->setInspectorProfilerAgent(0); } void InspectorProfilerAgent::addProfile(PassRefPtr prpProfile, unsigned lineNumber, const String& sourceURL) { RefPtr profile = prpProfile; m_profiles.add(profile->uid(), profile); if (m_frontend) m_frontend->addProfileHeader(createProfileHeader(*profile)); addProfileFinishedMessageToConsole(profile, lineNumber, sourceURL); } void InspectorProfilerAgent::addProfileFinishedMessageToConsole(PassRefPtr prpProfile, unsigned lineNumber, const String& sourceURL) { if (!m_frontend) return; RefPtr profile = prpProfile; String title = profile->title(); String message = makeString("Profile \"webkit-profile://", CPUProfileType, '/', encodeWithURLEscapeSequences(title), '#', String::number(profile->uid()), "\" finished."); m_consoleAgent->addMessageToConsole(JSMessageSource, LogMessageType, LogMessageLevel, message, lineNumber, sourceURL); } void InspectorProfilerAgent::addStartProfilingMessageToConsole(const String& title, unsigned lineNumber, const String& sourceURL) { if (!m_frontend) return; String message = makeString("Profile \"webkit-profile://", CPUProfileType, '/', encodeWithURLEscapeSequences(title), "#0\" started."); m_consoleAgent->addMessageToConsole(JSMessageSource, LogMessageType, LogMessageLevel, message, lineNumber, sourceURL); } void InspectorProfilerAgent::collectGarbage(WebCore::ErrorString*) { ScriptProfiler::collectGarbage(); } PassRefPtr InspectorProfilerAgent::createProfileHeader(const ScriptProfile& profile) { RefPtr header = InspectorObject::create(); header->setString("title", profile.title()); header->setNumber("uid", profile.uid()); header->setString("typeId", String(CPUProfileType)); return header; } PassRefPtr InspectorProfilerAgent::createSnapshotHeader(const ScriptHeapSnapshot& snapshot) { RefPtr header = InspectorObject::create(); header->setString("title", snapshot.title()); header->setNumber("uid", snapshot.uid()); header->setString("typeId", String(HeapProfileType)); return header; } void InspectorProfilerAgent::enable(ErrorString*) { if (enabled()) return; m_inspectorState->setBoolean(ProfilerAgentState::profilerEnabled, true); enable(false); } void InspectorProfilerAgent::disable(ErrorString*) { m_inspectorState->setBoolean(ProfilerAgentState::profilerEnabled, false); disable(); } void InspectorProfilerAgent::disable() { if (!m_enabled) return; m_enabled = false; PageScriptDebugServer::shared().recompileAllJSFunctionsSoon(); if (m_frontend) m_frontend->profilerWasDisabled(); } void InspectorProfilerAgent::enable(bool skipRecompile) { if (m_enabled) return; m_enabled = true; if (!skipRecompile) PageScriptDebugServer::shared().recompileAllJSFunctionsSoon(); if (m_frontend) m_frontend->profilerWasEnabled(); } String InspectorProfilerAgent::getCurrentUserInitiatedProfileName(bool incrementProfileNumber) { if (incrementProfileNumber) m_currentUserInitiatedProfileNumber = m_nextUserInitiatedProfileNumber++; return makeString(UserInitiatedProfileName, '.', String::number(m_currentUserInitiatedProfileNumber)); } void InspectorProfilerAgent::getProfileHeaders(ErrorString*, RefPtr* headers) { ProfilesMap::iterator profilesEnd = m_profiles.end(); for (ProfilesMap::iterator it = m_profiles.begin(); it != profilesEnd; ++it) (*headers)->pushObject(createProfileHeader(*it->second)); HeapSnapshotsMap::iterator snapshotsEnd = m_snapshots.end(); for (HeapSnapshotsMap::iterator it = m_snapshots.begin(); it != snapshotsEnd; ++it) (*headers)->pushObject(createSnapshotHeader(*it->second)); } namespace { class OutputStream : public ScriptHeapSnapshot::OutputStream { public: OutputStream(InspectorFrontend::Profiler* frontend, unsigned uid) : m_frontend(frontend), m_uid(uid) { } void Write(const String& chunk) { m_frontend->addHeapSnapshotChunk(m_uid, chunk); } void Close() { m_frontend->finishHeapSnapshot(m_uid); } private: InspectorFrontend::Profiler* m_frontend; int m_uid; }; } // namespace void InspectorProfilerAgent::getProfile(ErrorString*, const String& type, unsigned uid, RefPtr* profileObject) { if (type == CPUProfileType) { ProfilesMap::iterator it = m_profiles.find(uid); if (it != m_profiles.end()) { *profileObject = createProfileHeader(*it->second); (*profileObject)->setObject("head", it->second->buildInspectorObjectForHead()); } } else if (type == HeapProfileType) { HeapSnapshotsMap::iterator it = m_snapshots.find(uid); if (it != m_snapshots.end()) { RefPtr snapshot = it->second; *profileObject = createSnapshotHeader(*snapshot); if (m_frontend) { OutputStream stream(m_frontend, uid); snapshot->writeJSON(&stream); } } } } void InspectorProfilerAgent::removeProfile(ErrorString*, const String& type, unsigned uid) { if (type == CPUProfileType) { if (m_profiles.contains(uid)) m_profiles.remove(uid); } else if (type == HeapProfileType) { if (m_snapshots.contains(uid)) m_snapshots.remove(uid); } } void InspectorProfilerAgent::resetState() { stopUserInitiatedProfiling(); m_profiles.clear(); m_snapshots.clear(); m_currentUserInitiatedProfileNumber = 1; m_nextUserInitiatedProfileNumber = 1; m_nextUserInitiatedHeapSnapshotNumber = 1; resetFrontendProfiles(); } void InspectorProfilerAgent::resetFrontendProfiles() { if (m_frontend && m_profiles.begin() == m_profiles.end() && m_snapshots.begin() == m_snapshots.end()) m_frontend->resetProfiles(); } void InspectorProfilerAgent::setFrontend(InspectorFrontend* frontend) { m_frontend = frontend->profiler(); restoreEnablement(); } void InspectorProfilerAgent::clearFrontend() { m_frontend = 0; stopUserInitiatedProfiling(); } void InspectorProfilerAgent::restore() { // Need to restore enablement state here as in setFrontend m_inspectorState wasn't loaded yet. restoreEnablement(); // Revisit this. resetFrontendProfiles(); if (m_inspectorState->getBoolean(ProfilerAgentState::userInitiatedProfiling)) startUserInitiatedProfiling(); } void InspectorProfilerAgent::restoreEnablement() { if (m_inspectorState->getBoolean(ProfilerAgentState::profilerEnabled)) { ErrorString error; enable(&error); } } void InspectorProfilerAgent::startUserInitiatedProfiling() { if (m_recordingUserInitiatedProfile) return; if (!enabled()) { enable(true); PageScriptDebugServer::shared().recompileAllJSFunctions(0); } m_recordingUserInitiatedProfile = true; String title = getCurrentUserInitiatedProfileName(true); #if USE(JSC) JSC::ExecState* scriptState = toJSDOMWindow(m_inspectedPage->mainFrame(), debuggerWorld())->globalExec(); #else ScriptState* scriptState = 0; #endif ScriptProfiler::start(scriptState, title); addStartProfilingMessageToConsole(title, 0, String()); toggleRecordButton(true); m_inspectorState->setBoolean(ProfilerAgentState::userInitiatedProfiling, true); } void InspectorProfilerAgent::stopUserInitiatedProfiling(bool ignoreProfile) { if (!m_recordingUserInitiatedProfile) return; m_recordingUserInitiatedProfile = false; String title = getCurrentUserInitiatedProfileName(); #if USE(JSC) JSC::ExecState* scriptState = toJSDOMWindow(m_inspectedPage->mainFrame(), debuggerWorld())->globalExec(); #else // Use null script state to avoid filtering by context security token. // All functions from all iframes should be visible from Inspector UI. ScriptState* scriptState = 0; #endif RefPtr profile = ScriptProfiler::stop(scriptState, title); if (profile) { if (!ignoreProfile) addProfile(profile, 0, String()); else addProfileFinishedMessageToConsole(profile, 0, String()); } toggleRecordButton(false); m_inspectorState->setBoolean(ProfilerAgentState::userInitiatedProfiling, false); } namespace { class HeapSnapshotProgress: public ScriptProfiler::HeapSnapshotProgress { public: explicit HeapSnapshotProgress(InspectorFrontend::Profiler* frontend) : m_frontend(frontend) { } void Start(int totalWork) { m_totalWork = totalWork; } void Worked(int workDone) { if (m_frontend) m_frontend->reportHeapSnapshotProgress(workDone, m_totalWork); } void Done() { } bool isCanceled() { return false; } private: InspectorFrontend::Profiler* m_frontend; int m_totalWork; }; }; void InspectorProfilerAgent::takeHeapSnapshot(ErrorString*, bool detailed) { String title = makeString(UserInitiatedProfileName, '.', String::number(m_nextUserInitiatedHeapSnapshotNumber)); ++m_nextUserInitiatedHeapSnapshotNumber; HeapSnapshotProgress progress(m_frontend); RefPtr snapshot = ScriptProfiler::takeHeapSnapshot(title, detailed ? &progress : 0); if (snapshot) { m_snapshots.add(snapshot->uid(), snapshot); if (m_frontend) m_frontend->addProfileHeader(createSnapshotHeader(*snapshot)); } } void InspectorProfilerAgent::toggleRecordButton(bool isProfiling) { if (m_frontend) m_frontend->setRecordingProfile(isProfiling); } } // namespace WebCore #endif // ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR)