blob: 870a120a6b3937624bf2f7c243e732afe4bb7743 [file] [log] [blame]
/*
* 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 <wtf/OwnPtr.h>
#include <wtf/text/StringConcatenate.h>
#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> 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<ScriptProfile> prpProfile, unsigned lineNumber, const String& sourceURL)
{
RefPtr<ScriptProfile> profile = prpProfile;
m_profiles.add(profile->uid(), profile);
if (m_frontend)
m_frontend->addProfileHeader(createProfileHeader(*profile));
addProfileFinishedMessageToConsole(profile, lineNumber, sourceURL);
}
void InspectorProfilerAgent::addProfileFinishedMessageToConsole(PassRefPtr<ScriptProfile> prpProfile, unsigned lineNumber, const String& sourceURL)
{
if (!m_frontend)
return;
RefPtr<ScriptProfile> 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<InspectorObject> InspectorProfilerAgent::createProfileHeader(const ScriptProfile& profile)
{
RefPtr<InspectorObject> header = InspectorObject::create();
header->setString("title", profile.title());
header->setNumber("uid", profile.uid());
header->setString("typeId", String(CPUProfileType));
return header;
}
PassRefPtr<InspectorObject> InspectorProfilerAgent::createSnapshotHeader(const ScriptHeapSnapshot& snapshot)
{
RefPtr<InspectorObject> 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<InspectorArray>* 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<InspectorObject>* 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<ScriptHeapSnapshot> 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<ScriptProfile> 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<ScriptHeapSnapshot> 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)