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 "core/inspector/InspectorProfilerAgent.h"
32
33#include "bindings/core/v8/ScriptCallStackFactory.h"
34#include "bindings/core/v8/ScriptProfiler.h"
35#include "core/frame/ConsoleTypes.h"
36#include "core/frame/UseCounter.h"
37#include "core/inspector/InjectedScript.h"
38#include "core/inspector/InjectedScriptHost.h"
39#include "core/inspector/InspectorOverlay.h"
40#include "core/inspector/InspectorState.h"
41#include "core/inspector/InstrumentingAgents.h"
42#include "core/inspector/ScriptCallStack.h"
43#include "core/inspector/ScriptProfile.h"
44#include "wtf/CurrentTime.h"
45#include "wtf/text/StringConcatenate.h"
46
47namespace blink {
48
49namespace ProfilerAgentState {
50static const char samplingInterval[] = "samplingInterval";
51static const char userInitiatedProfiling[] = "userInitiatedProfiling";
52static const char profilerEnabled[] = "profilerEnabled";
53static const char nextProfileId[] = "nextProfileId";
54}
55
56static PassRefPtr<TypeBuilder::Profiler::CPUProfile> createCPUProfile(const ScriptProfile& scriptProfile)
57{
58    RefPtr<TypeBuilder::Profiler::CPUProfile> profile = TypeBuilder::Profiler::CPUProfile::create()
59        .setHead(scriptProfile.buildInspectorObjectForHead())
60        .setStartTime(scriptProfile.startTime())
61        .setEndTime(scriptProfile.endTime());
62    profile->setSamples(scriptProfile.buildInspectorObjectForSamples());
63    profile->setTimestamps(scriptProfile.buildInspectorObjectForTimestamps());
64    return profile.release();
65}
66
67static PassRefPtr<TypeBuilder::Debugger::Location> currentDebugLocation()
68{
69    RefPtrWillBeRawPtr<ScriptCallStack> callStack(createScriptCallStack(1));
70    const ScriptCallFrame& lastCaller = callStack->at(0);
71    RefPtr<TypeBuilder::Debugger::Location> location = TypeBuilder::Debugger::Location::create()
72        .setScriptId(lastCaller.scriptId())
73        .setLineNumber(lastCaller.lineNumber());
74    location->setColumnNumber(lastCaller.columnNumber());
75    return location.release();
76}
77
78class InspectorProfilerAgent::ProfileDescriptor {
79public:
80    ProfileDescriptor(const String& id, const String& title)
81        : m_id(id)
82        , m_title(title) { }
83    String m_id;
84    String m_title;
85};
86
87PassOwnPtrWillBeRawPtr<InspectorProfilerAgent> InspectorProfilerAgent::create(InjectedScriptManager* injectedScriptManager, InspectorOverlay* overlay)
88{
89    return adoptPtrWillBeNoop(new InspectorProfilerAgent(injectedScriptManager, overlay));
90}
91
92InspectorProfilerAgent::InspectorProfilerAgent(InjectedScriptManager* injectedScriptManager, InspectorOverlay* overlay)
93    : InspectorBaseAgent<InspectorProfilerAgent>("Profiler")
94    , m_injectedScriptManager(injectedScriptManager)
95    , m_frontend(0)
96    , m_recordingCPUProfile(false)
97    , m_profileNameIdleTimeMap(ScriptProfiler::currentProfileNameIdleTimeMap())
98    , m_idleStartTime(0.0)
99    , m_overlay(overlay)
100{
101}
102
103InspectorProfilerAgent::~InspectorProfilerAgent()
104{
105}
106
107void InspectorProfilerAgent::consoleProfile(ExecutionContext* context, const String& title)
108{
109    UseCounter::count(context, UseCounter::DevToolsConsoleProfile);
110    ASSERT(m_frontend && enabled());
111    String id = nextProfileId();
112    m_startedProfiles.append(ProfileDescriptor(id, title));
113    ScriptProfiler::start(id);
114    m_frontend->consoleProfileStarted(id, currentDebugLocation(), title.isNull() ? 0 : &title);
115}
116
117void InspectorProfilerAgent::consoleProfileEnd(const String& title)
118{
119    ASSERT(m_frontend && enabled());
120    String id;
121    String resolvedTitle;
122    // Take last started profile if no title was passed.
123    if (title.isNull()) {
124        if (m_startedProfiles.isEmpty())
125            return;
126        id = m_startedProfiles.last().m_id;
127        resolvedTitle = m_startedProfiles.last().m_title;
128        m_startedProfiles.removeLast();
129    } else {
130        for (size_t i = 0; i < m_startedProfiles.size(); i++) {
131            if (m_startedProfiles[i].m_title == title) {
132                resolvedTitle = title;
133                id = m_startedProfiles[i].m_id;
134                m_startedProfiles.remove(i);
135                break;
136            }
137        }
138        if (id.isEmpty())
139            return;
140    }
141    RefPtrWillBeRawPtr<ScriptProfile> profile = ScriptProfiler::stop(id);
142    if (!profile)
143        return;
144    RefPtr<TypeBuilder::Debugger::Location> location = currentDebugLocation();
145    m_frontend->consoleProfileFinished(id, location, createCPUProfile(*profile), resolvedTitle.isNull() ? 0 : &resolvedTitle);
146}
147
148void InspectorProfilerAgent::enable(ErrorString*)
149{
150    m_state->setBoolean(ProfilerAgentState::profilerEnabled, true);
151    doEnable();
152}
153
154void InspectorProfilerAgent::doEnable()
155{
156    m_instrumentingAgents->setInspectorProfilerAgent(this);
157}
158
159void InspectorProfilerAgent::disable(ErrorString*)
160{
161    for (Vector<ProfileDescriptor>::reverse_iterator it = m_startedProfiles.rbegin(); it != m_startedProfiles.rend(); ++it)
162        ScriptProfiler::stop(it->m_id);
163    m_startedProfiles.clear();
164    stop(0, 0);
165
166    m_instrumentingAgents->setInspectorProfilerAgent(0);
167    m_state->setBoolean(ProfilerAgentState::profilerEnabled, false);
168}
169
170bool InspectorProfilerAgent::enabled()
171{
172    return m_state->getBoolean(ProfilerAgentState::profilerEnabled);
173}
174
175void InspectorProfilerAgent::setSamplingInterval(ErrorString* error, int interval)
176{
177    if (m_recordingCPUProfile) {
178        *error = "Cannot change sampling interval when profiling.";
179        return;
180    }
181    m_state->setLong(ProfilerAgentState::samplingInterval, interval);
182    ScriptProfiler::setSamplingInterval(interval);
183}
184
185void InspectorProfilerAgent::setFrontend(InspectorFrontend* frontend)
186{
187    m_frontend = frontend->profiler();
188}
189
190void InspectorProfilerAgent::clearFrontend()
191{
192    m_frontend = 0;
193    ErrorString error;
194    disable(&error);
195    m_injectedScriptManager->injectedScriptHost()->clearInspectedObjects();
196}
197
198void InspectorProfilerAgent::restore()
199{
200    if (m_state->getBoolean(ProfilerAgentState::profilerEnabled))
201        doEnable();
202    if (long interval = m_state->getLong(ProfilerAgentState::samplingInterval, 0))
203        ScriptProfiler::setSamplingInterval(interval);
204    if (m_state->getBoolean(ProfilerAgentState::userInitiatedProfiling)) {
205        ErrorString error;
206        start(&error);
207    }
208}
209
210void InspectorProfilerAgent::start(ErrorString* error)
211{
212    if (m_recordingCPUProfile)
213        return;
214    if (!enabled()) {
215        *error = "Profiler is not enabled";
216        return;
217    }
218    m_recordingCPUProfile = true;
219    if (m_overlay)
220        m_overlay->startedRecordingProfile();
221    m_frontendInitiatedProfileId = nextProfileId();
222    ScriptProfiler::start(m_frontendInitiatedProfileId);
223    m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, true);
224}
225
226void InspectorProfilerAgent::stop(ErrorString* errorString, RefPtr<TypeBuilder::Profiler::CPUProfile>& profile)
227{
228    stop(errorString, &profile);
229}
230
231void InspectorProfilerAgent::stop(ErrorString* errorString, RefPtr<TypeBuilder::Profiler::CPUProfile>* profile)
232{
233    if (!m_recordingCPUProfile) {
234        if (errorString)
235            *errorString = "No recording profiles found";
236        return;
237    }
238    m_recordingCPUProfile = false;
239    if (m_overlay)
240        m_overlay->finishedRecordingProfile();
241    RefPtrWillBeRawPtr<ScriptProfile> scriptProfile = ScriptProfiler::stop(m_frontendInitiatedProfileId);
242    m_frontendInitiatedProfileId = String();
243    if (scriptProfile && profile)
244        *profile = createCPUProfile(*scriptProfile);
245    else if (errorString)
246        *errorString = "Profile wasn't found";
247    m_state->setBoolean(ProfilerAgentState::userInitiatedProfiling, false);
248}
249
250String InspectorProfilerAgent::nextProfileId()
251{
252    long nextId = m_state->getLong(ProfilerAgentState::nextProfileId, 1);
253    m_state->setLong(ProfilerAgentState::nextProfileId, nextId + 1);
254    return String::number(nextId);
255}
256
257void InspectorProfilerAgent::idleFinished()
258{
259    if (!m_profileNameIdleTimeMap || !m_profileNameIdleTimeMap->size())
260        return;
261    ScriptProfiler::setIdle(false);
262    if (!m_idleStartTime)
263        return;
264
265    double idleTime = WTF::monotonicallyIncreasingTime() - m_idleStartTime;
266    m_idleStartTime = 0.0;
267    ProfileNameIdleTimeMap::iterator end = m_profileNameIdleTimeMap->end();
268    for (ProfileNameIdleTimeMap::iterator it = m_profileNameIdleTimeMap->begin(); it != end; ++it)
269        it->value += idleTime;
270}
271
272void InspectorProfilerAgent::idleStarted()
273{
274    if (!m_profileNameIdleTimeMap || !m_profileNameIdleTimeMap->size())
275        return;
276    m_idleStartTime = WTF::monotonicallyIncreasingTime();
277    ScriptProfiler::setIdle(true);
278}
279
280void InspectorProfilerAgent::willProcessTask()
281{
282    idleFinished();
283}
284
285void InspectorProfilerAgent::didProcessTask()
286{
287    idleStarted();
288}
289
290void InspectorProfilerAgent::willEnterNestedRunLoop()
291{
292    idleStarted();
293}
294
295void InspectorProfilerAgent::didLeaveNestedRunLoop()
296{
297    idleFinished();
298}
299
300void InspectorProfilerAgent::trace(Visitor* visitor)
301{
302    visitor->trace(m_injectedScriptManager);
303    InspectorBaseAgent::trace(visitor);
304}
305
306} // namespace blink
307
308