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