https://bugs.webkit.org/show_bug.cgi?id=136351
Reviewed by Timothy Hatcher.
Source/JavaScriptCore:
Now that we have a stopwatch to provide pause-aware timing data, we can remove the
profiler's handling of debugger pause/continue callbacks. The debugger agent accounts
for suspended execution by pausing and resuming the stopwatch.
* API/JSProfilerPrivate.cpp:
(JSStartProfiling): Use a fresh stopwatch when profiling from the JSC API.
* inspector/InspectorEnvironment.h:
* inspector/JSGlobalObjectInspectorController.cpp:
(Inspector::JSGlobalObjectInspectorController::JSGlobalObjectInspectorController):
(Inspector::JSGlobalObjectInspectorController::executionStopwatch):
* inspector/JSGlobalObjectInspectorController.h:
* inspector/ScriptDebugServer.cpp:
(Inspector::ScriptDebugServer::handlePause):
* inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::InspectorDebuggerAgent::didPause):
(Inspector::InspectorDebuggerAgent::breakpointActionProbe):
(Inspector::InspectorDebuggerAgent::didContinue):
* inspector/agents/InspectorDebuggerAgent.h:
* profiler/LegacyProfiler.cpp:
(JSC::LegacyProfiler::profiler): Use nullptr.
(JSC::LegacyProfiler::startProfiling): Hand off a stopwatch to the profile generator.
(JSC::LegacyProfiler::stopProfiling): Use nullptr.
(JSC::LegacyProfiler::didPause): Deleted.
(JSC::LegacyProfiler::didContinue): Deleted.
* profiler/LegacyProfiler.h:
* profiler/Profile.cpp: The root node should always have a start time of 0.0.
(JSC::Profile::Profile):
* profiler/ProfileGenerator.cpp: Remove debugger pause/continue callbacks and the
timestamp member that was used to track time elapsed by the debugger. Just use the
stopwatch's elapsed times to generate start/elapsed times for function calls.
(JSC::ProfileGenerator::create):
(JSC::ProfileGenerator::ProfileGenerator):
(JSC::AddParentForConsoleStartFunctor::operator()): The parent node of |console.profile|
should have a start time of 0.0, since it represents the starting node of profiling.
(JSC::ProfileGenerator::beginCallEntry):
(JSC::ProfileGenerator::endCallEntry):
(JSC::ProfileGenerator::didPause): Deleted.
(JSC::ProfileGenerator::didContinue): Deleted.
* profiler/ProfileGenerator.h:
Source/WebCore:
To avoid counting time elapsed while the debugger is paused, timeline records should
keep track of time elapsed since the start of timeline capturing, rather than wall clock
timestamps. We can easily compute elapsed time by sharing a Stopwatch instance through the
inspector environment. The stopwatch runs with timelines and is paused with the debugger,
so subsequent time measurements will not include time elapsed while the debugger is paused.
This refactoring is safe because start and end times are only used to graph records; the
timestamp's actual value is irrelevant and is not displayed in the user interface. Date
timestamps are still included with network-related records as part of their header data.
No new tests, because we cannot reliably test timing changes induced by debugger pauses.
It is possible for records to accrue time before the debugger pauses or after it resumes.
* inspector/InspectorCSSAgent.cpp: Remove unnecessary include.
* inspector/InspectorController.cpp:
(WebCore::InspectorController::InspectorController):
(WebCore::InspectorController::executionStopwatch): Add a shared stopwatch.
* inspector/InspectorController.h:
* inspector/InspectorPageAgent.cpp:
(WebCore::InspectorPageAgent::timestamp): Redirect to the shared stopwatch.
(WebCore::InspectorPageAgent::domContentEventFired):
(WebCore::InspectorPageAgent::loadEventFired):
* inspector/InspectorPageAgent.h:
* inspector/InspectorResourceAgent.cpp:
(WebCore::InspectorResourceAgent::timestamp): Redirect to the shared stopwatch.
(WebCore::InspectorResourceAgent::willSendRequest):
(WebCore::InspectorResourceAgent::didReceiveResponse):
(WebCore::InspectorResourceAgent::didReceiveData):
(WebCore::InspectorResourceAgent::didFinishLoading):
(WebCore::InspectorResourceAgent::didFailLoading):
(WebCore::InspectorResourceAgent::didLoadResourceFromMemoryCache):
(WebCore::InspectorResourceAgent::willSendWebSocketHandshakeRequest):
(WebCore::InspectorResourceAgent::didReceiveWebSocketHandshakeResponse):
(WebCore::InspectorResourceAgent::didCloseWebSocket):
(WebCore::InspectorResourceAgent::didReceiveWebSocketFrame):
(WebCore::InspectorResourceAgent::didSendWebSocketFrame):
(WebCore::InspectorResourceAgent::didReceiveWebSocketFrameError):
* inspector/InspectorResourceAgent.h:
* inspector/InspectorTimelineAgent.cpp:
(WebCore::InspectorTimelineAgent::internalStart): Start and stop the stopwatch with timelines.
(WebCore::InspectorTimelineAgent::internalStop):
(WebCore::InspectorTimelineAgent::timestamp): Redirect to the shared stopwatch.
(WebCore::startProfiling):
(WebCore::InspectorTimelineAgent::startFromConsole):
(WebCore::InspectorTimelineAgent::willCallFunction):
(WebCore::InspectorTimelineAgent::willEvaluateScript):
(WebCore::TimelineTimeConverter::reset): Deleted.
* inspector/InspectorTimelineAgent.h:
(WebCore::TimelineTimeConverter::TimelineTimeConverter): Deleted.
(WebCore::TimelineTimeConverter::fromMonotonicallyIncreasingTime): Deleted.
(WebCore::InspectorTimelineAgent::timeConverter): Deleted.
* inspector/TimelineRecordFactory.cpp:
* inspector/WorkerInspectorController.cpp:
(WebCore::WorkerInspectorController::WorkerInspectorController):
(WebCore::WorkerInspectorController::executionStopwatch): Add a shared stopwatch.
* inspector/WorkerInspectorController.h:
Source/WebInspectorUI:
Don't update the timeline's current time when the debugger is paused.
Start and end times for timeline records are now in seconds elapsed since timeline
recording started, rather than milliseconds since the epoch. Also convert code that
tracks page/resource load timings to use elapsed times rather than timestamps.
Add a workaround to preserve compatibility with old backends. Convert legacy timestamps
in multiple agents to elapsed times.
* UserInterface/Controllers/FrameResourceManager.js:
(WebInspector.FrameResourceManager.prototype.resourceRequestWillBeSent):
(WebInspector.FrameResourceManager.prototype.resourceRequestWasServedFromMemoryCache):
(WebInspector.FrameResourceManager.prototype.resourceRequestDidReceiveResponse):
(WebInspector.FrameResourceManager.prototype.resourceRequestDidReceiveData):
(WebInspector.FrameResourceManager.prototype.resourceRequestDidFinishLoading):
(WebInspector.FrameResourceManager.prototype.resourceRequestDidFailLoading):
(WebInspector.FrameResourceManager.prototype._addNewResourceToFrame):
* UserInterface/Controllers/ProbeManager.js:
* UserInterface/Controllers/TimelineManager.js:
(WebInspector.TimelineManager.prototype.computeElapsedTime): Forward to the active TimelineRecording.
(WebInspector.TimelineManager.prototype.eventRecorded.processRecord):
(WebInspector.TimelineManager.prototype.eventRecorded):
(WebInspector.TimelineManager.prototype.pageDidLoad):
(WebInspector.TimelineManager.prototype._loadNewRecording):
* UserInterface/Models/Probe.js:
(WebInspector.ProbeSample):
* UserInterface/Models/Resource.js:
(WebInspector.Resource.prototype.updateForRedirectResponse):
(WebInspector.Resource.prototype.updateForResponse):
(WebInspector.Resource.prototype.increaseSize):
(WebInspector.Resource.prototype.markAsFinished):
(WebInspector.Resource.prototype.markAsFailed):
(WebInspector.Resource.prototype.revertMarkAsFinished):
* UserInterface/Models/TimelineRecording.js:
(WebInspector.TimelineRecording.prototype.computeElapsedTime):
* UserInterface/Views/TimelineContentView.js:
(WebInspector.TimelineContentView.prototype._debuggerPaused):
(WebInspector.TimelineContentView.prototype._debuggerResumed):
Source/WTF:
* WTF.vcxproj/WTF.vcxproj:
* WTF.vcxproj/WTF.vcxproj.filters:
* WTF.xcodeproj/project.pbxproj:
* wtf/CMakeLists.txt:
* wtf/Stopwatch.h: Added. This implements a refcounted monotonic stopwatch.
(WTF::Stopwatch::create):
(WTF::Stopwatch::Stopwatch):
(WTF::Stopwatch::reset):
(WTF::Stopwatch::start):
(WTF::Stopwatch::stop):
(WTF::Stopwatch::elapsedTime):
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@175203
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
void JSStartProfiling(JSContextRef ctx, JSStringRef title)
{
- LegacyProfiler::profiler()->startProfiling(toJS(ctx), title->string());
+ // Use an independent stopwatch for API-initiated profiling, since the user will expect it
+ // to be relative to when their command was issued.
+ RefPtr<Stopwatch> stopwatch = Stopwatch::create();
+ stopwatch->start();
+ LegacyProfiler::profiler()->startProfiling(toJS(ctx), title->string(), stopwatch.release());
}
void JSEndProfiling(JSContextRef ctx, JSStringRef title)
+2014-10-18 Brian J. Burg <burg@cs.washington.edu>
+
+ Web Inspector: timelines should not count time elapsed while paused in the debugger
+ https://bugs.webkit.org/show_bug.cgi?id=136351
+
+ Reviewed by Timothy Hatcher.
+
+ Now that we have a stopwatch to provide pause-aware timing data, we can remove the
+ profiler's handling of debugger pause/continue callbacks. The debugger agent accounts
+ for suspended execution by pausing and resuming the stopwatch.
+
+ * API/JSProfilerPrivate.cpp:
+ (JSStartProfiling): Use a fresh stopwatch when profiling from the JSC API.
+ * inspector/InspectorEnvironment.h:
+ * inspector/JSGlobalObjectInspectorController.cpp:
+ (Inspector::JSGlobalObjectInspectorController::JSGlobalObjectInspectorController):
+ (Inspector::JSGlobalObjectInspectorController::executionStopwatch):
+ * inspector/JSGlobalObjectInspectorController.h:
+ * inspector/ScriptDebugServer.cpp:
+ (Inspector::ScriptDebugServer::handlePause):
+ * inspector/agents/InspectorDebuggerAgent.cpp:
+ (Inspector::InspectorDebuggerAgent::didPause):
+ (Inspector::InspectorDebuggerAgent::breakpointActionProbe):
+ (Inspector::InspectorDebuggerAgent::didContinue):
+ * inspector/agents/InspectorDebuggerAgent.h:
+ * profiler/LegacyProfiler.cpp:
+ (JSC::LegacyProfiler::profiler): Use nullptr.
+ (JSC::LegacyProfiler::startProfiling): Hand off a stopwatch to the profile generator.
+ (JSC::LegacyProfiler::stopProfiling): Use nullptr.
+ (JSC::LegacyProfiler::didPause): Deleted.
+ (JSC::LegacyProfiler::didContinue): Deleted.
+ * profiler/LegacyProfiler.h:
+ * profiler/Profile.cpp: The root node should always have a start time of 0.0.
+ (JSC::Profile::Profile):
+ * profiler/ProfileGenerator.cpp: Remove debugger pause/continue callbacks and the
+ timestamp member that was used to track time elapsed by the debugger. Just use the
+ stopwatch's elapsed times to generate start/elapsed times for function calls.
+
+ (JSC::ProfileGenerator::create):
+ (JSC::ProfileGenerator::ProfileGenerator):
+ (JSC::AddParentForConsoleStartFunctor::operator()): The parent node of |console.profile|
+ should have a start time of 0.0, since it represents the starting node of profiling.
+
+ (JSC::ProfileGenerator::beginCallEntry):
+ (JSC::ProfileGenerator::endCallEntry):
+ (JSC::ProfileGenerator::didPause): Deleted.
+ (JSC::ProfileGenerator::didContinue): Deleted.
+ * profiler/ProfileGenerator.h:
+
2014-10-24 Mark Lam <mark.lam@apple.com>
Simplified IndexingType's hasAnyArrayStorage().
#include "CallData.h"
+namespace WTF {
+class Stopwatch;
+}
+
namespace JSC {
class SourceCode;
}
virtual void willCallInjectedScriptFunction(JSC::ExecState*, const String& scriptName, int scriptLine) = 0;
virtual void didCallInjectedScriptFunction(JSC::ExecState*) = 0;
virtual void frontendInitialized() = 0;
+ virtual PassRefPtr<WTF::Stopwatch> executionStopwatch() = 0;
};
} // namespace Inspector
#include "ScriptArguments.h"
#include "ScriptCallStack.h"
#include "ScriptCallStackFactory.h"
+#include <wtf/Stopwatch.h>
+
#include <cxxabi.h>
#include <dlfcn.h>
#include <execinfo.h>
: m_globalObject(globalObject)
, m_injectedScriptManager(std::make_unique<InjectedScriptManager>(*this, InjectedScriptHost::create()))
, m_inspectorFrontendChannel(nullptr)
+ , m_executionStopwatch(Stopwatch::create())
, m_includeNativeCallStackWithExceptions(true)
, m_isAutomaticInspection(false)
#if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS)
m_agents.append(WTF::move(runtimeAgent));
m_agents.append(WTF::move(consoleAgent));
m_agents.append(WTF::move(debuggerAgent));
+
+ m_executionStopwatch->start();
}
JSGlobalObjectInspectorController::~JSGlobalObjectInspectorController()
#endif
}
+PassRefPtr<Stopwatch> JSGlobalObjectInspectorController::executionStopwatch()
+{
+ return m_executionStopwatch;
+}
+
} // namespace Inspector
#endif // ENABLE(INSPECTOR)
#include "AugmentableInspectorController.h"
#endif
+namespace WTF {
+class Stopwatch;
+}
+
+
namespace JSC {
class ConsoleClient;
class ExecState;
virtual void willCallInjectedScriptFunction(JSC::ExecState*, const String&, int) override { }
virtual void didCallInjectedScriptFunction(JSC::ExecState*) override { }
virtual void frontendInitialized() override;
+ virtual PassRefPtr<WTF::Stopwatch> executionStopwatch() override;
#if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS)
virtual AugmentableInspectorControllerClient* augmentableInspectorControllerClient() const override { return m_augmentingClient; }
InspectorAgentRegistry m_agents;
InspectorFrontendChannel* m_inspectorFrontendChannel;
RefPtr<InspectorBackendDispatcher> m_inspectorBackendDispatcher;
+ RefPtr<WTF::Stopwatch> m_executionStopwatch;
bool m_includeNativeCallStackWithExceptions;
bool m_isAutomaticInspection;
#include "JSJavaScriptCallFrame.h"
#include "JSLock.h"
#include "JavaScriptCallFrame.h"
-#include "LegacyProfiler.h"
#include "ScriptValue.h"
#include "SourceProvider.h"
#include <wtf/NeverDestroyed.h>
void ScriptDebugServer::handlePause(Debugger::ReasonForPause, JSGlobalObject* vmEntryGlobalObject)
{
dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause);
- LegacyProfiler::profiler()->didPause(currentDebuggerCallFrame());
didPause(vmEntryGlobalObject);
m_doneProcessingDebuggerEvents = false;
runEventLoopWhilePaused();
didContinue(vmEntryGlobalObject);
- LegacyProfiler::profiler()->didContinue(currentDebuggerCallFrame());
dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue);
}
#include "ScriptDebugServer.h"
#include "ScriptObject.h"
#include "ScriptValue.h"
+#include <wtf/Stopwatch.h>
#include <wtf/text/WTFString.h>
namespace Inspector {
if (m_listener)
m_listener->didPause();
+
+ m_injectedScriptManager->inspectorEnvironment().executionStopwatch()->stop();
}
void InspectorDebuggerAgent::breakpointActionSound(int breakpointActionIdentifier)
.setProbeId(action.identifier)
.setSampleId(sampleId)
.setBatchId(hitCount)
- .setTimestamp(monotonicallyIncreasingTime())
+ .setTimestamp(m_injectedScriptManager->inspectorEnvironment().executionStopwatch()->elapsedTime())
.setPayload(payload.release());
m_frontendDispatcher->didSampleProbe(result.release());
{
m_pausedScriptState = nullptr;
m_currentCallStack = Deprecated::ScriptValue();
+ m_injectedScriptManager->inspectorEnvironment().executionStopwatch()->start();
clearBreakDetails();
m_frontendDispatcher->resumed();
m_breakAuxData = nullptr;
}
-
} // namespace Inspector
#endif // ENABLE(INSPECTOR)
#include <wtf/Vector.h>
#include <wtf/text/StringHash.h>
+namespace WTF {
+class Stopwatch;
+}
+
namespace Inspector {
class InjectedScript;
RefPtr<InspectorObject> m_breakAuxData;
bool m_enabled;
bool m_javaScriptPauseScheduled;
+ RefPtr<WTF::Stopwatch> m_stopwatch;
int m_nextProbeSampleId;
};
#include "CallFrame.h"
#include "CodeBlock.h"
#include "CommonIdentifiers.h"
-#include "DebuggerCallFrame.h"
#include "InternalFunction.h"
#include "JSFunction.h"
#include "JSGlobalObject.h"
static CallIdentifier createCallIdentifierFromFunctionImp(ExecState*, JSObject*, const String& defaultSourceURL, unsigned defaultLineNumber, unsigned defaultColumnNumber);
-LegacyProfiler* LegacyProfiler::s_sharedLegacyProfiler = 0;
+LegacyProfiler* LegacyProfiler::s_sharedLegacyProfiler = nullptr;
LegacyProfiler* LegacyProfiler::profiler()
{
if (!s_sharedLegacyProfiler)
s_sharedLegacyProfiler = new LegacyProfiler();
return s_sharedLegacyProfiler;
-}
+}
-void LegacyProfiler::startProfiling(ExecState* exec, const String& title)
+void LegacyProfiler::startProfiling(ExecState* exec, const String& title, PassRefPtr<Stopwatch> stopwatch)
{
if (!exec)
return;
}
exec->vm().setEnabledProfiler(this);
- RefPtr<ProfileGenerator> profileGenerator = ProfileGenerator::create(exec, title, ++ProfilesUID);
+ RefPtr<ProfileGenerator> profileGenerator = ProfileGenerator::create(exec, title, ++ProfilesUID, stopwatch);
m_currentProfiles.append(profileGenerator);
}
PassRefPtr<Profile> LegacyProfiler::stopProfiling(ExecState* exec, const String& title)
{
if (!exec)
- return 0;
+ return nullptr;
JSGlobalObject* origin = exec->lexicalGlobalObject();
for (ptrdiff_t i = m_currentProfiles.size() - 1; i >= 0; --i) {
m_currentProfiles.remove(i);
if (!m_currentProfiles.size())
exec->vm().setEnabledProfiler(nullptr);
-
+
return returnProfile;
}
}
- return 0;
+ return nullptr;
}
void LegacyProfiler::stopProfiling(JSGlobalObject* origin)
callFunctionForProfilesWithGroup(std::bind(&ProfileGenerator::exceptionUnwind, std::placeholders::_1, handlerCallFrame, callIdentifier), m_currentProfiles, handlerCallFrame->lexicalGlobalObject()->profileGroup());
}
-void LegacyProfiler::didPause(PassRefPtr<DebuggerCallFrame> prpCallFrame)
-{
- if (m_currentProfiles.isEmpty())
- return;
-
- RefPtr<DebuggerCallFrame> callFrame = prpCallFrame;
- CallIdentifier callIdentifier = createCallIdentifier(callFrame->exec(), JSValue(), StringImpl::empty(), 0, 0);
-
- callFunctionForProfilesWithGroup(std::bind(&ProfileGenerator::didPause, std::placeholders::_1, callFrame, callIdentifier), m_currentProfiles, callFrame->vmEntryGlobalObject()->profileGroup());
-}
-
-void LegacyProfiler::didContinue(PassRefPtr<DebuggerCallFrame> prpCallFrame)
-{
- if (m_currentProfiles.isEmpty())
- return;
-
- RefPtr<DebuggerCallFrame> callFrame = prpCallFrame;
- CallIdentifier callIdentifier = createCallIdentifier(callFrame->exec(), JSValue(), StringImpl::empty(), 0, 0);
-
- callFunctionForProfilesWithGroup(std::bind(&ProfileGenerator::didContinue, std::placeholders::_1, callFrame, callIdentifier), m_currentProfiles, callFrame->vmEntryGlobalObject()->profileGroup());
-}
-
CallIdentifier LegacyProfiler::createCallIdentifier(ExecState* exec, JSValue functionValue, const String& defaultSourceURL, unsigned defaultLineNumber, unsigned defaultColumnNumber)
{
if (!functionValue)
#include "Profile.h"
#include <wtf/PassRefPtr.h>
#include <wtf/RefPtr.h>
+#include <wtf/Stopwatch.h>
#include <wtf/Vector.h>
namespace JSC {
-class DebuggerCallFrame;
class ExecState;
-class VM;
class JSGlobalObject;
class JSObject;
class JSValue;
class LegacyProfiler {
WTF_MAKE_FAST_ALLOCATED;
public:
- JS_EXPORT_PRIVATE static LegacyProfiler* profiler();
+ JS_EXPORT_PRIVATE static LegacyProfiler* profiler();
static CallIdentifier createCallIdentifier(ExecState*, JSValue, const WTF::String& sourceURL, unsigned defaultLineNumber, unsigned defaultColumnNumber);
- JS_EXPORT_PRIVATE void startProfiling(ExecState*, const WTF::String& title);
+ JS_EXPORT_PRIVATE void startProfiling(ExecState*, const WTF::String& title, PassRefPtr<Stopwatch>);
JS_EXPORT_PRIVATE PassRefPtr<Profile> stopProfiling(ExecState*, const WTF::String& title);
void stopProfiling(JSGlobalObject*);
void exceptionUnwind(ExecState* handlerCallFrame);
- void didPause(PassRefPtr<DebuggerCallFrame>);
- void didContinue(PassRefPtr<DebuggerCallFrame>);
-
const Vector<RefPtr<ProfileGenerator>>& currentProfiles() { return m_currentProfiles; };
private:
// FIXME: When multi-threading is supported this will be a vector and calls
// into the profiler will need to know which thread it is executing on.
m_rootNode = ProfileNode::create(nullptr, CallIdentifier(ASCIILiteral("Thread_1"), String(), 0, 0), nullptr);
- m_rootNode->appendCall(ProfileNode::Call(currentTime()));
+ m_rootNode->appendCall(ProfileNode::Call(0.0));
}
Profile::~Profile()
#include "CallFrame.h"
#include "CodeBlock.h"
-#include "Debugger.h"
#include "JSGlobalObject.h"
#include "JSStringRef.h"
#include "JSFunction.h"
namespace JSC {
-PassRefPtr<ProfileGenerator> ProfileGenerator::create(ExecState* exec, const String& title, unsigned uid)
+PassRefPtr<ProfileGenerator> ProfileGenerator::create(ExecState* exec, const String& title, unsigned uid, PassRefPtr<Stopwatch> stopwatch)
{
- return adoptRef(new ProfileGenerator(exec, title, uid));
+ return adoptRef(new ProfileGenerator(exec, title, uid, stopwatch));
}
-ProfileGenerator::ProfileGenerator(ExecState* exec, const String& title, unsigned uid)
+ProfileGenerator::ProfileGenerator(ExecState* exec, const String& title, unsigned uid, PassRefPtr<Stopwatch> stopwatch)
: m_origin(exec ? exec->lexicalGlobalObject() : nullptr)
, m_profileGroup(exec ? exec->lexicalGlobalObject()->profileGroup() : 0)
- , m_debuggerPausedTimestamp(NAN)
+ , m_stopwatch(stopwatch)
, m_foundConsoleStartParent(false)
, m_suspended(false)
{
- if (Debugger* debugger = exec->lexicalGlobalObject()->debugger())
- m_debuggerPausedTimestamp = debugger->isPaused() ? currentTime() : NAN;
-
m_profile = Profile::create(title, uid);
m_currentNode = m_rootNode = m_profile->rootNode();
if (exec)
unsigned column = 0;
visitor->computeLineAndColumn(line, column);
m_currentNode = ProfileNode::create(m_exec, LegacyProfiler::createCallIdentifier(m_exec, visitor->callee(), visitor->sourceURL(), line, column), m_rootNode.get());
- m_currentNode->appendCall(ProfileNode::Call(currentTime()));
+ // Assume that profile times are relative to when the |console.profile| command is evaluated.
+ // This matches the logic in JSStartProfiling() and InspectorTimelineAgent::startFromConsole().
+ m_currentNode->appendCall(ProfileNode::Call(0.0));
m_rootNode->spliceNode(m_currentNode.get());
m_foundParent = true;
ASSERT_ARG(node, node);
if (std::isnan(startTime))
- startTime = currentTime();
-
- // If the debugger is paused when beginning, then don't set the start time. It
- // will be fixed up when the debugger unpauses or the call entry ends.
- if (!std::isnan(m_debuggerPausedTimestamp))
- startTime = NAN;
+ startTime = m_stopwatch->elapsedTime();
node->appendCall(ProfileNode::Call(startTime));
}
ProfileNode::Call& last = node->lastCall();
- // If the debugger is paused, ignore the interval that ends now.
- if (!std::isnan(m_debuggerPausedTimestamp) && !std::isnan(last.elapsedTime()))
- return;
-
- // If paused and no time was accrued then the debugger was never unpaused. The call will
- // have no time accrued and appear to have started when the debugger was paused.
- if (!std::isnan(m_debuggerPausedTimestamp)) {
- last.setStartTime(m_debuggerPausedTimestamp);
- last.setElapsedTime(0.0);
- return;
- }
-
- // Otherwise, add the interval ending now to elapsed time.
double previousElapsedTime = std::isnan(last.elapsedTime()) ? 0.0 : last.elapsedTime();
- double newlyElapsedTime = currentTime() - last.startTime();
+ double newlyElapsedTime = m_stopwatch->elapsedTime() - last.startTime();
last.setElapsedTime(previousElapsedTime + newlyElapsedTime);
}
}
m_currentNode = calleeNode;
- beginCallEntry(calleeNode.get());
+ beginCallEntry(calleeNode.get(), m_stopwatch->elapsedTime());
}
void ProfileGenerator::didExecute(ExecState* callerCallFrame, const CallIdentifier& callIdentifier)
}
}
-void ProfileGenerator::didPause(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&)
-{
- ASSERT(std::isnan(m_debuggerPausedTimestamp));
-
- m_debuggerPausedTimestamp = currentTime();
-
- for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent()) {
- ProfileNode::Call& last = node->lastCall();
- ASSERT(!std::isnan(last.startTime()));
-
- double previousElapsedTime = std::isnan(last.elapsedTime()) ? 0.0 : last.elapsedTime();
- double additionalElapsedTime = m_debuggerPausedTimestamp - last.startTime();
- last.setStartTime(NAN);
- last.setElapsedTime(previousElapsedTime + additionalElapsedTime);
- }
-}
-
-void ProfileGenerator::didContinue(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&)
-{
- ASSERT(!std::isnan(m_debuggerPausedTimestamp));
-
- for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent())
- node->lastCall().setStartTime(m_debuggerPausedTimestamp);
-
- m_debuggerPausedTimestamp = NAN;
-}
-
void ProfileGenerator::stopProfiling()
{
for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent())
#include <wtf/PassRefPtr.h>
#include <wtf/RefCounted.h>
#include <wtf/RefPtr.h>
+#include <wtf/Stopwatch.h>
#include <wtf/text/WTFString.h>
namespace JSC {
class ProfileGenerator : public RefCounted<ProfileGenerator> {
public:
- static PassRefPtr<ProfileGenerator> create(ExecState*, const WTF::String& title, unsigned uid);
+ static PassRefPtr<ProfileGenerator> create(ExecState*, const WTF::String& title, unsigned uid, PassRefPtr<Stopwatch>);
// Members
const WTF::String& title() const;
void didExecute(ExecState* callerCallFrame, const CallIdentifier&);
void exceptionUnwind(ExecState* handlerCallFrame, const CallIdentifier&);
- void didPause(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&);
- void didContinue(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&);
-
void setIsSuspended(bool suspended) { ASSERT(m_suspended != suspended); m_suspended = suspended; }
void stopProfiling();
private:
- ProfileGenerator(ExecState*, const WTF::String& title, unsigned uid);
+ ProfileGenerator(ExecState*, const WTF::String& title, unsigned uid, PassRefPtr<Stopwatch>);
void addParentForConsoleStart(ExecState*);
void removeProfileStart();
void removeProfileEnd();
- void beginCallEntry(ProfileNode*, double startTime = NAN);
+ void beginCallEntry(ProfileNode*, double startTime);
void endCallEntry(ProfileNode*);
RefPtr<Profile> m_profile;
JSGlobalObject* m_origin;
unsigned m_profileGroup;
- // Timestamp is set to NAN when the debugger is not currently paused.
- double m_debuggerPausedTimestamp;
+ RefPtr<Stopwatch> m_stopwatch;
RefPtr<ProfileNode> m_rootNode;
RefPtr<ProfileNode> m_currentNode;
bool m_foundConsoleStartParent;
+2014-10-18 Brian J. Burg <burg@cs.washington.edu>
+
+ Web Inspector: timelines should not count time elapsed while paused in the debugger
+ https://bugs.webkit.org/show_bug.cgi?id=136351
+
+ Reviewed by Timothy Hatcher.
+
+ * WTF.vcxproj/WTF.vcxproj:
+ * WTF.vcxproj/WTF.vcxproj.filters:
+ * WTF.xcodeproj/project.pbxproj:
+ * wtf/CMakeLists.txt:
+ * wtf/Stopwatch.h: Added. This implements a refcounted monotonic stopwatch.
+ (WTF::Stopwatch::create):
+ (WTF::Stopwatch::Stopwatch):
+ (WTF::Stopwatch::reset):
+ (WTF::Stopwatch::start):
+ (WTF::Stopwatch::stop):
+ (WTF::Stopwatch::elapsedTime):
+
2014-10-23 Joseph Pecoraro <pecoraro@apple.com>
Web Inspector: Provide a way to have alternate inspector agents
<ClInclude Include="..\wtf\StackBounds.h" />
<ClInclude Include="..\wtf\StaticConstructors.h" />
<ClInclude Include="..\wtf\StdLibExtras.h" />
+ <ClInclude Include="..\wtf\Stopwatch.h" />
<ClInclude Include="..\wtf\StringExtras.h" />
<ClInclude Include="..\wtf\StringHasher.h" />
<ClInclude Include="..\wtf\StringPrintStream.h" />
<ClInclude Include="..\wtf\StdLibExtras.h">
<Filter>wtf</Filter>
</ClInclude>
+ <ClInclude Include="..\wtf\Stopwatch.h">
+ <Filter>wtf</Filter>
+ </ClInclude>
<ClInclude Include="..\wtf\StringExtras.h">
<Filter>wtf</Filter>
</ClInclude>
A8A47487151A825B004123FF /* WTFThreadData.h in Headers */ = {isa = PBXBuildFile; fileRef = A8A4737B151A825B004123FF /* WTFThreadData.h */; };
A8A4748C151A8264004123FF /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = A8A4748B151A8264004123FF /* config.h */; };
B38FD7BD168953E80065C969 /* FeatureDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = B38FD7BC168953E80065C969 /* FeatureDefines.h */; };
+ C4F8A93719C65EB400B2B15D /* Stopwatch.h in Headers */ = {isa = PBXBuildFile; fileRef = C4F8A93619C65EB400B2B15D /* Stopwatch.h */; };
CD5497AC15857D0300B5BC30 /* MediaTime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD5497AA15857D0300B5BC30 /* MediaTime.cpp */; };
CD5497AD15857D0300B5BC30 /* MediaTime.h in Headers */ = {isa = PBXBuildFile; fileRef = CD5497AB15857D0300B5BC30 /* MediaTime.h */; };
CE46516E19DB1FB4003ECA05 /* NSMapTableSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = CE46516D19DB1FB4003ECA05 /* NSMapTableSPI.h */; };
A8A4737B151A825B004123FF /* WTFThreadData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WTFThreadData.h; sourceTree = "<group>"; };
A8A4748B151A8264004123FF /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = "<group>"; };
B38FD7BC168953E80065C969 /* FeatureDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeatureDefines.h; sourceTree = "<group>"; };
+ C4F8A93619C65EB400B2B15D /* Stopwatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Stopwatch.h; sourceTree = "<group>"; };
CD5497AA15857D0300B5BC30 /* MediaTime.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MediaTime.cpp; sourceTree = "<group>"; };
CD5497AB15857D0300B5BC30 /* MediaTime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaTime.h; sourceTree = "<group>"; };
CE46516D19DB1FB4003ECA05 /* NSMapTableSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSMapTableSPI.h; sourceTree = "<group>"; };
FEDACD3C1630F83F00C69634 /* StackStats.h */,
A8A47310151A825B004123FF /* StaticConstructors.h */,
A8A47311151A825B004123FF /* StdLibExtras.h */,
+ C4F8A93619C65EB400B2B15D /* Stopwatch.h */,
1A6BB768162F300500DD16DB /* StreamBuffer.h */,
A8A47313151A825B004123FF /* StringExtras.h */,
A748745117A0BDAE00FA04CB /* StringHashDumpContext.h */,
A8A473A3151A825B004123FF /* DecimalNumber.h in Headers */,
0F2B66A617B6B4FB00A7AE3F /* DeferrableRefCounted.h in Headers */,
A8A473A5151A825B004123FF /* Deque.h in Headers */,
+ C4F8A93719C65EB400B2B15D /* Stopwatch.h in Headers */,
A8A473A6151A825B004123FF /* DisallowCType.h in Headers */,
A8A473AF151A825B004123FF /* diy-fp.h in Headers */,
A8A473B1151A825B004123FF /* double-conversion.h in Headers */,
StackStats.h
StaticConstructors.h
StdLibExtras.h
+ Stopwatch.h
StringExtras.h
StringHasher.h
StringPrintStream.h
--- /dev/null
+/*
+ * Copyright (C) 2014 University of Washington. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef Stopwatch_h
+#define Stopwatch_h
+
+#include <cmath>
+#include <wtf/CurrentTime.h>
+#include <wtf/RefCounted.h>
+
+namespace WTF {
+
+class Stopwatch : public RefCounted<Stopwatch> {
+public:
+ static PassRefPtr<Stopwatch> create()
+ {
+ return adoptRef(new Stopwatch());
+ }
+
+ void reset();
+ void start();
+ void stop();
+
+ double elapsedTime();
+
+private:
+ Stopwatch()
+ : m_elapsedTime(0.0)
+ , m_lastStartTime(NAN)
+ {
+ }
+
+ double m_elapsedTime;
+ double m_lastStartTime;
+};
+
+inline void Stopwatch::reset()
+{
+ m_elapsedTime = 0.0;
+ m_lastStartTime = NAN;
+}
+
+inline void Stopwatch::start()
+{
+ ASSERT(isnan(m_lastStartTime));
+
+ m_lastStartTime = monotonicallyIncreasingTime();
+}
+
+inline void Stopwatch::stop()
+{
+ ASSERT(!isnan(m_lastStartTime));
+
+ m_elapsedTime += monotonicallyIncreasingTime() - m_lastStartTime;
+ m_lastStartTime = NAN;
+}
+
+inline double Stopwatch::elapsedTime()
+{
+ bool shouldSuspend = !isnan(m_lastStartTime);
+ if (shouldSuspend)
+ stop();
+
+ double elapsedTime = m_elapsedTime;
+
+ if (shouldSuspend)
+ start();
+ return elapsedTime;
+}
+
+} // namespace WTF
+
+using WTF::Stopwatch;
+
+#endif // Stopwatch_h
+2014-10-18 Brian J. Burg <burg@cs.washington.edu>
+
+ Web Inspector: timelines should not count time elapsed while paused in the debugger
+ https://bugs.webkit.org/show_bug.cgi?id=136351
+
+ Reviewed by Timothy Hatcher.
+
+ To avoid counting time elapsed while the debugger is paused, timeline records should
+ keep track of time elapsed since the start of timeline capturing, rather than wall clock
+ timestamps. We can easily compute elapsed time by sharing a Stopwatch instance through the
+ inspector environment. The stopwatch runs with timelines and is paused with the debugger,
+ so subsequent time measurements will not include time elapsed while the debugger is paused.
+
+ This refactoring is safe because start and end times are only used to graph records; the
+ timestamp's actual value is irrelevant and is not displayed in the user interface. Date
+ timestamps are still included with network-related records as part of their header data.
+
+ No new tests, because we cannot reliably test timing changes induced by debugger pauses.
+ It is possible for records to accrue time before the debugger pauses or after it resumes.
+
+ * inspector/InspectorCSSAgent.cpp: Remove unnecessary include.
+ * inspector/InspectorController.cpp:
+ (WebCore::InspectorController::InspectorController):
+ (WebCore::InspectorController::executionStopwatch): Add a shared stopwatch.
+ * inspector/InspectorController.h:
+ * inspector/InspectorPageAgent.cpp:
+ (WebCore::InspectorPageAgent::timestamp): Redirect to the shared stopwatch.
+ (WebCore::InspectorPageAgent::domContentEventFired):
+ (WebCore::InspectorPageAgent::loadEventFired):
+ * inspector/InspectorPageAgent.h:
+ * inspector/InspectorResourceAgent.cpp:
+ (WebCore::InspectorResourceAgent::timestamp): Redirect to the shared stopwatch.
+ (WebCore::InspectorResourceAgent::willSendRequest):
+ (WebCore::InspectorResourceAgent::didReceiveResponse):
+ (WebCore::InspectorResourceAgent::didReceiveData):
+ (WebCore::InspectorResourceAgent::didFinishLoading):
+ (WebCore::InspectorResourceAgent::didFailLoading):
+ (WebCore::InspectorResourceAgent::didLoadResourceFromMemoryCache):
+ (WebCore::InspectorResourceAgent::willSendWebSocketHandshakeRequest):
+ (WebCore::InspectorResourceAgent::didReceiveWebSocketHandshakeResponse):
+ (WebCore::InspectorResourceAgent::didCloseWebSocket):
+ (WebCore::InspectorResourceAgent::didReceiveWebSocketFrame):
+ (WebCore::InspectorResourceAgent::didSendWebSocketFrame):
+ (WebCore::InspectorResourceAgent::didReceiveWebSocketFrameError):
+ * inspector/InspectorResourceAgent.h:
+ * inspector/InspectorTimelineAgent.cpp:
+ (WebCore::InspectorTimelineAgent::internalStart): Start and stop the stopwatch with timelines.
+ (WebCore::InspectorTimelineAgent::internalStop):
+ (WebCore::InspectorTimelineAgent::timestamp): Redirect to the shared stopwatch.
+ (WebCore::startProfiling):
+ (WebCore::InspectorTimelineAgent::startFromConsole):
+ (WebCore::InspectorTimelineAgent::willCallFunction):
+ (WebCore::InspectorTimelineAgent::willEvaluateScript):
+ (WebCore::TimelineTimeConverter::reset): Deleted.
+ * inspector/InspectorTimelineAgent.h:
+ (WebCore::TimelineTimeConverter::TimelineTimeConverter): Deleted.
+ (WebCore::TimelineTimeConverter::fromMonotonicallyIncreasingTime): Deleted.
+ (WebCore::InspectorTimelineAgent::timeConverter): Deleted.
+ * inspector/TimelineRecordFactory.cpp:
+ * inspector/WorkerInspectorController.cpp:
+ (WebCore::WorkerInspectorController::WorkerInspectorController):
+ (WebCore::WorkerInspectorController::executionStopwatch): Add a shared stopwatch.
+ * inspector/WorkerInspectorController.h:
+
2014-10-25 Dan Bernstein <mitz@apple.com>
Fix builds using the public SDK.
#include "StyleSheetList.h"
#include "WebKitNamedFlow.h"
#include <inspector/InspectorProtocolObjects.h>
-#include <wtf/CurrentTime.h>
#include <wtf/HashSet.h>
#include <wtf/Ref.h>
#include <wtf/Vector.h>
#include <inspector/agents/InspectorAgent.h>
#include <profiler/LegacyProfiler.h>
#include <runtime/JSLock.h>
+#include <wtf/Stopwatch.h>
#if ENABLE(REMOTE_INSPECTOR)
#include "PageDebuggable.h"
, m_injectedScriptManager(std::make_unique<WebInjectedScriptManager>(*this, WebInjectedScriptHost::create()))
, m_overlay(std::make_unique<InspectorOverlay>(page, inspectorClient))
, m_inspectorFrontendChannel(nullptr)
+ , m_executionStopwatch(Stopwatch::create())
, m_page(page)
, m_inspectorClient(inspectorClient)
, m_inspectorFrontendClient(nullptr)
#endif
}
+PassRefPtr<Stopwatch> InspectorController::executionStopwatch()
+{
+ return m_executionStopwatch;
+}
+
} // namespace WebCore
#endif // ENABLE(INSPECTOR)
virtual void willCallInjectedScriptFunction(JSC::ExecState*, const String& scriptName, int scriptLine) override;
virtual void didCallInjectedScriptFunction(JSC::ExecState*) override;
virtual void frontendInitialized() override;
+ virtual PassRefPtr<WTF::Stopwatch> executionStopwatch() override;
private:
friend InstrumentingAgents* instrumentationForPage(Page*);
RefPtr<Inspector::InspectorBackendDispatcher> m_inspectorBackendDispatcher;
Inspector::InspectorFrontendChannel* m_inspectorFrontendChannel;
+ RefPtr<WTF::Stopwatch> m_executionStopwatch;
Page& m_page;
InspectorClient* m_inspectorClient;
InspectorFrontendClient* m_inspectorFrontendClient;
#include "InspectorDOMAgent.h"
#include "InspectorInstrumentation.h"
#include "InspectorOverlay.h"
+#include "InspectorTimelineAgent.h"
#include "InstrumentingAgents.h"
#include "MainFrame.h"
#include "MemoryCache.h"
#include <inspector/ContentSearchUtilities.h>
#include <inspector/IdentifiersFactory.h>
#include <inspector/InspectorValues.h>
-#include <wtf/CurrentTime.h>
#include <wtf/ListHashSet.h>
#include <wtf/text/Base64.h>
#include <wtf/text/StringBuilder.h>
#endif
}
+double InspectorPageAgent::timestamp()
+{
+ return m_instrumentingAgents->inspectorEnvironment().executionStopwatch()->elapsedTime();
+}
+
void InspectorPageAgent::enable(ErrorString&)
{
m_enabled = true;
void InspectorPageAgent::domContentEventFired()
{
m_isFirstLayoutAfterOnLoad = true;
- m_frontendDispatcher->domContentEventFired(currentTime());
+ m_frontendDispatcher->domContentEventFired(timestamp());
}
void InspectorPageAgent::loadEventFired()
{
- m_frontendDispatcher->loadEventFired(currentTime());
+ m_frontendDispatcher->loadEventFired(timestamp());
}
void InspectorPageAgent::frameNavigated(DocumentLoader* loader)
void updateTouchEventEmulationInPage(bool);
#endif
+ double timestamp();
+
static bool mainResourceContent(Frame*, bool withBase64Encode, String* result);
static bool dataContent(const char* data, unsigned size, const String& textEncodingName, bool withBase64Encode, String* result);
#include "IconController.h"
#include "InspectorClient.h"
#include "InspectorPageAgent.h"
+#include "InspectorTimelineAgent.h"
#include "InstrumentingAgents.h"
#include "JSMainThreadExecState.h"
#include "MemoryCache.h"
#include <inspector/InspectorValues.h>
#include <inspector/ScriptCallStack.h>
#include <inspector/ScriptCallStackFactory.h>
-#include <wtf/CurrentTime.h>
#include <wtf/RefPtr.h>
#include <wtf/text/StringBuilder.h>
ASSERT(!m_instrumentingAgents->inspectorResourceAgent());
}
+double InspectorResourceAgent::timestamp()
+{
+ return m_instrumentingAgents->inspectorEnvironment().executionStopwatch()->elapsedTime();
+}
+
void InspectorResourceAgent::willSendRequest(unsigned long identifier, DocumentLoader* loader, ResourceRequest& request, const ResourceResponse& redirectResponse)
{
if (request.hiddenFromInspector()) {
Inspector::Protocol::Page::ResourceType resourceType = InspectorPageAgent::resourceTypeJson(type);
RefPtr<Inspector::Protocol::Network::Initiator> initiatorObject = buildInitiatorObject(loader->frame() ? loader->frame()->document() : nullptr);
- m_frontendDispatcher->requestWillBeSent(requestId, m_pageAgent->frameId(loader->frame()), m_pageAgent->loaderId(loader), loader->url().string(), buildObjectForResourceRequest(request), currentTime(), initiatorObject, buildObjectForResourceResponse(redirectResponse, loader), type != InspectorPageAgent::OtherResource ? &resourceType : nullptr);
+ m_frontendDispatcher->requestWillBeSent(requestId, m_pageAgent->frameId(loader->frame()), m_pageAgent->loaderId(loader), loader->url().string(), buildObjectForResourceRequest(request), timestamp(), initiatorObject, buildObjectForResourceResponse(redirectResponse, loader), type != InspectorPageAgent::OtherResource ? &resourceType : nullptr);
}
void InspectorResourceAgent::markResourceAsCached(unsigned long identifier)
m_resourcesData->responseReceived(requestId, m_pageAgent->frameId(loader->frame()), response);
m_resourcesData->setResourceType(requestId, type);
- m_frontendDispatcher->responseReceived(requestId, m_pageAgent->frameId(loader->frame()), m_pageAgent->loaderId(loader), currentTime(), InspectorPageAgent::resourceTypeJson(type), resourceResponse);
+ m_frontendDispatcher->responseReceived(requestId, m_pageAgent->frameId(loader->frame()), m_pageAgent->loaderId(loader), timestamp(), InspectorPageAgent::resourceTypeJson(type), resourceResponse);
// If we revalidated the resource and got Not modified, send content length following didReceiveResponse
// as there will be no calls to didReceiveData from the network stack.
m_resourcesData->maybeAddResourceData(requestId, data, dataLength);
}
- m_frontendDispatcher->dataReceived(requestId, currentTime(), dataLength, encodedDataLength);
+ m_frontendDispatcher->dataReceived(requestId, timestamp(), dataLength, encodedDataLength);
}
void InspectorResourceAgent::didFinishLoading(unsigned long identifier, DocumentLoader* loader, double finishTime)
// However, all other times passed to the Inspector are generated from the web process. Mixing
// times from different processes can cause the finish time to be earlier than the response
// received time due to inter-process communication lag.
- finishTime = currentTime();
+ finishTime = timestamp();
String sourceMappingURL;
NetworkResourcesData::ResourceData const* resourceData = m_resourcesData->data(requestId);
}
bool canceled = error.isCancellation();
- m_frontendDispatcher->loadingFailed(requestId, currentTime(), error.localizedDescription(), canceled ? &canceled : nullptr);
+ m_frontendDispatcher->loadingFailed(requestId, timestamp(), error.localizedDescription(), canceled ? &canceled : nullptr);
}
void InspectorResourceAgent::didLoadResourceFromMemoryCache(DocumentLoader* loader, CachedResource* resource)
RefPtr<Inspector::Protocol::Network::Initiator> initiatorObject = buildInitiatorObject(loader->frame() ? loader->frame()->document() : nullptr);
- m_frontendDispatcher->requestServedFromMemoryCache(requestId, frameId, loaderId, loader->url().string(), currentTime(), initiatorObject, buildObjectForCachedResource(resource, loader));
+ m_frontendDispatcher->requestServedFromMemoryCache(requestId, frameId, loaderId, loader->url().string(), timestamp(), initiatorObject, buildObjectForCachedResource(resource, loader));
}
void InspectorResourceAgent::setInitialScriptContent(unsigned long identifier, const String& sourceString)
{
RefPtr<Inspector::Protocol::Network::WebSocketRequest> requestObject = Inspector::Protocol::Network::WebSocketRequest::create()
.setHeaders(buildObjectForHeaders(request.httpHeaderFields()));
- m_frontendDispatcher->webSocketWillSendHandshakeRequest(IdentifiersFactory::requestId(identifier), currentTime(), requestObject);
+ m_frontendDispatcher->webSocketWillSendHandshakeRequest(IdentifiersFactory::requestId(identifier), timestamp(), requestObject);
}
void InspectorResourceAgent::didReceiveWebSocketHandshakeResponse(unsigned long identifier, const ResourceResponse& response)
.setStatus(response.httpStatusCode())
.setStatusText(response.httpStatusText())
.setHeaders(buildObjectForHeaders(response.httpHeaderFields()));
- m_frontendDispatcher->webSocketHandshakeResponseReceived(IdentifiersFactory::requestId(identifier), currentTime(), responseObject);
+ m_frontendDispatcher->webSocketHandshakeResponseReceived(IdentifiersFactory::requestId(identifier), timestamp(), responseObject);
}
void InspectorResourceAgent::didCloseWebSocket(unsigned long identifier)
{
- m_frontendDispatcher->webSocketClosed(IdentifiersFactory::requestId(identifier), currentTime());
+ m_frontendDispatcher->webSocketClosed(IdentifiersFactory::requestId(identifier), timestamp());
}
void InspectorResourceAgent::didReceiveWebSocketFrame(unsigned long identifier, const WebSocketFrame& frame)
.setOpcode(frame.opCode)
.setMask(frame.masked)
.setPayloadData(String(frame.payload, frame.payloadLength));
- m_frontendDispatcher->webSocketFrameReceived(IdentifiersFactory::requestId(identifier), currentTime(), frameObject);
+ m_frontendDispatcher->webSocketFrameReceived(IdentifiersFactory::requestId(identifier), timestamp(), frameObject);
}
void InspectorResourceAgent::didSendWebSocketFrame(unsigned long identifier, const WebSocketFrame& frame)
.setOpcode(frame.opCode)
.setMask(frame.masked)
.setPayloadData(String(frame.payload, frame.payloadLength));
- m_frontendDispatcher->webSocketFrameSent(IdentifiersFactory::requestId(identifier), currentTime(), frameObject);
+ m_frontendDispatcher->webSocketFrameSent(IdentifiersFactory::requestId(identifier), timestamp(), frameObject);
}
void InspectorResourceAgent::didReceiveWebSocketFrameError(unsigned long identifier, const String& errorMessage)
{
- m_frontendDispatcher->webSocketFrameError(IdentifiersFactory::requestId(identifier), currentTime(), errorMessage);
+ m_frontendDispatcher->webSocketFrameError(IdentifiersFactory::requestId(identifier), timestamp(), errorMessage);
}
#endif // ENABLE(WEB_SOCKETS)
private:
void enable();
+ double timestamp();
+
InspectorPageAgent* m_pageAgent;
InspectorClient* m_client;
std::unique_ptr<Inspector::InspectorNetworkFrontendDispatcher> m_frontendDispatcher;
namespace WebCore {
-void TimelineTimeConverter::reset()
-{
- m_startOffset = monotonicallyIncreasingTime() - currentTime();
-}
-
InspectorTimelineAgent::~InspectorTimelineAgent()
{
}
else
m_maxCallStackDepth = 5;
- m_timeConverter.reset();
+ m_instrumentingAgents->inspectorEnvironment().executionStopwatch()->start();
m_instrumentingAgents->setInspectorTimelineAgent(this);
if (!m_enabled)
return;
+ m_instrumentingAgents->inspectorEnvironment().executionStopwatch()->stop();
+
m_instrumentingAgents->setInspectorTimelineAgent(nullptr);
if (m_scriptDebugServer)
m_frontendDispatcher->recordingStopped();
}
+double InspectorTimelineAgent::timestamp()
+{
+ return m_instrumentingAgents->inspectorEnvironment().executionStopwatch()->elapsedTime();
+}
+
void InspectorTimelineAgent::setPageScriptDebugServer(PageScriptDebugServer* scriptDebugServer)
{
ASSERT(!m_enabled);
m_scriptDebugServer = scriptDebugServer;
}
-static inline void startProfiling(JSC::ExecState* exec, const String& title)
+static inline void startProfiling(JSC::ExecState* exec, const String& title, PassRefPtr<Stopwatch> stopwatch)
{
- JSC::LegacyProfiler::profiler()->startProfiling(exec, title);
+ JSC::LegacyProfiler::profiler()->startProfiling(exec, title, stopwatch);
}
static inline PassRefPtr<JSC::Profile> stopProfiling(JSC::ExecState* exec, const String& title)
return JSC::LegacyProfiler::profiler()->stopProfiling(exec, title);
}
-static inline void startProfiling(Frame* frame, const String& title)
+static inline void startProfiling(Frame* frame, const String& title, PassRefPtr<Stopwatch> stopwatch)
{
- startProfiling(toJSDOMWindow(frame, debuggerWorld())->globalExec(), title);
+ startProfiling(toJSDOMWindow(frame, debuggerWorld())->globalExec(), title, stopwatch);
}
static inline PassRefPtr<JSC::Profile> stopProfiling(Frame* frame, const String& title)
if (!m_enabled && m_pendingConsoleProfileRecords.isEmpty())
internalStart();
- startProfiling(exec, title);
+ // Use an independent stopwatch for console-initiated profiling, since the user will expect it
+ // to be relative to when their command was issued.
+ RefPtr<Stopwatch> profilerStopwatch = Stopwatch::create();
+ profilerStopwatch->start();
+ startProfiling(exec, title, profilerStopwatch.release());
m_pendingConsoleProfileRecords.append(createRecordEntry(TimelineRecordFactory::createConsoleProfileData(title), TimelineRecordType::ConsoleProfile, true, frameFromExecState(exec)));
}
pushCurrentRecord(TimelineRecordFactory::createFunctionCallData(scriptName, scriptLine), TimelineRecordType::FunctionCall, true, frame);
if (frame && !m_callStackDepth)
- startProfiling(frame, ASCIILiteral("Timeline FunctionCall"));
+ startProfiling(frame, ASCIILiteral("Timeline FunctionCall"), m_instrumentingAgents->inspectorEnvironment().executionStopwatch());
++m_callStackDepth;
}
if (frame && !m_callStackDepth) {
++m_callStackDepth;
- startProfiling(frame, ASCIILiteral("Timeline EvaluateScript"));
+ startProfiling(frame, ASCIILiteral("Timeline EvaluateScript"), m_instrumentingAgents->inspectorEnvironment().executionStopwatch());
}
}
quad->setP4(frameView.contentsToRootView(roundedIntPoint(absolute.p4())));
}
-double InspectorTimelineAgent::timestamp()
-{
- return m_timeConverter.fromMonotonicallyIncreasingTime(monotonicallyIncreasingTime());
-}
-
Page* InspectorTimelineAgent::page()
{
return m_pageAgent ? m_pageAgent->page() : nullptr;
#include <inspector/InspectorFrontendDispatchers.h>
#include <inspector/InspectorValues.h>
#include <inspector/ScriptDebugListener.h>
+#include <wtf/Stopwatch.h>
#include <wtf/Vector.h>
#include <wtf/WeakPtr.h>
WebSocketDestroy
};
-class TimelineTimeConverter {
-public:
- TimelineTimeConverter()
- : m_startOffset(0)
- {
- }
- double fromMonotonicallyIncreasingTime(double time) const { return (time - m_startOffset) * 1000.0; }
- void reset();
-
-private:
- double m_startOffset;
-};
-
class InspectorTimelineAgent
: public InspectorAgentBase
, public Inspector::InspectorTimelineBackendDispatcherHandler
#endif
protected:
- // ScriptDebugListener. This is only used to create records for probe samples.
+ // ScriptDebugListener
virtual void didParseSource(JSC::SourceID, const Script&) override { }
virtual void failedToParseSource(const String&, const String&, int, int, const String&) override { }
virtual void didPause(JSC::ExecState*, const Deprecated::ScriptValue&, const Deprecated::ScriptValue&) override { }
void internalStart(const int* maxCallStackDepth = nullptr);
void internalStop();
+ double timestamp();
void sendEvent(PassRefPtr<Inspector::InspectorObject>);
void appendRecord(PassRefPtr<Inspector::InspectorObject> data, TimelineRecordType, bool captureCallStack, Frame*);
void clearRecordStack();
void localToPageQuad(const RenderObject&, const LayoutRect&, FloatQuad*);
- const TimelineTimeConverter& timeConverter() const { return m_timeConverter; }
- double timestamp();
Page* page();
InspectorPageAgent* m_pageAgent;
PageScriptDebugServer* m_scriptDebugServer;
- TimelineTimeConverter m_timeConverter;
std::unique_ptr<Inspector::InspectorTimelineFrontendDispatcher> m_frontendDispatcher;
RefPtr<Inspector::InspectorTimelineBackendDispatcher> m_backendDispatcher;
- double m_timestampOffset;
Vector<TimelineRecordEntry> m_recordStack;
#include <inspector/ScriptCallStack.h>
#include <inspector/ScriptCallStackFactory.h>
#include <profiler/Profile.h>
-#include <wtf/CurrentTime.h>
using namespace Inspector;
#include "WorkerThread.h"
#include <inspector/InspectorBackendDispatcher.h>
#include <inspector/InspectorFrontendDispatchers.h>
+#include <wtf/Stopwatch.h>
using namespace Inspector;
, m_instrumentingAgents(InstrumentingAgents::create(*this))
, m_injectedScriptManager(std::make_unique<WebInjectedScriptManager>(*this, WebInjectedScriptHost::create()))
, m_runtimeAgent(nullptr)
+ , m_executionStopwatch(Stopwatch::create())
{
auto runtimeAgent = std::make_unique<WorkerRuntimeAgent>(m_injectedScriptManager.get(), &workerGlobalScope);
m_runtimeAgent = runtimeAgent.get();
InspectorInstrumentation::didCallFunction(cookie, scriptExecutionContext);
}
+PassRefPtr<Stopwatch> WorkerInspectorController::executionStopwatch()
+{
+ return m_executionStopwatch;
+}
+
} // namespace WebCore
#endif // ENABLE(INSPECTOR)
virtual void willCallInjectedScriptFunction(JSC::ExecState*, const String& scriptName, int scriptLine) override;
virtual void didCallInjectedScriptFunction(JSC::ExecState*) override;
virtual void frontendInitialized() override { }
+ virtual PassRefPtr<WTF::Stopwatch> executionStopwatch() override;
private:
friend InstrumentingAgents* instrumentationForWorkerGlobalScope(WorkerGlobalScope*);
WorkerRuntimeAgent* m_runtimeAgent;
Inspector::InspectorAgentRegistry m_agents;
std::unique_ptr<InspectorFrontendChannel> m_frontendChannel;
+ RefPtr<WTF::Stopwatch> m_executionStopwatch;
RefPtr<Inspector::InspectorBackendDispatcher> m_backendDispatcher;
Vector<InspectorInstrumentationCookie, 2> m_injectedScriptInstrumentationCookies;
};
+2014-10-10 Brian J. Burg <burg@cs.washington.edu>
+
+ Web Inspector: timelines should not count time elapsed while paused in the debugger
+ https://bugs.webkit.org/show_bug.cgi?id=136351
+
+ Reviewed by Timothy Hatcher.
+
+ Don't update the timeline's current time when the debugger is paused.
+
+ Start and end times for timeline records are now in seconds elapsed since timeline
+ recording started, rather than milliseconds since the epoch. Also convert code that
+ tracks page/resource load timings to use elapsed times rather than timestamps.
+
+ Add a workaround to preserve compatibility with old backends. Convert legacy timestamps
+ in multiple agents to elapsed times.
+
+ * UserInterface/Controllers/FrameResourceManager.js:
+ (WebInspector.FrameResourceManager.prototype.resourceRequestWillBeSent):
+ (WebInspector.FrameResourceManager.prototype.resourceRequestWasServedFromMemoryCache):
+ (WebInspector.FrameResourceManager.prototype.resourceRequestDidReceiveResponse):
+ (WebInspector.FrameResourceManager.prototype.resourceRequestDidReceiveData):
+ (WebInspector.FrameResourceManager.prototype.resourceRequestDidFinishLoading):
+ (WebInspector.FrameResourceManager.prototype.resourceRequestDidFailLoading):
+ (WebInspector.FrameResourceManager.prototype._addNewResourceToFrame):
+ * UserInterface/Controllers/ProbeManager.js:
+ * UserInterface/Controllers/TimelineManager.js:
+ (WebInspector.TimelineManager.prototype.computeElapsedTime): Forward to the active TimelineRecording.
+ (WebInspector.TimelineManager.prototype.eventRecorded.processRecord):
+ (WebInspector.TimelineManager.prototype.eventRecorded):
+ (WebInspector.TimelineManager.prototype.pageDidLoad):
+ (WebInspector.TimelineManager.prototype._loadNewRecording):
+ * UserInterface/Models/Probe.js:
+ (WebInspector.ProbeSample):
+ * UserInterface/Models/Resource.js:
+ (WebInspector.Resource.prototype.updateForRedirectResponse):
+ (WebInspector.Resource.prototype.updateForResponse):
+ (WebInspector.Resource.prototype.increaseSize):
+ (WebInspector.Resource.prototype.markAsFinished):
+ (WebInspector.Resource.prototype.markAsFailed):
+ (WebInspector.Resource.prototype.revertMarkAsFinished):
+ * UserInterface/Models/TimelineRecording.js:
+ (WebInspector.TimelineRecording.prototype.computeElapsedTime):
+ * UserInterface/Views/TimelineContentView.js:
+ (WebInspector.TimelineContentView.prototype._debuggerPaused):
+ (WebInspector.TimelineContentView.prototype._debuggerResumed):
+
+
2014-10-23 Jono Wells <jonowells@apple.com>
Web Inspector: Double border appearing in node sidebar in expanded items in OS X Mavericks.
if (this._waitingForMainFrameResourceTreePayload)
return;
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
var resource = this._resourceRequestIdentifierMap[requestIdentifier];
if (resource) {
// This is an existing request which is being redirected, update the resource.
console.assert(redirectResponse);
- resource.updateForRedirectResponse(request.url, request.headers, timestamp);
+ resource.updateForRedirectResponse(request.url, request.headers, elapsedTime);
return;
}
var initiatorSourceCodeLocation = this._initiatorSourceCodeLocationFromPayload(initiator);
// This is a new request, make a new resource and add it to the right frame.
- resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, request.url, type, request.method, request.headers, request.postData, timestamp, null, null, initiatorSourceCodeLocation);
+ resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, request.url, type, request.method, request.headers, request.postData, elapsedTime, null, null, initiatorSourceCodeLocation);
// Associate the resource with the requestIdentifier so it can be found in future loading events.
this._resourceRequestIdentifierMap[requestIdentifier] = resource;
console.assert(!(requestIdentifier in this._resourceRequestIdentifierMap));
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
var initiatorSourceCodeLocation = this._initiatorSourceCodeLocationFromPayload(initiator);
-
var response = cachedResourcePayload.response;
- var resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload.url, cachedResourcePayload.type, null, null, timestamp, null, null, initiatorSourceCodeLocation);
+ var resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload.url, cachedResourcePayload.type, null, null, elapsedTime, null, null, initiatorSourceCodeLocation);
resource.markAsCached();
- resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, timestamp);
- resource.markAsFinished(timestamp);
+ resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime);
+ resource.markAsFinished(elapsedTime);
if (cachedResourcePayload.sourceMapURL)
WebInspector.sourceMapManager.downloadSourceMap(cachedResourcePayload.sourceMapURL, resource.url, resource);
if (this._waitingForMainFrameResourceTreePayload)
return;
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
var resource = this._resourceRequestIdentifierMap[requestIdentifier];
// We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
// If we haven't found an existing Resource by now, then it is a resource that was loading when the inspector
// opened and we just missed the resourceRequestWillBeSent for it. So make a new resource and add it.
if (!resource) {
- resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, response.url, type, null, response.requestHeaders, timestamp, null, null);
+ resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, response.url, type, null, response.requestHeaders, elapsedTime, null, null);
// Associate the resource with the requestIdentifier so it can be found in future loading events.
this._resourceRequestIdentifierMap[requestIdentifier] = resource;
if (response.fromDiskCache)
resource.markAsCached();
- resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, timestamp);
+ resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, elapsedTime);
},
resourceRequestDidReceiveData: function(requestIdentifier, dataLength, encodedDataLength, timestamp)
return;
var resource = this._resourceRequestIdentifierMap[requestIdentifier];
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
// We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
// We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
if (!resource)
return;
- resource.increaseSize(dataLength, timestamp);
+ resource.increaseSize(dataLength, elapsedTime);
if (encodedDataLength !== -1)
resource.increaseTransferSize(encodedDataLength);
if (!resource)
return;
- resource.markAsFinished(timestamp);
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
+ resource.markAsFinished(elapsedTime);
if (sourceMapURL)
WebInspector.sourceMapManager.downloadSourceMap(sourceMapURL, resource.url, resource);
if (!resource)
return;
- resource.markAsFailed(canceled, timestamp);
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(timestamp);
+ resource.markAsFailed(canceled, elapsedTime);
if (resource === resource.parentFrame.provisionalMainResource)
resource.parentFrame.clearProvisionalLoad();
// Private
- _addNewResourceToFrame: function(requestIdentifier, frameIdentifier, loaderIdentifier, url, type, requestMethod, requestHeaders, requestData, timestamp, frameName, frameSecurityOrigin, initiatorSourceCodeLocation)
+ _addNewResourceToFrame: function(requestIdentifier, frameIdentifier, loaderIdentifier, url, type, requestMethod, requestHeaders, requestData, elapsedTime, frameName, frameSecurityOrigin, initiatorSourceCodeLocation)
{
console.assert(!this._waitingForMainFrameResourceTreePayload);
else if (frame.provisionalMainResource && frame.provisionalMainResource.url === url && frame.provisionalLoaderIdentifier === loaderIdentifier)
resource = frame.provisionalMainResource;
else {
- resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, timestamp, initiatorSourceCodeLocation);
+ resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, elapsedTime, initiatorSourceCodeLocation);
this._addResourceToFrame(frame, resource);
}
} else {
// This is a new request for a new frame, which is always the main resource.
- resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, timestamp, initiatorSourceCodeLocation);
+ resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, elapsedTime, initiatorSourceCodeLocation);
frame = new WebInspector.Frame(frameIdentifier, frameName, frameSecurityOrigin, loaderIdentifier, resource);
this._frameIdentifierMap[frame.id] = frame;
{
console.assert(this._probesByIdentifier.has(sample.probeId), "Unknown probe identifier specified for sample: ", sample);
var probe = this._probesByIdentifier.get(sample.probeId);
- probe.addSample(new WebInspector.ProbeSample(sample.sampleId, sample.batchId, sample.timestamp, sample.payload));
+ var elapsedTime = WebInspector.timelineManager.computeElapsedTime(sample.timestamp);
+ probe.addSample(new WebInspector.ProbeSample(sample.sampleId, sample.batchId, elapsedTime, sample.payload));
},
// Private
this._activeRecording = null;
},
+ computeElapsedTime: function(timestamp)
+ {
+ if (!this._activeRecording)
+ return 0;
+
+ return this._activeRecording.computeElapsedTime(timestamp);
+ },
+
// Protected
capturingStarted: function()
function processRecord(recordPayload, parentRecordPayload)
{
- // Convert the timestamps to seconds to match the resource timestamps.
- var startTime = recordPayload.startTime / 1000;
- var endTime = recordPayload.endTime / 1000;
-
+ var startTime = this.activeRecording.computeElapsedTime(recordPayload.startTime);
+ var endTime = this.activeRecording.computeElapsedTime(recordPayload.endTime);
var callFrames = this._callFramesFromPayload(recordPayload.stackTrace);
var significantCallFrame = null;
pageDidLoad: function(timestamp)
{
if (isNaN(WebInspector.frameResourceManager.mainFrame.loadEventTimestamp))
- WebInspector.frameResourceManager.mainFrame.markLoadEvent(timestamp);
+ WebInspector.frameResourceManager.mainFrame.markLoadEvent(this.activeRecording.computeElapsedTime(timestamp));
},
// Private
if (oldRecording)
oldRecording.unloaded();
+ this._legacyFirstRecordedTimestamp = NaN;
this._activeRecording = newRecording;
this.dispatchEventToListeners(WebInspector.TimelineManager.Event.RecordingLoaded, {oldRecording: oldRecording});
},
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-WebInspector.ProbeSample = function(sampleId, batchId, timestamp, payload)
+WebInspector.ProbeSample = function(sampleId, batchId, elapsedTime, payload)
{
this.sampleId = sampleId;
this.batchId = batchId;
- this.timestamp = timestamp;
+ this.timestamp = elapsedTime;
this.object = WebInspector.RemoteObject.fromPayload(payload);
};
return null;
},
- updateForRedirectResponse: function(url, requestHeaders, timestamp)
+ updateForRedirectResponse: function(url, requestHeaders, elapsedTime)
{
console.assert(!this._finished);
console.assert(!this._failed);
this._url = url;
this._requestHeaders = requestHeaders || {};
- this._lastRedirectReceivedTimestamp = timestamp || NaN;
+ this._lastRedirectReceivedTimestamp = elapsedTime || NaN;
if (oldURL !== url) {
// Delete the URL components so the URL is re-parsed the next time it is requested.
this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange);
},
- updateForResponse: function(url, mimeType, type, responseHeaders, statusCode, statusText, timestamp)
+ updateForResponse: function(url, mimeType, type, responseHeaders, statusCode, statusText, elapsedTime)
{
console.assert(!this._finished);
console.assert(!this._failed);
this._statusCode = statusCode;
this._statusText = statusText;
this._responseHeaders = responseHeaders || {};
- this._responseReceivedTimestamp = timestamp || NaN;
+ this._responseReceivedTimestamp = elapsedTime || NaN;
this._responseHeadersSize = String(this._statusCode).length + this._statusText.length + 12; // Extra length is for "HTTP/1.1 ", " ", and "\r\n".
for (var name in this._responseHeaders)
return false;
},
- increaseSize: function(dataLength, timestamp)
+ increaseSize: function(dataLength, elapsedTime)
{
console.assert(dataLength >= 0);
this._size += dataLength;
- this._lastDataReceivedTimestamp = timestamp || NaN;
+ this._lastDataReceivedTimestamp = elapsedTime || NaN;
this.dispatchEventToListeners(WebInspector.Resource.Event.SizeDidChange, {previousSize: previousSize});
this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange);
},
- markAsFinished: function(timestamp)
+ markAsFinished: function(elapsedTime)
{
console.assert(!this._failed);
console.assert(!this._canceled);
this._finished = true;
- this._finishedOrFailedTimestamp = timestamp || NaN;
+ this._finishedOrFailedTimestamp = elapsedTime || NaN;
this.dispatchEventToListeners(WebInspector.Resource.Event.LoadingDidFinish);
this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange);
this.requestContentFromBackendIfNeeded();
},
- markAsFailed: function(canceled, timestamp)
+ markAsFailed: function(canceled, elapsedTime)
{
console.assert(!this._finished);
this._failed = true;
this._canceled = canceled;
- this._finishedOrFailedTimestamp = timestamp || NaN;
+ this._finishedOrFailedTimestamp = elapsedTime || NaN;
this.dispatchEventToListeners(WebInspector.Resource.Event.LoadingDidFail);
this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange);
this.servicePendingContentRequests(true);
},
- revertMarkAsFinished: function(timestamp)
+ revertMarkAsFinished: function()
{
console.assert(!this._failed);
console.assert(!this._canceled);
this._displayName = displayName;
this._isWritable = true;
+ // For legacy backends, we compute the elapsed time of records relative to this timestamp.
+ this._legacyFirstRecordedTimestamp = NaN;
+
for (var key of Object.keys(WebInspector.TimelineRecord.Type)) {
var type = WebInspector.TimelineRecord.Type[key];
var timeline = new WebInspector.Timeline(type);
TimesUpdated: "timeline-recording-times-updated"
};
+WebInspector.TimelineRecording.TimestampThresholdForLegacyRecordConversion = 28800000; // Date.parse("Jan 1, 1970")
+
WebInspector.TimelineRecording.prototype = {
constructor: WebInspector.TimelineRecording,
__proto__: WebInspector.Object.prototype,
this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.SourceCodeTimelineAdded, {sourceCodeTimeline: sourceCodeTimeline});
},
+ computeElapsedTime: function(timestamp)
+ {
+ if (!timestamp || isNaN(timestamp))
+ return NaN;
+
+ // COMPATIBILITY (iOS8): old backends send timestamps (milliseconds since the epoch), rather
+ // than seconds elapsed since timeline capturing started. We approximate the latter by
+ // subtracting the start timestamp, as old versions did not use monotonic times.
+ if (isNaN(this._legacyFirstRecordedTimestamp))
+ this._legacyFirstRecordedTimestamp = timestamp;
+
+ // If the record's start time sems unreasonably large, treat it as a legacy timestamp.
+ if (timestamp > WebInspector.TimelineRecording.TimestampThresholdForLegacyRecordConversion)
+ return (timestamp - this._legacyFirstRecordedTimestamp) / 1000.0;
+
+ return timestamp;
+ },
+
// Private
_keyForRecord: function(record)
WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerPaused, this);
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerResumed, this);
+
this.showOverviewTimelineView();
};
this._stopUpdatingCurrentTime();
},
+ _debuggerPaused: function(event)
+ {
+ if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
+ return;
+
+ this._stopUpdatingCurrentTime();
+ },
+
+ _debuggerResumed: function(event)
+ {
+ if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
+ return;
+
+ this._startUpdatingCurrentTime();
+ },
+
_recordingTimesUpdated: function(event)
{
if (!this._waitingToResetCurrentTime)