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
36using namespace JSC;
37
38namespace Inspector {
39
40InspectorScriptProfilerAgent::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
48InspectorScriptProfilerAgent::~InspectorScriptProfilerAgent()
49{
50}
51
52void InspectorScriptProfilerAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
53{
54}
55
56void 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
69void 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
96void 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
109bool InspectorScriptProfilerAgent::isAlreadyProfiling() const
110{
111 return m_activeEvaluateScript;
112}
113
114Seconds 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
129void 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
138static 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
153void 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)
167static 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
204void 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
233void 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
252