Ignore:
Timestamp:
Oct 25, 2014, 3:05:57 PM (11 years ago)
Author:
Brian Burg
Message:

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.

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):

Location:
trunk/Source/JavaScriptCore/profiler
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/profiler/LegacyProfiler.cpp

    r174322 r175203  
    3333#include "CodeBlock.h"
    3434#include "CommonIdentifiers.h"
    35 #include "DebuggerCallFrame.h"
    3635#include "InternalFunction.h"
    3736#include "JSFunction.h"
     
    5150static CallIdentifier createCallIdentifierFromFunctionImp(ExecState*, JSObject*, const String& defaultSourceURL, unsigned defaultLineNumber, unsigned defaultColumnNumber);
    5251
    53 LegacyProfiler* LegacyProfiler::s_sharedLegacyProfiler = 0;
     52LegacyProfiler* LegacyProfiler::s_sharedLegacyProfiler = nullptr;
    5453
    5554LegacyProfiler* LegacyProfiler::profiler()
     
    5857        s_sharedLegacyProfiler = new LegacyProfiler();
    5958    return s_sharedLegacyProfiler;
    60 }   
    61 
    62 void LegacyProfiler::startProfiling(ExecState* exec, const String& title)
     59}
     60
     61void LegacyProfiler::startProfiling(ExecState* exec, const String& title, PassRefPtr<Stopwatch> stopwatch)
    6362{
    6463    if (!exec)
     
    7675
    7776    exec->vm().setEnabledProfiler(this);
    78     RefPtr<ProfileGenerator> profileGenerator = ProfileGenerator::create(exec, title, ++ProfilesUID);
     77    RefPtr<ProfileGenerator> profileGenerator = ProfileGenerator::create(exec, title, ++ProfilesUID, stopwatch);
    7978    m_currentProfiles.append(profileGenerator);
    8079}
     
    8382{
    8483    if (!exec)
    85         return 0;
     84        return nullptr;
    8685
    8786    JSGlobalObject* origin = exec->lexicalGlobalObject();
     
    9594            if (!m_currentProfiles.size())
    9695                exec->vm().setEnabledProfiler(nullptr);
    97            
     96
    9897            return returnProfile;
    9998        }
    10099    }
    101100
    102     return 0;
     101    return nullptr;
    103102}
    104103
     
    183182
    184183    callFunctionForProfilesWithGroup(std::bind(&ProfileGenerator::exceptionUnwind, std::placeholders::_1, handlerCallFrame, callIdentifier), m_currentProfiles, handlerCallFrame->lexicalGlobalObject()->profileGroup());
    185 }
    186 
    187 void LegacyProfiler::didPause(PassRefPtr<DebuggerCallFrame> prpCallFrame)
    188 {
    189     if (m_currentProfiles.isEmpty())
    190         return;
    191 
    192     RefPtr<DebuggerCallFrame> callFrame = prpCallFrame;
    193     CallIdentifier callIdentifier = createCallIdentifier(callFrame->exec(), JSValue(), StringImpl::empty(), 0, 0);
    194 
    195     callFunctionForProfilesWithGroup(std::bind(&ProfileGenerator::didPause, std::placeholders::_1, callFrame, callIdentifier), m_currentProfiles, callFrame->vmEntryGlobalObject()->profileGroup());
    196 }
    197 
    198 void LegacyProfiler::didContinue(PassRefPtr<DebuggerCallFrame> prpCallFrame)
    199 {
    200     if (m_currentProfiles.isEmpty())
    201         return;
    202 
    203     RefPtr<DebuggerCallFrame> callFrame = prpCallFrame;
    204     CallIdentifier callIdentifier = createCallIdentifier(callFrame->exec(), JSValue(), StringImpl::empty(), 0, 0);
    205 
    206     callFunctionForProfilesWithGroup(std::bind(&ProfileGenerator::didContinue, std::placeholders::_1, callFrame, callIdentifier), m_currentProfiles, callFrame->vmEntryGlobalObject()->profileGroup());
    207184}
    208185
  • trunk/Source/JavaScriptCore/profiler/LegacyProfiler.h

    r174322 r175203  
    3333#include <wtf/PassRefPtr.h>
    3434#include <wtf/RefPtr.h>
     35#include <wtf/Stopwatch.h>
    3536#include <wtf/Vector.h>
    3637
    3738namespace JSC {
    3839
    39 class DebuggerCallFrame;
    4040class ExecState;
    41 class VM;
    4241class JSGlobalObject;
    4342class JSObject;
     
    4948    WTF_MAKE_FAST_ALLOCATED;
    5049public:
    51     JS_EXPORT_PRIVATE static LegacyProfiler* profiler(); 
     50    JS_EXPORT_PRIVATE static LegacyProfiler* profiler();
    5251    static CallIdentifier createCallIdentifier(ExecState*, JSValue, const WTF::String& sourceURL, unsigned defaultLineNumber, unsigned defaultColumnNumber);
    5352
    54     JS_EXPORT_PRIVATE void startProfiling(ExecState*, const WTF::String& title);
     53    JS_EXPORT_PRIVATE void startProfiling(ExecState*, const WTF::String& title, PassRefPtr<Stopwatch>);
    5554    JS_EXPORT_PRIVATE PassRefPtr<Profile> stopProfiling(ExecState*, const WTF::String& title);
    5655    void stopProfiling(JSGlobalObject*);
     
    6766    void exceptionUnwind(ExecState* handlerCallFrame);
    6867
    69     void didPause(PassRefPtr<DebuggerCallFrame>);
    70     void didContinue(PassRefPtr<DebuggerCallFrame>);
    71 
    7268    const Vector<RefPtr<ProfileGenerator>>& currentProfiles() { return m_currentProfiles; };
    7369
  • trunk/Source/JavaScriptCore/profiler/Profile.cpp

    r173262 r175203  
    4545    // into the profiler will need to know which thread it is executing on.
    4646    m_rootNode = ProfileNode::create(nullptr, CallIdentifier(ASCIILiteral("Thread_1"), String(), 0, 0), nullptr);
    47     m_rootNode->appendCall(ProfileNode::Call(currentTime()));
     47    m_rootNode->appendCall(ProfileNode::Call(0.0));
    4848}
    4949
  • trunk/Source/JavaScriptCore/profiler/ProfileGenerator.cpp

    r174762 r175203  
    2929#include "CallFrame.h"
    3030#include "CodeBlock.h"
    31 #include "Debugger.h"
    3231#include "JSGlobalObject.h"
    3332#include "JSStringRef.h"
     
    4140namespace JSC {
    4241
    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)
     42PassRefPtr<ProfileGenerator> ProfileGenerator::create(ExecState* exec, const String& title, unsigned uid, PassRefPtr<Stopwatch> stopwatch)
     43{
     44    return adoptRef(new ProfileGenerator(exec, title, uid, stopwatch));
     45}
     46
     47ProfileGenerator::ProfileGenerator(ExecState* exec, const String& title, unsigned uid, PassRefPtr<Stopwatch> stopwatch)
    4948    : m_origin(exec ? exec->lexicalGlobalObject() : nullptr)
    5049    , m_profileGroup(exec ? exec->lexicalGlobalObject()->profileGroup() : 0)
    51     , m_debuggerPausedTimestamp(NAN)
     50    , m_stopwatch(stopwatch)
    5251    , m_foundConsoleStartParent(false)
    5352    , m_suspended(false)
    5453{
    55     if (Debugger* debugger = exec->lexicalGlobalObject()->debugger())
    56         m_debuggerPausedTimestamp = debugger->isPaused() ? currentTime() : NAN;
    57 
    5854    m_profile = Profile::create(title, uid);
    5955    m_currentNode = m_rootNode = m_profile->rootNode();
     
    8682        visitor->computeLineAndColumn(line, column);
    8783        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()));
     84        // Assume that profile times are relative to when the |console.profile| command is evaluated.
     85        // This matches the logic in JSStartProfiling() and InspectorTimelineAgent::startFromConsole().
     86        m_currentNode->appendCall(ProfileNode::Call(0.0));
    8987        m_rootNode->spliceNode(m_currentNode.get());
    9088
     
    119117
    120118    if (std::isnan(startTime))
    121         startTime = currentTime();
    122 
    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 (!std::isnan(m_debuggerPausedTimestamp))
    126         startTime = NAN;
     119        startTime = m_stopwatch->elapsedTime();
    127120
    128121    node->appendCall(ProfileNode::Call(startTime));
     
    135128    ProfileNode::Call& last = node->lastCall();
    136129
    137     // If the debugger is paused, ignore the interval that ends now.
    138     if (!std::isnan(m_debuggerPausedTimestamp) && !std::isnan(last.elapsedTime()))
    139         return;
    140 
    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 (!std::isnan(m_debuggerPausedTimestamp)) {
    144         last.setStartTime(m_debuggerPausedTimestamp);
    145         last.setElapsedTime(0.0);
    146         return;
    147     }
    148 
    149     // Otherwise, add the interval ending now to elapsed time.
    150130    double previousElapsedTime = std::isnan(last.elapsedTime()) ? 0.0 : last.elapsedTime();
    151     double newlyElapsedTime = currentTime() - last.startTime();
     131    double newlyElapsedTime = m_stopwatch->elapsedTime() - last.startTime();
    152132    last.setElapsedTime(previousElapsedTime + newlyElapsedTime);
    153133}
     
    181161
    182162    m_currentNode = calleeNode;
    183     beginCallEntry(calleeNode.get());
     163    beginCallEntry(calleeNode.get(), m_stopwatch->elapsedTime());
    184164}
    185165
     
    224204}
    225205
    226 void ProfileGenerator::didPause(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&)
    227 {
    228     ASSERT(std::isnan(m_debuggerPausedTimestamp));
    229 
    230     m_debuggerPausedTimestamp = currentTime();
    231 
    232     for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent()) {
    233         ProfileNode::Call& last = node->lastCall();
    234         ASSERT(!std::isnan(last.startTime()));
    235 
    236         double previousElapsedTime = std::isnan(last.elapsedTime()) ? 0.0 : last.elapsedTime();
    237         double additionalElapsedTime = m_debuggerPausedTimestamp - last.startTime();
    238         last.setStartTime(NAN);
    239         last.setElapsedTime(previousElapsedTime + additionalElapsedTime);
    240     }
    241 }
    242 
    243 void ProfileGenerator::didContinue(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&)
    244 {
    245     ASSERT(!std::isnan(m_debuggerPausedTimestamp));
    246 
    247     for (ProfileNode* node = m_currentNode.get(); node != m_profile->rootNode(); node = node->parent())
    248         node->lastCall().setStartTime(m_debuggerPausedTimestamp);
    249 
    250     m_debuggerPausedTimestamp = NAN;
    251 }
    252 
    253206void ProfileGenerator::stopProfiling()
    254207{
  • trunk/Source/JavaScriptCore/profiler/ProfileGenerator.h

    r174322 r175203  
    3030#include <wtf/RefCounted.h>
    3131#include <wtf/RefPtr.h>
     32#include <wtf/Stopwatch.h>
    3233#include <wtf/text/WTFString.h>
    3334
     
    4344    class ProfileGenerator : public RefCounted<ProfileGenerator>  {
    4445    public:
    45         static PassRefPtr<ProfileGenerator> create(ExecState*, const WTF::String& title, unsigned uid);
     46        static PassRefPtr<ProfileGenerator> create(ExecState*, const WTF::String& title, unsigned uid, PassRefPtr<Stopwatch>);
    4647
    4748        // Members
     
    5556        void exceptionUnwind(ExecState* handlerCallFrame, const CallIdentifier&);
    5657
    57         void didPause(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&);
    58         void didContinue(PassRefPtr<DebuggerCallFrame>, const CallIdentifier&);
    59 
    6058        void setIsSuspended(bool suspended) { ASSERT(m_suspended != suspended); m_suspended = suspended; }
    6159
     
    6361
    6462    private:
    65         ProfileGenerator(ExecState*, const WTF::String& title, unsigned uid);
     63        ProfileGenerator(ExecState*, const WTF::String& title, unsigned uid, PassRefPtr<Stopwatch>);
    6664        void addParentForConsoleStart(ExecState*);
    6765
     
    6967        void removeProfileEnd();
    7068
    71         void beginCallEntry(ProfileNode*, double startTime = NAN);
     69        void beginCallEntry(ProfileNode*, double startTime);
    7270        void endCallEntry(ProfileNode*);
    7371
     
    7573        JSGlobalObject* m_origin;
    7674        unsigned m_profileGroup;
    77         // Timestamp is set to NAN when the debugger is not currently paused.
    78         double m_debuggerPausedTimestamp;
     75        RefPtr<Stopwatch> m_stopwatch;
    7976        RefPtr<ProfileNode> m_rootNode;
    8077        RefPtr<ProfileNode> m_currentNode;
Note: See TracChangeset for help on using the changeset viewer.