7e43446f350a46e753e9ccc4a9d979149457bd9a
[WebKit-https.git] / Source / JavaScriptCore / profiler / ProfileGenerator.cpp
1 /*
2  * Copyright (C) 2008, 2014 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "ProfileGenerator.h"
28
29 #include "CallFrame.h"
30 #include "CodeBlock.h"
31 #include "Debugger.h"
32 #include "JSGlobalObject.h"
33 #include "JSStringRef.h"
34 #include "JSFunction.h"
35 #include "LegacyProfiler.h"
36 #include "JSCInlines.h"
37 #include "Profile.h"
38 #include "StackVisitor.h"
39 #include "Tracing.h"
40
41 namespace JSC {
42
43 PassRefPtr<ProfileGenerator> ProfileGenerator::create(ExecState* exec, const String& title, unsigned uid)
44 {
45     return adoptRef(new ProfileGenerator(exec, title, uid));
46 }
47
48 ProfileGenerator::ProfileGenerator(ExecState* exec, const String& title, unsigned uid)
49     : m_origin(exec ? exec->lexicalGlobalObject() : nullptr)
50     , m_profileGroup(exec ? exec->lexicalGlobalObject()->profileGroup() : 0)
51     , m_debuggerPausedTimestamp(NAN)
52     , m_foundConsoleStartParent(false)
53 {
54     if (Debugger* debugger = exec->lexicalGlobalObject()->debugger())
55         m_debuggerPausedTimestamp = debugger->isPaused() ? currentTime() : NAN;
56
57     m_profile = Profile::create(title, uid);
58     m_currentNode = m_rootNode = m_profile->rootNode();
59     if (exec)
60         addParentForConsoleStart(exec);
61 }
62
63 class AddParentForConsoleStartFunctor {
64 public:
65     AddParentForConsoleStartFunctor(ExecState* exec, RefPtr<ProfileNode>& rootNode, RefPtr<ProfileNode>& currentNode)
66         : m_exec(exec)
67         , m_hasSkippedFirstFrame(false)
68         , m_foundParent(false)
69         , m_rootNode(rootNode)
70         , m_currentNode(currentNode)
71     {
72     }
73
74     bool foundParent() const { return m_foundParent; }
75
76     StackVisitor::Status operator()(StackVisitor& visitor)
77     {
78         if (!m_hasSkippedFirstFrame) {
79             m_hasSkippedFirstFrame = true;
80             return StackVisitor::Continue;
81         }
82
83         unsigned line = 0;
84         unsigned column = 0;
85         visitor->computeLineAndColumn(line, column);
86         m_currentNode = ProfileNode::create(m_exec, LegacyProfiler::createCallIdentifier(m_exec, visitor->callee(), visitor->sourceURL(), line, column), m_rootNode.get());
87         m_currentNode->appendCall(ProfileNode::Call(currentTime()));
88         m_rootNode->spliceNode(m_currentNode.get());
89
90         m_foundParent = true;
91         return StackVisitor::Done;
92     }
93
94 private:
95     ExecState* m_exec;
96     bool m_hasSkippedFirstFrame;
97     bool m_foundParent;
98     RefPtr<ProfileNode>& m_rootNode;
99     RefPtr<ProfileNode>& m_currentNode;
100 };
101
102 void ProfileGenerator::addParentForConsoleStart(ExecState* exec)
103 {
104     AddParentForConsoleStartFunctor functor(exec, m_rootNode, m_currentNode);
105     exec->iterate(functor);
106
107     m_foundConsoleStartParent = functor.foundParent();
108 }
109
110 const String& ProfileGenerator::title() const
111 {
112     return m_profile->title();
113 }
114
115 void ProfileGenerator::beginCallEntry(ProfileNode* node, double startTime)
116 {
117     ASSERT_ARG(node, node);
118
119     if (isnan(startTime))
120         startTime = currentTime();
121
122     // If the debugger is paused when beginning, then don't set the start time. It
123     // will be fixed up when the debugger unpauses or the call entry ends.
124     if (!isnan(m_debuggerPausedTimestamp))
125         startTime = NAN;
126
127     node->appendCall(ProfileNode::Call(startTime));
128 }
129
130 void ProfileGenerator::endCallEntry(ProfileNode* node)
131 {
132     ASSERT_ARG(node, node);
133
134     ProfileNode::Call& last = node->lastCall();
135
136     // If the debugger is paused, ignore the interval that ends now.
137     if (!isnan(m_debuggerPausedTimestamp) && !isnan(last.elapsedTime()))
138         return;
139
140     // If paused and no time was accrued then the debugger was never unpaused. The call will
141     // have no time accrued and appear to have started when the debugger was paused.
142     if (!isnan(m_debuggerPausedTimestamp)) {
143         last.setStartTime(m_debuggerPausedTimestamp);
144         last.setElapsedTime(0.0);
145         return;
146     }
147
148     // Otherwise, add the interval ending now to elapsed time.
149     double previousElapsedTime = isnan(last.elapsedTime()) ? 0.0 : last.elapsedTime();
150     double newlyElapsedTime = currentTime() - last.startTime();
151     last.setElapsedTime(previousElapsedTime + newlyElapsedTime);
152 }
153
154 void ProfileGenerator::willExecute(ExecState* callerCallFrame, const CallIdentifier& callIdentifier)
155 {
156     if (JAVASCRIPTCORE_PROFILE_WILL_EXECUTE_ENABLED()) {
157         CString name = callIdentifier.functionName().utf8();
158         CString url = callIdentifier.url().utf8();
159         JAVASCRIPTCORE_PROFILE_WILL_EXECUTE(m_profileGroup, const_cast<char*>(name.data()), const_cast<char*>(url.data()), callIdentifier.lineNumber(), callIdentifier.columnNumber());
160     }
161
162     if (!m_origin)
163         return;
164
165     RefPtr<ProfileNode> calleeNode = nullptr;
166
167     // Find or create a node for the callee call frame.
168     for (const RefPtr<ProfileNode>& child : m_currentNode->children()) {
169         if (child->callIdentifier() == callIdentifier)
170             calleeNode = child;
171     }
172
173     if (!calleeNode) {
174         calleeNode = ProfileNode::create(callerCallFrame, callIdentifier, m_currentNode.get());
175         m_currentNode->addChild(calleeNode);
176     }
177
178     m_currentNode = calleeNode;
179     beginCallEntry(calleeNode.get());
180 }
181
182 void ProfileGenerator::didExecute(ExecState* callerCallFrame, const CallIdentifier& callIdentifier)
183 {
184     if (JAVASCRIPTCORE_PROFILE_DID_EXECUTE_ENABLED()) {
185         CString name = callIdentifier.functionName().utf8();
186         CString url = callIdentifier.url().utf8();
187         JAVASCRIPTCORE_PROFILE_DID_EXECUTE(m_profileGroup, const_cast<char*>(name.data()), const_cast<char*>(url.data()), callIdentifier.lineNumber(), callIdentifier.columnNumber());
188     }
189
190     if (!m_origin)
191         return;
192
193     // Make a new node if the caller node has never seen this callee call frame before.
194     // This can happen if |console.profile()| is called several frames deep in the call stack.
195     ASSERT(m_currentNode);
196     if (m_currentNode->callIdentifier() != callIdentifier) {
197         RefPtr<ProfileNode> calleeNode = ProfileNode::create(callerCallFrame, callIdentifier, m_currentNode.get());
198         beginCallEntry(calleeNode.get(), m_currentNode->lastCall().startTime());
199         endCallEntry(calleeNode.get());
200         m_currentNode->spliceNode(calleeNode.release());
201         return;
202     }
203
204     endCallEntry(m_currentNode.get());
205     m_currentNode = m_currentNode->parent();
206 }
207
208 void ProfileGenerator::exceptionUnwind(ExecState* handlerCallFrame, const CallIdentifier&)
209 {
210     // If the current node was called by the handler (==) or any
211     // more nested function (>) the we have exited early from it.
212     ASSERT(m_currentNode);
213     while (m_currentNode->callerCallFrame() >= handlerCallFrame) {
214         didExecute(m_currentNode->callerCallFrame(), m_currentNode->callIdentifier());
215         ASSERT(m_currentNode);
216     }
217 }
218
219 void ProfileGenerator::didPause(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&)
220 {
221     ASSERT(isnan(m_debuggerPausedTimestamp));
222
223     m_debuggerPausedTimestamp = currentTime();
224
225     for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent()) {
226         ProfileNode::Call& last = node->lastCall();
227         ASSERT(!isnan(last.startTime()));
228
229         double previousElapsedTime = isnan(last.elapsedTime()) ? 0.0 : last.elapsedTime();
230         double additionalElapsedTime = m_debuggerPausedTimestamp - last.startTime();
231         last.setStartTime(NAN);
232         last.setElapsedTime(previousElapsedTime + additionalElapsedTime);
233     }
234 }
235
236 void ProfileGenerator::didContinue(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&)
237 {
238     ASSERT(!isnan(m_debuggerPausedTimestamp));
239
240     for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent())
241         node->lastCall().setStartTime(m_debuggerPausedTimestamp);
242
243     m_debuggerPausedTimestamp = NAN;
244 }
245
246 void ProfileGenerator::stopProfiling()
247 {
248     for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent())
249         endCallEntry(node);
250
251     if (m_foundConsoleStartParent) {
252         removeProfileStart();
253         removeProfileEnd();
254     }
255
256     ASSERT(m_currentNode);
257
258     // Set the current node to the parent, because we are in a call that
259     // will not get didExecute call.
260     m_currentNode = m_currentNode->parent();
261 }
262
263 // The console.profile that started this ProfileGenerator will be the first child.
264 void ProfileGenerator::removeProfileStart()
265 {
266     ProfileNode* currentNode = nullptr;
267     for (ProfileNode* next = m_rootNode.get(); next; next = next->firstChild())
268         currentNode = next;
269
270     if (currentNode->callIdentifier().functionName() != "profile")
271         return;
272
273     currentNode->parent()->removeChild(currentNode);
274 }
275
276 // The console.profileEnd that stopped this ProfileGenerator will be the last child.
277 void ProfileGenerator::removeProfileEnd()
278 {
279     ProfileNode* currentNode = nullptr;
280     for (ProfileNode* next = m_rootNode.get(); next; next = next->lastChild())
281         currentNode = next;
282
283     if (currentNode->callIdentifier().functionName() != "profileEnd")
284         return;
285
286     ASSERT(currentNode->callIdentifier() == (currentNode->parent()->children()[currentNode->parent()->children().size() - 1])->callIdentifier());
287     currentNode->parent()->removeChild(currentNode);
288 }
289
290 } // namespace JSC