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