Web Inspector: Timelines: can't reliably stop/start a recording
[WebKit-https.git] / Source / JavaScriptCore / inspector / agents / InspectorScriptProfilerAgent.cpp
1 /*
2  * Copyright (C) 2015-2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "InspectorScriptProfilerAgent.h"
28
29 #include "DeferGC.h"
30 #include "HeapInlines.h"
31 #include "InspectorEnvironment.h"
32 #include "SamplingProfiler.h"
33 #include "ScriptDebugServer.h"
34 #include <wtf/Stopwatch.h>
35
36 using namespace JSC;
37
38 namespace Inspector {
39
40 InspectorScriptProfilerAgent::InspectorScriptProfilerAgent(AgentContext& context)
41     : InspectorAgentBase("ScriptProfiler"_s)
42     , m_frontendDispatcher(std::make_unique<ScriptProfilerFrontendDispatcher>(context.frontendRouter))
43     , m_backendDispatcher(ScriptProfilerBackendDispatcher::create(context.backendDispatcher, this))
44     , m_environment(context.environment)
45 {
46 }
47
48 InspectorScriptProfilerAgent::~InspectorScriptProfilerAgent()
49 {
50 }
51
52 void InspectorScriptProfilerAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
53 {
54 }
55
56 void InspectorScriptProfilerAgent::willDestroyFrontendAndBackend(DisconnectReason)
57 {
58     // Stop tracking without sending results.
59     if (m_tracking) {
60         m_tracking = false;
61         m_activeEvaluateScript = false;
62         m_environment.scriptDebugServer().setProfilingClient(nullptr);
63
64         // Stop sampling without processing the samples.
65         stopSamplingWhenDisconnecting();
66     }
67 }
68
69 void InspectorScriptProfilerAgent::startTracking(ErrorString&, const bool* includeSamples)
70 {
71     if (m_tracking)
72         return;
73
74     m_tracking = true;
75
76 #if ENABLE(SAMPLING_PROFILER)
77     if (includeSamples && *includeSamples) {
78         VM& vm = m_environment.scriptDebugServer().vm();
79         SamplingProfiler& samplingProfiler = vm.ensureSamplingProfiler(m_environment.executionStopwatch());
80
81         LockHolder locker(samplingProfiler.getLock());
82         samplingProfiler.setStopWatch(locker, m_environment.executionStopwatch());
83         samplingProfiler.noticeCurrentThreadAsJSCExecutionThread(locker);
84         samplingProfiler.start(locker);
85         m_enabledSamplingProfiler = true;
86     }
87 #else
88     UNUSED_PARAM(includeSamples);
89 #endif // ENABLE(SAMPLING_PROFILER)
90
91     m_environment.scriptDebugServer().setProfilingClient(this);
92
93     m_frontendDispatcher->trackingStart(m_environment.executionStopwatch()->elapsedTime().seconds());
94 }
95
96 void InspectorScriptProfilerAgent::stopTracking(ErrorString&)
97 {
98     if (!m_tracking)
99         return;
100
101     m_tracking = false;
102     m_activeEvaluateScript = false;
103
104     m_environment.scriptDebugServer().setProfilingClient(nullptr);
105
106     trackingComplete();
107 }
108
109 bool InspectorScriptProfilerAgent::isAlreadyProfiling() const
110 {
111     return m_activeEvaluateScript;
112 }
113
114 Seconds InspectorScriptProfilerAgent::willEvaluateScript()
115 {
116     m_activeEvaluateScript = true;
117
118 #if ENABLE(SAMPLING_PROFILER)
119     if (m_enabledSamplingProfiler) {
120         SamplingProfiler* samplingProfiler = m_environment.scriptDebugServer().vm().samplingProfiler();
121         RELEASE_ASSERT(samplingProfiler);
122         samplingProfiler->noticeCurrentThreadAsJSCExecutionThread();
123     }
124 #endif
125
126     return m_environment.executionStopwatch()->elapsedTime();
127 }
128
129 void InspectorScriptProfilerAgent::didEvaluateScript(Seconds startTime, ProfilingReason reason)
130 {
131     m_activeEvaluateScript = false;
132
133     Seconds endTime = m_environment.executionStopwatch()->elapsedTime();
134
135     addEvent(startTime, endTime, reason);
136 }
137
138 static Protocol::ScriptProfiler::EventType toProtocol(ProfilingReason reason)
139 {
140     switch (reason) {
141     case ProfilingReason::API:
142         return Protocol::ScriptProfiler::EventType::API;
143     case ProfilingReason::Microtask:
144         return Protocol::ScriptProfiler::EventType::Microtask;
145     case ProfilingReason::Other:
146         return Protocol::ScriptProfiler::EventType::Other;
147     }
148
149     ASSERT_NOT_REACHED();
150     return Protocol::ScriptProfiler::EventType::Other;
151 }
152
153 void InspectorScriptProfilerAgent::addEvent(Seconds startTime, Seconds endTime, ProfilingReason reason)
154 {
155     ASSERT(endTime >= startTime);
156
157     auto event = Protocol::ScriptProfiler::Event::create()
158         .setStartTime(startTime.seconds())
159         .setEndTime(endTime.seconds())
160         .setType(toProtocol(reason))
161         .release();
162
163     m_frontendDispatcher->trackingUpdate(WTFMove(event));
164 }
165
166 #if ENABLE(SAMPLING_PROFILER)
167 static Ref<Protocol::ScriptProfiler::Samples> buildSamples(VM& vm, Vector<SamplingProfiler::StackTrace>&& samplingProfilerStackTraces)
168 {
169     auto stackTraces = JSON::ArrayOf<Protocol::ScriptProfiler::StackTrace>::create();
170     for (SamplingProfiler::StackTrace& stackTrace : samplingProfilerStackTraces) {
171         auto frames = JSON::ArrayOf<Protocol::ScriptProfiler::StackFrame>::create();
172         for (SamplingProfiler::StackFrame& stackFrame : stackTrace.frames) {
173             auto frameObject = Protocol::ScriptProfiler::StackFrame::create()
174                 .setSourceID(String::number(stackFrame.sourceID()))
175                 .setName(stackFrame.displayName(vm))
176                 .setLine(stackFrame.functionStartLine())
177                 .setColumn(stackFrame.functionStartColumn())
178                 .setUrl(stackFrame.url())
179                 .release();
180
181             if (stackFrame.hasExpressionInfo()) {
182                 Ref<Protocol::ScriptProfiler::ExpressionLocation> expressionLocation = Protocol::ScriptProfiler::ExpressionLocation::create()
183                     .setLine(stackFrame.lineNumber())
184                     .setColumn(stackFrame.columnNumber())
185                     .release();
186                 frameObject->setExpressionLocation(WTFMove(expressionLocation));
187             }
188
189             frames->addItem(WTFMove(frameObject));
190         }
191         Ref<Protocol::ScriptProfiler::StackTrace> inspectorStackTrace = Protocol::ScriptProfiler::StackTrace::create()
192             .setTimestamp(stackTrace.timestamp.seconds())
193             .setStackFrames(WTFMove(frames))
194             .release();
195         stackTraces->addItem(WTFMove(inspectorStackTrace));
196     }
197
198     return Protocol::ScriptProfiler::Samples::create()
199         .setStackTraces(WTFMove(stackTraces))
200         .release();
201 }
202 #endif // ENABLE(SAMPLING_PROFILER)
203
204 void InspectorScriptProfilerAgent::trackingComplete()
205 {
206     auto timestamp = m_environment.executionStopwatch()->elapsedTime().seconds();
207
208 #if ENABLE(SAMPLING_PROFILER)
209     if (m_enabledSamplingProfiler) {
210         VM& vm = m_environment.scriptDebugServer().vm();
211         JSLockHolder lock(vm);
212         DeferGC deferGC(vm.heap); // This is required because we will have raw pointers into the heap after we releaseStackTraces().
213         SamplingProfiler* samplingProfiler = vm.samplingProfiler();
214         RELEASE_ASSERT(samplingProfiler);
215
216         LockHolder locker(samplingProfiler->getLock());
217         samplingProfiler->pause(locker);
218         Vector<SamplingProfiler::StackTrace> stackTraces = samplingProfiler->releaseStackTraces(locker);
219         locker.unlockEarly();
220
221         Ref<Protocol::ScriptProfiler::Samples> samples = buildSamples(vm, WTFMove(stackTraces));
222
223         m_enabledSamplingProfiler = false;
224
225         m_frontendDispatcher->trackingComplete(timestamp, WTFMove(samples));
226     } else
227         m_frontendDispatcher->trackingComplete(timestamp, nullptr);
228 #else
229     m_frontendDispatcher->trackingComplete(timestamp, nullptr);
230 #endif // ENABLE(SAMPLING_PROFILER)
231 }
232
233 void InspectorScriptProfilerAgent::stopSamplingWhenDisconnecting()
234 {
235 #if ENABLE(SAMPLING_PROFILER)
236     if (!m_enabledSamplingProfiler)
237         return;
238
239     VM& vm = m_environment.scriptDebugServer().vm();
240     JSLockHolder lock(vm);
241     SamplingProfiler* samplingProfiler = vm.samplingProfiler();
242     RELEASE_ASSERT(samplingProfiler);
243     LockHolder locker(samplingProfiler->getLock());
244     samplingProfiler->pause(locker);
245     samplingProfiler->clearData(locker);
246
247     m_enabledSamplingProfiler = false;
248 #endif
249 }
250
251 } // namespace Inspector