2 * Copyright (C) 2008, 2014 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "ProfileGenerator.h"
29 #include "CallFrame.h"
30 #include "CodeBlock.h"
32 #include "JSGlobalObject.h"
33 #include "JSStringRef.h"
34 #include "JSFunction.h"
35 #include "LegacyProfiler.h"
36 #include "JSCInlines.h"
38 #include "StackVisitor.h"
43 PassRefPtr<ProfileGenerator> ProfileGenerator::create(ExecState* exec, const String& title, unsigned uid)
45 return adoptRef(new ProfileGenerator(exec, title, uid));
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)
55 if (Debugger* debugger = exec->lexicalGlobalObject()->debugger())
56 m_debuggerPausedTimestamp = debugger->isPaused() ? currentTime() : NAN;
58 m_profile = Profile::create(title, uid);
59 m_currentNode = m_rootNode = m_profile->rootNode();
61 addParentForConsoleStart(exec);
64 class AddParentForConsoleStartFunctor {
66 AddParentForConsoleStartFunctor(ExecState* exec, RefPtr<ProfileNode>& rootNode, RefPtr<ProfileNode>& currentNode)
68 , m_hasSkippedFirstFrame(false)
69 , m_foundParent(false)
70 , m_rootNode(rootNode)
71 , m_currentNode(currentNode)
75 bool foundParent() const { return m_foundParent; }
77 StackVisitor::Status operator()(StackVisitor& visitor)
79 if (!m_hasSkippedFirstFrame) {
80 m_hasSkippedFirstFrame = true;
81 return StackVisitor::Continue;
86 visitor->computeLineAndColumn(line, column);
87 m_currentNode = ProfileNode::create(m_exec, LegacyProfiler::createCallIdentifier(m_exec, visitor->callee(), visitor->sourceURL(), line, column), m_rootNode.get());
88 m_currentNode->appendCall(ProfileNode::Call(currentTime()));
89 m_rootNode->spliceNode(m_currentNode.get());
92 return StackVisitor::Done;
97 bool m_hasSkippedFirstFrame;
99 RefPtr<ProfileNode>& m_rootNode;
100 RefPtr<ProfileNode>& m_currentNode;
103 void ProfileGenerator::addParentForConsoleStart(ExecState* exec)
105 AddParentForConsoleStartFunctor functor(exec, m_rootNode, m_currentNode);
106 exec->iterate(functor);
108 m_foundConsoleStartParent = functor.foundParent();
111 const String& ProfileGenerator::title() const
113 return m_profile->title();
116 void ProfileGenerator::beginCallEntry(ProfileNode* node, double startTime)
118 ASSERT_ARG(node, node);
120 if (isnan(startTime))
121 startTime = currentTime();
123 // If the debugger is paused when beginning, then don't set the start time. It
124 // will be fixed up when the debugger unpauses or the call entry ends.
125 if (!isnan(m_debuggerPausedTimestamp))
128 node->appendCall(ProfileNode::Call(startTime));
131 void ProfileGenerator::endCallEntry(ProfileNode* node)
133 ASSERT_ARG(node, node);
135 ProfileNode::Call& last = node->lastCall();
137 // If the debugger is paused, ignore the interval that ends now.
138 if (!isnan(m_debuggerPausedTimestamp) && !isnan(last.elapsedTime()))
141 // If paused and no time was accrued then the debugger was never unpaused. The call will
142 // have no time accrued and appear to have started when the debugger was paused.
143 if (!isnan(m_debuggerPausedTimestamp)) {
144 last.setStartTime(m_debuggerPausedTimestamp);
145 last.setElapsedTime(0.0);
149 // Otherwise, add the interval ending now to elapsed time.
150 double previousElapsedTime = isnan(last.elapsedTime()) ? 0.0 : last.elapsedTime();
151 double newlyElapsedTime = currentTime() - last.startTime();
152 last.setElapsedTime(previousElapsedTime + newlyElapsedTime);
155 void ProfileGenerator::willExecute(ExecState* callerCallFrame, const CallIdentifier& callIdentifier)
157 if (JAVASCRIPTCORE_PROFILE_WILL_EXECUTE_ENABLED()) {
158 CString name = callIdentifier.functionName().utf8();
159 CString url = callIdentifier.url().utf8();
160 JAVASCRIPTCORE_PROFILE_WILL_EXECUTE(m_profileGroup, const_cast<char*>(name.data()), const_cast<char*>(url.data()), callIdentifier.lineNumber(), callIdentifier.columnNumber());
169 RefPtr<ProfileNode> calleeNode = nullptr;
171 // Find or create a node for the callee call frame.
172 for (const RefPtr<ProfileNode>& child : m_currentNode->children()) {
173 if (child->callIdentifier() == callIdentifier)
178 calleeNode = ProfileNode::create(callerCallFrame, callIdentifier, m_currentNode.get());
179 m_currentNode->addChild(calleeNode);
182 m_currentNode = calleeNode;
183 beginCallEntry(calleeNode.get());
186 void ProfileGenerator::didExecute(ExecState* callerCallFrame, const CallIdentifier& callIdentifier)
188 if (JAVASCRIPTCORE_PROFILE_DID_EXECUTE_ENABLED()) {
189 CString name = callIdentifier.functionName().utf8();
190 CString url = callIdentifier.url().utf8();
191 JAVASCRIPTCORE_PROFILE_DID_EXECUTE(m_profileGroup, const_cast<char*>(name.data()), const_cast<char*>(url.data()), callIdentifier.lineNumber(), callIdentifier.columnNumber());
200 // Make a new node if the caller node has never seen this callee call frame before.
201 // This can happen if |console.profile()| is called several frames deep in the call stack.
202 ASSERT(m_currentNode);
203 if (m_currentNode->callIdentifier() != callIdentifier) {
204 RefPtr<ProfileNode> calleeNode = ProfileNode::create(callerCallFrame, callIdentifier, m_currentNode.get());
205 beginCallEntry(calleeNode.get(), m_currentNode->lastCall().startTime());
206 endCallEntry(calleeNode.get());
207 m_currentNode->spliceNode(calleeNode.release());
211 endCallEntry(m_currentNode.get());
212 m_currentNode = m_currentNode->parent();
215 void ProfileGenerator::exceptionUnwind(ExecState* handlerCallFrame, const CallIdentifier&)
217 // If the current node was called by the handler (==) or any
218 // more nested function (>) the we have exited early from it.
219 ASSERT(m_currentNode);
220 while (m_currentNode->callerCallFrame() >= handlerCallFrame) {
221 didExecute(m_currentNode->callerCallFrame(), m_currentNode->callIdentifier());
222 ASSERT(m_currentNode);
226 void ProfileGenerator::didPause(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&)
228 ASSERT(isnan(m_debuggerPausedTimestamp));
230 m_debuggerPausedTimestamp = currentTime();
232 for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent()) {
233 ProfileNode::Call& last = node->lastCall();
234 ASSERT(!isnan(last.startTime()));
236 double previousElapsedTime = isnan(last.elapsedTime()) ? 0.0 : last.elapsedTime();
237 double additionalElapsedTime = m_debuggerPausedTimestamp - last.startTime();
238 last.setStartTime(NAN);
239 last.setElapsedTime(previousElapsedTime + additionalElapsedTime);
243 void ProfileGenerator::didContinue(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&)
245 ASSERT(!isnan(m_debuggerPausedTimestamp));
247 for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent())
248 node->lastCall().setStartTime(m_debuggerPausedTimestamp);
250 m_debuggerPausedTimestamp = NAN;
253 void ProfileGenerator::stopProfiling()
255 for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent())
258 if (m_foundConsoleStartParent) {
259 removeProfileStart();
263 ASSERT(m_currentNode);
265 // Set the current node to the parent, because we are in a call that
266 // will not get didExecute call.
267 m_currentNode = m_currentNode->parent();
270 // The console.profile that started this ProfileGenerator will be the first child.
271 void ProfileGenerator::removeProfileStart()
273 ProfileNode* currentNode = nullptr;
274 for (ProfileNode* next = m_rootNode.get(); next; next = next->firstChild())
277 if (currentNode->callIdentifier().functionName() != "profile")
280 currentNode->parent()->removeChild(currentNode);
283 // The console.profileEnd that stopped this ProfileGenerator will be the last child.
284 void ProfileGenerator::removeProfileEnd()
286 ProfileNode* currentNode = nullptr;
287 for (ProfileNode* next = m_rootNode.get(); next; next = next->lastChild())
290 if (currentNode->callIdentifier().functionName() != "profileEnd")
293 ASSERT(currentNode->callIdentifier() == (currentNode->parent()->children()[currentNode->parent()->children().size() - 1])->callIdentifier());
294 currentNode->parent()->removeChild(currentNode);