mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 03:15:11 +00:00
Bug 1668219 - [devtools] Remove devtools/client/performance. r=julienw.
Differential Revision: https://phabricator.services.mozilla.com/D145461
This commit is contained in:
parent
e1f5f32c3a
commit
30db855c84
@ -32,15 +32,12 @@ devtools.jar:
|
||||
skin/images/breadcrumbs-scrollbutton.svg (themes/images/breadcrumbs-scrollbutton.svg)
|
||||
skin/animation.css (themes/animation.css)
|
||||
skin/perf.css (themes/perf.css)
|
||||
skin/performance.css (themes/performance.css)
|
||||
skin/memory.css (themes/memory.css)
|
||||
skin/storage.css (themes/storage.css)
|
||||
skin/splitview.css (themes/splitview.css)
|
||||
skin/styleeditor.css (themes/styleeditor.css)
|
||||
skin/components-frame.css (themes/components-frame.css)
|
||||
skin/components-h-split-box.css (themes/components-h-split-box.css)
|
||||
skin/jit-optimizations.css (themes/jit-optimizations.css)
|
||||
skin/images/filter.svg (themes/images/filter.svg)
|
||||
skin/images/filter-small.svg (themes/images/filter-small.svg)
|
||||
skin/images/search.svg (themes/images/search.svg)
|
||||
skin/images/item-toggle.svg (themes/images/item-toggle.svg)
|
||||
@ -167,9 +164,6 @@ devtools.jar:
|
||||
skin/images/pseudo-class.svg (themes/images/pseudo-class.svg)
|
||||
skin/images/copy.svg (themes/images/copy.svg)
|
||||
skin/images/animation-fast-track.svg (themes/images/animation-fast-track.svg)
|
||||
skin/images/performance-details-waterfall.svg (themes/images/performance-details-waterfall.svg)
|
||||
skin/images/performance-details-call-tree.svg (themes/images/performance-details-call-tree.svg)
|
||||
skin/images/performance-details-flamegraph.svg (themes/images/performance-details-flamegraph.svg)
|
||||
skin/breadcrumbs.css (themes/breadcrumbs.css)
|
||||
skin/chart.css (themes/chart.css)
|
||||
skin/widgets.css (themes/widgets.css)
|
||||
@ -336,7 +330,6 @@ devtools.jar:
|
||||
content/debugger/src/utils/editor/source-editor.css (debugger/src/utils/editor/source-editor.css)
|
||||
|
||||
# Perfomance
|
||||
content/performance/index.xhtml (performance/index.xhtml)
|
||||
content/performance-new/index.xhtml (performance-new/index.xhtml)
|
||||
|
||||
# Memory
|
||||
|
@ -1,35 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# LOCALIZATION NOTE These strings are used within the JIT tools
|
||||
# in the Performance Tools which is available from the Web Developer
|
||||
# sub-menu -> 'Performance' The correct localization of this file might
|
||||
# be to keep it in English, or another language commonly spoken among
|
||||
# web developers. You want to make that choice consistent across the
|
||||
# developer tools. A good criteria is the language in which you'd find the best
|
||||
# documentation on web development on the web.
|
||||
|
||||
# LOCALIZATION NOTE (jit.title):
|
||||
# This string is displayed in the header of the JIT Optimizations view.
|
||||
jit.title=JIT Optimizations
|
||||
|
||||
# LOCALIZATION NOTE (jit.optimizationFailure):
|
||||
# This string is displayed in a tooltip when no JIT optimizations were detected.
|
||||
jit.optimizationFailure=Optimization failed
|
||||
|
||||
# LOCALIZATION NOTE (jit.samples):
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# This string is displayed for the unit representing the number of times a
|
||||
# frame is sampled.
|
||||
# "#1" represents the number of samples
|
||||
# example: 30 samples
|
||||
jit.samples=#1 sample;#1 samples
|
||||
|
||||
# LOCALIZATION NOTE (jit.types):
|
||||
# This string is displayed for the group of Ion Types in the optimizations view.
|
||||
jit.types=Types
|
||||
|
||||
# LOCALIZATION NOTE (jit.attempts):
|
||||
# This string is displayed for the group of optimization attempts in the optimizations view.
|
||||
jit.attempts=Attempts
|
@ -1,138 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# LOCALIZATION NOTE These strings are used inside the Performance Tools
|
||||
# which is available from the Web Developer sub-menu -> 'Performance'.
|
||||
# The correct localization of this file might be to keep it in
|
||||
# English, or another language commonly spoken among web developers.
|
||||
# You want to make that choice consistent across the developer tools.
|
||||
# A good criteria is the language in which you'd find the best
|
||||
# documentation on web development on the web. These strings
|
||||
# are specifically for marker names in the performance tool.
|
||||
|
||||
# LOCALIZATION NOTE (marker.label.*):
|
||||
# These strings are displayed in the Performance Tool waterfall, identifying markers.
|
||||
# We want to use the same wording as Google Chrome when appropriate.
|
||||
marker.label.styles=Recalculate Style
|
||||
marker.label.stylesApplyChanges=Apply Style Changes
|
||||
marker.label.reflow=Layout
|
||||
marker.label.paint=Paint
|
||||
marker.label.composite=Composite Layers
|
||||
marker.label.compositeForwardTransaction=Composite Request Sent
|
||||
marker.label.javascript=Function Call
|
||||
marker.label.parseHTML=Parse HTML
|
||||
marker.label.parseXML=Parse XML
|
||||
marker.label.domevent=DOM Event
|
||||
marker.label.consoleTime=Console
|
||||
marker.label.garbageCollection2=Garbage Collection
|
||||
marker.label.garbageCollection.incremental=Incremental GC
|
||||
marker.label.garbageCollection.nonIncremental=Non-incremental GC
|
||||
marker.label.minorGC=Minor GC
|
||||
marker.label.cycleCollection=Cycle Collection
|
||||
marker.label.cycleCollection.forgetSkippable=CC Graph Reduction
|
||||
marker.label.timestamp=Timestamp
|
||||
marker.label.worker=Worker
|
||||
marker.label.messagePort=MessagePort
|
||||
marker.label.unknown=Unknown
|
||||
|
||||
# LOCALIZATION NOTE (marker.label.javascript.*):
|
||||
# These strings are displayed as JavaScript markers that have special
|
||||
# reasons that can be translated.
|
||||
marker.label.javascript.scriptElement=Script Tag
|
||||
marker.label.javascript.promiseCallback=Promise Callback
|
||||
marker.label.javascript.promiseInit=Promise Init
|
||||
marker.label.javascript.workerRunnable=Worker
|
||||
marker.label.javascript.jsURI=JavaScript URI
|
||||
marker.label.javascript.eventHandler=Event Handler
|
||||
|
||||
# LOCALIZATION NOTE (marker.field.*):
|
||||
# Strings used in the waterfall sidebar as property names.
|
||||
|
||||
# General marker fields
|
||||
marker.field.start=Start:
|
||||
marker.field.end=End:
|
||||
marker.field.duration=Duration:
|
||||
|
||||
# General "reason" for a marker (JavaScript, Garbage Collection)
|
||||
marker.field.causeName=Cause:
|
||||
# General "type" for a marker (Cycle Collection, Garbage Collection)
|
||||
marker.field.type=Type:
|
||||
# General "label" for a marker (user defined)
|
||||
marker.field.label=Label:
|
||||
|
||||
# Field names for stack values
|
||||
marker.field.stack=Stack:
|
||||
marker.field.startStack=Stack at start:
|
||||
marker.field.endStack=Stack at end:
|
||||
|
||||
# %S is the "Async Cause" of a marker, and this signifies that the cause
|
||||
# was an asynchronous one in a displayed stack.
|
||||
marker.field.asyncStack=(Async: %S)
|
||||
|
||||
# For console.time markers
|
||||
marker.field.consoleTimerName=Timer Name:
|
||||
|
||||
# For DOM Event markers
|
||||
marker.field.DOMEventType=Event Type:
|
||||
marker.field.DOMEventPhase=Phase:
|
||||
|
||||
# Non-incremental cause for a Garbage Collection marker
|
||||
marker.field.nonIncrementalCause=Non-incremental Cause:
|
||||
|
||||
# For "Recalculate Style" markers
|
||||
marker.field.isAnimationOnly=Animation Only:
|
||||
|
||||
# The type of operation performed by a Worker.
|
||||
marker.worker.serializeDataOffMainThread=Serialize data in Worker
|
||||
marker.worker.serializeDataOnMainThread=Serialize data on the main thread
|
||||
marker.worker.deserializeDataOffMainThread=Deserialize data in Worker
|
||||
marker.worker.deserializeDataOnMainThread=Deserialize data on the main thread
|
||||
|
||||
# The type of operation performed by a MessagePort
|
||||
marker.messagePort.serializeData=Serialize data
|
||||
marker.messagePort.deserializeData=Deserialize data
|
||||
|
||||
# Strings used in the waterfall sidebar as values.
|
||||
marker.value.unknownFrame=<unknown location>
|
||||
marker.value.DOMEventTargetPhase=Target
|
||||
marker.value.DOMEventCapturingPhase=Capture
|
||||
marker.value.DOMEventBubblingPhase=Bubbling
|
||||
|
||||
# LOCALIZATION NOTE (marker.gcreason.label.*):
|
||||
# These strings are used to give a concise but readable description of a GC reason.
|
||||
marker.gcreason.label.API=API Call
|
||||
marker.gcreason.label.EAGER_ALLOC_TRIGGER=Eager Allocation Trigger
|
||||
marker.gcreason.label.DESTROY_RUNTIME=Shutdown
|
||||
marker.gcreason.label.LAST_DITCH=Out of Memory
|
||||
marker.gcreason.label.TOO_MUCH_MALLOC=Too Many Bytes Allocated
|
||||
marker.gcreason.label.ALLOC_TRIGGER=Too Many Allocations
|
||||
marker.gcreason.label.DEBUG_GC=Debug GC
|
||||
marker.gcreason.label.COMPARTMENT_REVIVED=Dead Global Revived
|
||||
marker.gcreason.label.RESET=Finish Incremental Cycle
|
||||
marker.gcreason.label.OUT_OF_NURSERY=Nursery is Full
|
||||
marker.gcreason.label.EVICT_NURSERY=Nursery Eviction
|
||||
marker.gcreason.label.FULL_STORE_BUFFER=Nursery Objects Too Active
|
||||
marker.gcreason.label.SHARED_MEMORY_LIMIT=Large Allocation Failed
|
||||
marker.gcreason.label.PERIODIC_FULL_GC=Periodic Full GC
|
||||
marker.gcreason.label.INCREMENTAL_TOO_SLOW=Allocations Rate Too Fast
|
||||
marker.gcreason.label.COMPONENT_UTILS=Cu.forceGC
|
||||
marker.gcreason.label.MEM_PRESSURE=Low Memory
|
||||
marker.gcreason.label.CC_FINISHED=Cycle Collection Finished
|
||||
marker.gcreason.label.CC_FORCED=Forced by Cycle Collection
|
||||
marker.gcreason.label.LOAD_END=Page Load Finished
|
||||
marker.gcreason.label.PAGE_HIDE=Moved to Background
|
||||
marker.gcreason.label.NSJSCONTEXT_DESTROY=Destroy JS Context
|
||||
marker.gcreason.label.SET_NEW_DOCUMENT=New Document
|
||||
marker.gcreason.label.SET_DOC_SHELL=New Document
|
||||
marker.gcreason.label.DOM_UTILS=API Call
|
||||
marker.gcreason.label.DOM_IPC=IPC
|
||||
marker.gcreason.label.DOM_WORKER=Periodic Worker GC
|
||||
marker.gcreason.label.INTER_SLICE_GC=Periodic Incremental GC Slice
|
||||
marker.gcreason.label.FULL_GC_TIMER=Periodic Full GC
|
||||
marker.gcreason.label.SHUTDOWN_CC=Shutdown
|
||||
marker.gcreason.label.DOM_WINDOW_UTILS=User Inactive
|
||||
marker.gcreason.label.USER_INACTIVE=User Inactive
|
||||
|
||||
# The name of a nursery collection.
|
||||
marker.nurseryCollection=Nursery Collection
|
@ -1,129 +0,0 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<!-- LOCALIZATION NOTE : FILE This file contains the Performance strings -->
|
||||
<!-- LOCALIZATION NOTE : FILE Do not translate commandkey -->
|
||||
|
||||
<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
|
||||
- keep it in English, or another language commonly spoken among web developers.
|
||||
- You want to make that choice consistent across the developer tools.
|
||||
- A good criteria is the language in which you'd find the best
|
||||
- documentation on web development on the web. -->
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.bufferStatusTooltip): This string
|
||||
- is displayed as the tooltip for the buffer capacity during a recording. -->
|
||||
<!ENTITY performanceUI.bufferStatusTooltip "The profiler stores samples in a circular buffer, and once the buffer reaches the limit for a recording, newer samples begin to overwrite samples at the beginning of the recording.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.disabledRealTime.disabledE10S): This string
|
||||
- is displayed as a message for why the real time overview graph is disabled
|
||||
- when running on a build that can run multiprocess Firefox, but just is not enabled. -->
|
||||
<!ENTITY performanceUI.disabledRealTime.disabledE10S "Enable multiprocess Firefox in preferences for rendering recording data in realtime.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.bufferStatusFull): This string
|
||||
- is displayed when the profiler's circular buffer has started to overlap. -->
|
||||
<!ENTITY performanceUI.bufferStatusFull "The buffer is full. Older samples are now being overwritten.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.unavailableNoticePB): This is the label shown
|
||||
- in the details view while the profiler is unavailable, for example, while
|
||||
- in Private Browsing mode. -->
|
||||
<!ENTITY performanceUI.unavailableNoticePB "Recording a profile is currently unavailable. Please close all private browsing windows and try again.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.loadingNotice): This is the label shown
|
||||
- in the details view while loading a profile. -->
|
||||
<!ENTITY performanceUI.loadingNotice "Loading…">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.toolbar.*): These strings are displayed
|
||||
- in the toolbar on buttons that select which view is currently shown. -->
|
||||
<!ENTITY performanceUI.toolbar.waterfall "Waterfall">
|
||||
<!ENTITY performanceUI.toolbar.waterfall.tooltiptext "Shows the different operations the browser is performing during the recording, laid out sequentially as a waterfall.">
|
||||
<!ENTITY performanceUI.toolbar.js-calltree "Call Tree">
|
||||
<!ENTITY performanceUI.toolbar.js-calltree.tooltiptext "Highlights JavaScript functions where the browser spent most time during the recording.">
|
||||
<!ENTITY performanceUI.toolbar.memory-calltree "Allocations">
|
||||
<!ENTITY performanceUI.toolbar.allocations.tooltiptext "Shows where memory was allocated during the recording.">
|
||||
<!ENTITY performanceUI.toolbar.js-flamegraph "JS Flame Chart">
|
||||
<!ENTITY performanceUI.toolbar.js-flamegraph.tooltiptext "Shows the JavaScript call stack over the course of the recording.">
|
||||
<!ENTITY performanceUI.toolbar.memory-flamegraph "Allocations Flame Chart">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.table.*): These strings are displayed
|
||||
- in the call tree headers for a recording. -->
|
||||
<!ENTITY performanceUI.table.totalDuration "Total Time">
|
||||
<!ENTITY performanceUI.table.totalDuration.tooltip "The amount of time spent in this function and functions it calls.">
|
||||
<!ENTITY performanceUI.table.selfDuration "Self Time">
|
||||
<!ENTITY performanceUI.table.selfDuration.tooltip "The amount of time spent only within this function.">
|
||||
<!ENTITY performanceUI.table.totalPercentage "Total Cost">
|
||||
<!ENTITY performanceUI.table.totalPercentage.tooltip "The percentage of time spent in this function and functions it calls.">
|
||||
<!ENTITY performanceUI.table.selfPercentage "Self Cost">
|
||||
<!ENTITY performanceUI.table.selfPercentage.tooltip "The percentage of time spent only within this function.">
|
||||
<!ENTITY performanceUI.table.samples "Samples">
|
||||
<!ENTITY performanceUI.table.samples.tooltip "The number of times this function was on the stack when the profiler took a sample.">
|
||||
<!ENTITY performanceUI.table.function "Function">
|
||||
<!ENTITY performanceUI.table.function.tooltip "The name and source location of the sampled function.">
|
||||
<!ENTITY performanceUI.table.totalAlloc.tooltip "The total number of Object allocations sampled at this location and in callees.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.options.filter.tooltiptext): This string
|
||||
- is displayed next to the filter button-->
|
||||
<!ENTITY performanceUI.options.filter.tooltiptext "Select what data to display in the timeline">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.options.gear.tooltiptext): This is the
|
||||
- tooltip for the options button. -->
|
||||
<!ENTITY performanceUI.options.gear.tooltiptext "Configure performance preferences.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.invertTree): This is the label shown next to
|
||||
- a checkbox that inverts and un-inverts the profiler's call tree. -->
|
||||
<!ENTITY performanceUI.invertTree "Invert Call Tree">
|
||||
<!ENTITY performanceUI.invertTree.tooltiptext "Inverting the call tree displays the profiled call paths starting from the youngest frames and expanding out to the older frames.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.invertFlameGraph): This is the label shown next to
|
||||
- a checkbox that inverts and un-inverts the profiler's flame graph. -->
|
||||
<!ENTITY performanceUI.invertFlameGraph "Invert Flame Chart">
|
||||
<!ENTITY performanceUI.invertFlameGraph.tooltiptext "Inverting the flame chart displays the profiled call paths starting from the youngest frames and expanding out to the older frames.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.showPlatformData): This is the
|
||||
- label for the checkbox that toggles whether or not Gecko platform data
|
||||
- is displayed in the profiler. -->
|
||||
<!ENTITY performanceUI.showPlatformData "Show Gecko Platform Data">
|
||||
<!ENTITY performanceUI.showPlatformData.tooltiptext "Showing platform data enables the JavaScript Profiler reports to include Gecko platform symbols.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.showJITOptimizations): This string
|
||||
- is displayed next to a checkbox determining whether or not JIT optimization data
|
||||
- should be displayed. -->
|
||||
<!ENTITY performanceUI.showJITOptimizations "Show JIT Optimizations">
|
||||
<!ENTITY performanceUI.showJITOptimizations.tooltiptext "Show JIT optimization data sampled in each JavaScript frame.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.flattenTreeRecursion): This is the
|
||||
- label for the checkbox that toggles the flattening of tree recursion in inspected
|
||||
- functions in the profiler. -->
|
||||
<!ENTITY performanceUI.flattenTreeRecursion "Flatten Tree Recursion">
|
||||
<!ENTITY performanceUI.flattenTreeRecursion.tooltiptext "Flatten recursion when inspecting functions.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.enableMemory): This string
|
||||
- is displayed next to a checkbox determining whether or not memory
|
||||
- measurements are enabled. -->
|
||||
<!ENTITY performanceUI.enableMemory "Record Memory">
|
||||
<!ENTITY performanceUI.enableMemory.tooltiptext "Record memory consumption while profiling.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.enableAllocations): This string
|
||||
- is displayed next to a checkbox determining whether or not allocation
|
||||
- measurements are enabled. -->
|
||||
<!ENTITY performanceUI.enableAllocations "Record Allocations">
|
||||
<!ENTITY performanceUI.enableAllocations.tooltiptext "Record Object allocations while profiling.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.enableFramerate): This string
|
||||
- is displayed next to a checkbox determining whether or not framerate
|
||||
- is recorded. -->
|
||||
<!ENTITY performanceUI.enableFramerate "Record Framerate">
|
||||
<!ENTITY performanceUI.enableFramerate.tooltiptext "Record framerate while profiling.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.console.recordingNoticeStart/recordingNoticeEnd):
|
||||
- This string is displayed when a recording is selected that started via console.profile.
|
||||
- Wraps the command used to start, like "Currently recording via console.profile("label")" -->
|
||||
<!ENTITY performanceUI.console.recordingNoticeStart "Currently recording via">
|
||||
<!ENTITY performanceUI.console.recordingNoticeEnd "">
|
||||
|
||||
<!-- LOCALIZATION NOTE (performanceUI.console.stopCommandStart/stopCommandEnd):
|
||||
- This string is displayed when a recording is selected that started via console.profile.
|
||||
- Indicates how to stop the recording, wrapping the command, like
|
||||
- "Stop recording by entering console.profileEnd("label") into the console." -->
|
||||
<!ENTITY performanceUI.console.stopCommandStart "Stop recording by entering">
|
||||
<!ENTITY performanceUI.console.stopCommandEnd "into the console.">
|
@ -1,160 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# LOCALIZATION NOTE These strings are used inside the Performance Tools
|
||||
# which is available from the Web Developer sub-menu -> 'Performance'.
|
||||
# The correct localization of this file might be to keep it in
|
||||
# English, or another language commonly spoken among web developers.
|
||||
# You want to make that choice consistent across the developer tools.
|
||||
# A good criteria is the language in which you'd find the best
|
||||
# documentation on web development on the web.
|
||||
|
||||
# LOCALIZATION NOTE (noRecordingsText): The text to display in the
|
||||
# recordings menu when there are no recorded profiles yet.
|
||||
noRecordingsText=There are no profiles yet.
|
||||
|
||||
# LOCALIZATION NOTE (recordingsList.itemLabel):
|
||||
# This string is displayed in the recordings list of the Performance Tools,
|
||||
# identifying a set of function calls. %S represents the number of recording,
|
||||
# iterating for every new recording, resulting in "Recording #1", "Recording #2", etc.
|
||||
recordingsList.itemLabel=Recording #%S
|
||||
|
||||
# LOCALIZATION NOTE (recordingsList.recordingLabel):
|
||||
# This string is displayed in the recordings list of the Performance Tools,
|
||||
# for an item that has not finished recording.
|
||||
recordingsList.recordingLabel=In progress…
|
||||
|
||||
# LOCALIZATION NOTE (recordingsList.loadingLabel):
|
||||
# This string is displayed in the recordings list of the Performance Tools,
|
||||
# for an item that is finished and is loading.
|
||||
recordingsList.loadingLabel=Loading…
|
||||
|
||||
# LOCALIZATION NOTE (recordingsList.durationLabel):
|
||||
# This string is displayed in the recordings list of the Performance Tools,
|
||||
# for an item that has finished recording.
|
||||
recordingsList.durationLabel=%S ms
|
||||
|
||||
# LOCALIZATION NOTE (recordingsList.saveLabel):
|
||||
# This string is displayed in the recordings list of the Performance Tools,
|
||||
# for saving an item to disk.
|
||||
recordingsList.saveLabel=Save
|
||||
|
||||
# LOCALIZATION NOTE (graphs.fps):
|
||||
# This string is displayed in the framerate graph of the Performance Tools,
|
||||
# as the unit used to measure frames per second. This label should be kept
|
||||
# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
|
||||
graphs.fps=fps
|
||||
|
||||
# LOCALIZATION NOTE (graphs.ms):
|
||||
# This string is displayed in the flamegraph of the Performance Tools,
|
||||
# as the unit used to measure time (in milliseconds). This label should be kept
|
||||
# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
|
||||
graphs.ms=ms
|
||||
|
||||
# LOCALIZATION NOTE (graphs.memory):
|
||||
# This string is displayed in the memory graph of the Performance tool,
|
||||
# as the unit used to memory consumption. This label should be kept
|
||||
# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
|
||||
graphs.memory=MB
|
||||
|
||||
# LOCALIZATION NOTE (category.*):
|
||||
# These strings are displayed in the categories graph of the Performance Tools,
|
||||
# as the legend for each block in every bar. These labels should be kept
|
||||
# AS SHORT AS POSSIBLE so they don't obstruct important parts of the graph.
|
||||
category.other=Gecko
|
||||
category.layout=Layout
|
||||
category.js=JIT
|
||||
category.gc=GC
|
||||
category.network=Network
|
||||
category.graphics=Graphics
|
||||
category.dom=DOM
|
||||
category.idle=Idle
|
||||
category.tools=Tools
|
||||
|
||||
# LOCALIZATION NOTE (table.bytes):
|
||||
# This string is displayed in the call tree after bytesize units.
|
||||
# %S represents the value in bytes.
|
||||
table.bytes=%S B
|
||||
|
||||
# LOCALIZATION NOTE (table.ms2):
|
||||
# This string is displayed in the call tree after units of time in milliseconds.
|
||||
# %S represents the value in milliseconds.
|
||||
table.ms2=%S ms
|
||||
|
||||
# LOCALIZATION NOTE (table.percentage3):
|
||||
# This string is displayed in the call tree after units representing percentages.
|
||||
# %S represents the value in percentage with two decimal points, localized.
|
||||
# there are two "%" after %S to escape and display "%"
|
||||
table.percentage3=%S%%
|
||||
|
||||
# LOCALIZATION NOTE (table.root):
|
||||
# This string is displayed in the call tree for the root node.
|
||||
table.root=(root)
|
||||
|
||||
# LOCALIZATION NOTE (table.idle):
|
||||
# This string is displayed in the call tree for the idle blocks.
|
||||
table.idle=(idle)
|
||||
|
||||
# LOCALIZATION NOTE (table.url.tooltiptext):
|
||||
# This string is displayed in the call tree as the tooltip text for the url
|
||||
# labels which, when clicked, jump to the debugger.
|
||||
table.url.tooltiptext=View source in Debugger
|
||||
|
||||
# LOCALIZATION NOTE (table.view-optimizations.tooltiptext2):
|
||||
# This string is displayed in the icon displayed next to frames that
|
||||
# have optimization data
|
||||
table.view-optimizations.tooltiptext2=Frame contains JIT optimization data
|
||||
|
||||
# LOCALIZATION NOTE (recordingsList.importDialogTitle):
|
||||
# This string is displayed as a title for importing a recoring from disk.
|
||||
recordingsList.importDialogTitle=Import recording…
|
||||
|
||||
# LOCALIZATION NOTE (recordingsList.saveDialogTitle):
|
||||
# This string is displayed as a title for saving a recording to disk.
|
||||
recordingsList.saveDialogTitle=Save recording…
|
||||
|
||||
# LOCALIZATION NOTE (recordingsList.saveDialogJSONFilter):
|
||||
# This string is displayed as a filter for saving a recording to disk.
|
||||
recordingsList.saveDialogJSONFilter=JSON Files
|
||||
|
||||
# LOCALIZATION NOTE (recordingsList.saveDialogAllFilter):
|
||||
# This string is displayed as a filter for saving a recording to disk.
|
||||
recordingsList.saveDialogAllFilter=All Files
|
||||
|
||||
# LOCALIZATION NOTE (timeline.tick):
|
||||
# This string is displayed in the timeline overview, for delimiting ticks
|
||||
# by time, in milliseconds.
|
||||
timeline.tick=%S ms
|
||||
|
||||
# LOCALIZATION NOTE (timeline.records):
|
||||
# This string is displayed in the timeline waterfall, as a title for the menu.
|
||||
timeline.records=RECORDS
|
||||
|
||||
# LOCALIZATION NOTE (profiler.bufferFull):
|
||||
# This string is displayed when recording, indicating how much of the
|
||||
# buffer is currently be used.
|
||||
# %S is the percentage of the buffer used -- there are two "%"s after to escape
|
||||
# the % that is actually displayed.
|
||||
# Example: "Buffer 54% full"
|
||||
profiler.bufferFull=Buffer %S%% full
|
||||
|
||||
# LOCALIZATION NOTE (recordings.start):
|
||||
# The label shown on the main recording buttons to start recording.
|
||||
recordings.start=Start Recording Performance
|
||||
|
||||
# LOCALIZATION NOTE (recordings.stop):
|
||||
# The label shown on the main recording buttons to stop recording.
|
||||
recordings.stop=Stop Recording Performance
|
||||
|
||||
# LOCALIZATION NOTE (recordings.start.tooltip):
|
||||
# This string is displayed as a tooltip on a button that starts a new profile.
|
||||
recordings.start.tooltip=Toggle the recording state of a performance recording.
|
||||
|
||||
# LOCALIZATION NOTE (recordings.import.tooltip):
|
||||
# This string is displayed on a button that opens a dialog to import a saved profile data file.
|
||||
recordings.import.tooltip=Import…
|
||||
|
||||
# LOCALIZATION NOTE (recordings.clear.tooltip):
|
||||
# This string is displayed on a button that removes all the recordings.
|
||||
recordings.clear.tooltip=Clear
|
@ -19,7 +19,6 @@ DIRS += [
|
||||
"locales",
|
||||
"memory",
|
||||
"netmonitor",
|
||||
"performance",
|
||||
"performance-new",
|
||||
"preferences",
|
||||
"responsive",
|
||||
|
@ -1,274 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const STRINGS_URI = "devtools/client/locales/jit-optimizations.properties";
|
||||
const L10N = new LocalizationHelper(STRINGS_URI);
|
||||
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const {
|
||||
Component,
|
||||
createFactory,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const Tree = createFactory(
|
||||
require("devtools/client/shared/components/VirtualizedTree")
|
||||
);
|
||||
const OptimizationsItem = createFactory(
|
||||
require("devtools/client/performance/components/JITOptimizationsItem")
|
||||
);
|
||||
const FrameView = createFactory(
|
||||
require("devtools/client/shared/components/Frame")
|
||||
);
|
||||
const JIT_TITLE = L10N.getStr("jit.title");
|
||||
// If TREE_ROW_HEIGHT changes, be sure to change `var(--jit-tree-row-height)`
|
||||
// in `devtools/client/themes/jit-optimizations.css`
|
||||
const TREE_ROW_HEIGHT = 14;
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
/**
|
||||
* TODO - Re-enable this eslint rule. The JIT tool is a work in progress, and isn't fully
|
||||
* integrated as of yet, and this may represent intended functionality.
|
||||
*/
|
||||
const onClickTooltipString = frame =>
|
||||
L10N.getFormatStr(
|
||||
"viewsourceindebugger",
|
||||
`${frame.source}:${frame.line}:${frame.column}`
|
||||
);
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
const optimizationAttemptModel = {
|
||||
id: PropTypes.number.isRequired,
|
||||
strategy: PropTypes.string.isRequired,
|
||||
outcome: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
const optimizationObservedTypeModel = {
|
||||
keyedBy: PropTypes.string.isRequired,
|
||||
name: PropTypes.string,
|
||||
location: PropTypes.string,
|
||||
line: PropTypes.string,
|
||||
};
|
||||
|
||||
const optimizationIonTypeModel = {
|
||||
id: PropTypes.number.isRequired,
|
||||
typeset: PropTypes.arrayOf(optimizationObservedTypeModel),
|
||||
site: PropTypes.number.isRequired,
|
||||
mirType: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
const optimizationSiteModel = {
|
||||
id: PropTypes.number.isRequired,
|
||||
propertyName: PropTypes.string,
|
||||
line: PropTypes.number.isRequired,
|
||||
column: PropTypes.number.isRequired,
|
||||
data: PropTypes.shape({
|
||||
attempts: PropTypes.arrayOf(optimizationAttemptModel).isRequired,
|
||||
types: PropTypes.arrayOf(optimizationIonTypeModel).isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
class JITOptimizations extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
onViewSourceInDebugger: PropTypes.func.isRequired,
|
||||
frameData: PropTypes.object.isRequired,
|
||||
optimizationSites: PropTypes.arrayOf(optimizationSiteModel).isRequired,
|
||||
autoExpandDepth: PropTypes.number,
|
||||
};
|
||||
}
|
||||
|
||||
static get defaultProps() {
|
||||
return {
|
||||
autoExpandDepth: 0,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
expanded: new Set(),
|
||||
};
|
||||
|
||||
this._createHeader = this._createHeader.bind(this);
|
||||
this._createTree = this._createTree.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frame data generated from `frameNode.getInfo()`, or an empty
|
||||
* object, as well as a handler for clicking on the frame component.
|
||||
*
|
||||
* @param {?Object} .frameData
|
||||
* @param {Function} .onViewSourceInDebugger
|
||||
* @return {ReactElement}
|
||||
*/
|
||||
_createHeader({ frameData, onViewSourceInDebugger }) {
|
||||
const { isMetaCategory, url, line } = frameData;
|
||||
const name = isMetaCategory
|
||||
? frameData.categoryData.label
|
||||
: frameData.functionName || "";
|
||||
|
||||
// Neither Meta Category nodes, or the lack of a selected frame node,
|
||||
// renders out a frame source, like "file.js:123"; so just use
|
||||
// an empty span.
|
||||
let frameComponent;
|
||||
if (isMetaCategory || !name) {
|
||||
frameComponent = dom.span();
|
||||
} else {
|
||||
frameComponent = FrameView({
|
||||
frame: {
|
||||
source: url,
|
||||
line: +line,
|
||||
functionDisplayName: name,
|
||||
},
|
||||
onClick: onViewSourceInDebugger,
|
||||
});
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{ className: "optimization-header" },
|
||||
dom.span({ className: "header-title" }, JIT_TITLE),
|
||||
dom.span({ className: "header-function-name" }, name),
|
||||
frameComponent
|
||||
);
|
||||
}
|
||||
|
||||
_createTree(props) {
|
||||
const {
|
||||
autoExpandDepth,
|
||||
frameData,
|
||||
onViewSourceInDebugger,
|
||||
optimizationSites: sites,
|
||||
} = this.props;
|
||||
|
||||
const getSite = id => sites.find(site => site.id === id);
|
||||
const getIonTypeForObserved = type => {
|
||||
return getSite(type.id).data.types.find(iontype =>
|
||||
(iontype.typeset || []).includes(type)
|
||||
);
|
||||
};
|
||||
const isSite = site => getSite(site.id) === site;
|
||||
const isAttempts = attempts =>
|
||||
getSite(attempts.id).data.attempts === attempts;
|
||||
const isAttempt = attempt =>
|
||||
getSite(attempt.id).data.attempts.includes(attempt);
|
||||
const isTypes = types => getSite(types.id).data.types === types;
|
||||
const isType = type => getSite(type.id).data.types.includes(type);
|
||||
const isObservedType = type => getIonTypeForObserved(type);
|
||||
|
||||
const getRowType = node => {
|
||||
if (isSite(node)) {
|
||||
return "site";
|
||||
}
|
||||
if (isAttempts(node)) {
|
||||
return "attempts";
|
||||
}
|
||||
if (isTypes(node)) {
|
||||
return "types";
|
||||
}
|
||||
if (isAttempt(node)) {
|
||||
return "attempt";
|
||||
}
|
||||
if (isType(node)) {
|
||||
return "type";
|
||||
}
|
||||
if (isObservedType(node)) {
|
||||
return "observedtype";
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Creates a unique key for each node in the
|
||||
// optimizations data
|
||||
const getKey = node => {
|
||||
const site = getSite(node.id);
|
||||
if (isSite(node)) {
|
||||
return node.id;
|
||||
} else if (isAttempts(node)) {
|
||||
return `${node.id}-A`;
|
||||
} else if (isTypes(node)) {
|
||||
return `${node.id}-T`;
|
||||
} else if (isType(node)) {
|
||||
return `${node.id}-T-${site.data.types.indexOf(node)}`;
|
||||
} else if (isAttempt(node)) {
|
||||
return `${node.id}-A-${site.data.attempts.indexOf(node)}`;
|
||||
} else if (isObservedType(node)) {
|
||||
const iontype = getIonTypeForObserved(node);
|
||||
return `${getKey(iontype)}-O-${iontype.typeset.indexOf(node)}`;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
return Tree({
|
||||
autoExpandDepth,
|
||||
preventNavigationOnArrowRight: false,
|
||||
getParent: node => {
|
||||
const site = getSite(node.id);
|
||||
let parent;
|
||||
if (isAttempts(node) || isTypes(node)) {
|
||||
parent = site;
|
||||
} else if (isType(node)) {
|
||||
parent = site.data.types;
|
||||
} else if (isAttempt(node)) {
|
||||
parent = site.data.attempts;
|
||||
} else if (isObservedType(node)) {
|
||||
parent = getIonTypeForObserved(node);
|
||||
}
|
||||
assert(parent, "Could not find a parent for optimization data node");
|
||||
|
||||
return parent;
|
||||
},
|
||||
getChildren: node => {
|
||||
if (isSite(node)) {
|
||||
return [node.data.types, node.data.attempts];
|
||||
} else if (isAttempts(node) || isTypes(node)) {
|
||||
return node;
|
||||
} else if (isType(node)) {
|
||||
return node.typeset || [];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
isExpanded: node => this.state.expanded.has(node),
|
||||
onExpand: node =>
|
||||
this.setState(state => {
|
||||
const expanded = new Set(state.expanded);
|
||||
expanded.add(node);
|
||||
return { expanded };
|
||||
}),
|
||||
onCollapse: node =>
|
||||
this.setState(state => {
|
||||
const expanded = new Set(state.expanded);
|
||||
expanded.delete(node);
|
||||
return { expanded };
|
||||
}),
|
||||
onFocus: function() {},
|
||||
getKey,
|
||||
getRoots: () => sites || [],
|
||||
itemHeight: TREE_ROW_HEIGHT,
|
||||
renderItem: (item, depth, focused, arrow, expanded) =>
|
||||
new OptimizationsItem({
|
||||
onViewSourceInDebugger,
|
||||
item,
|
||||
depth,
|
||||
focused,
|
||||
arrow,
|
||||
expanded,
|
||||
type: getRowType(item),
|
||||
frameData,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const header = this._createHeader(this.props);
|
||||
const tree = this._createTree(this.props);
|
||||
|
||||
return dom.div({}, header, tree);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JITOptimizations;
|
@ -1,228 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const STRINGS_URI = "devtools/client/locales/jit-optimizations.properties";
|
||||
const L10N = new LocalizationHelper(STRINGS_URI);
|
||||
|
||||
const { PluralForm } = require("devtools/shared/plural-form");
|
||||
const {
|
||||
Component,
|
||||
createFactory,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const Frame = createFactory(require("devtools/client/shared/components/Frame"));
|
||||
const PROPNAME_MAX_LENGTH = 4;
|
||||
// If TREE_ROW_HEIGHT changes, be sure to change `var(--jit-tree-row-height)`
|
||||
// in `devtools/client/themes/jit-optimizations.css`
|
||||
const TREE_ROW_HEIGHT = 14;
|
||||
|
||||
const OPTIMIZATION_ITEM_TYPES = [
|
||||
"site",
|
||||
"attempts",
|
||||
"types",
|
||||
"attempt",
|
||||
"type",
|
||||
"observedtype",
|
||||
];
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
/**
|
||||
* TODO - Re-enable this eslint rule. The JIT tool is a work in progress, and isn't fully
|
||||
* integrated as of yet.
|
||||
*/
|
||||
const {
|
||||
JITOptimizations,
|
||||
hasSuccessfulOutcome,
|
||||
isSuccessfulOutcome,
|
||||
} = require("devtools/client/performance/modules/logic/jit");
|
||||
const OPTIMIZATION_FAILURE = L10N.getStr("jit.optimizationFailure");
|
||||
const JIT_SAMPLES = L10N.getStr("jit.samples");
|
||||
const JIT_TYPES = L10N.getStr("jit.types");
|
||||
const JIT_ATTEMPTS = L10N.getStr("jit.attempts");
|
||||
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
class JITOptimizationsItem extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
onViewSourceInDebugger: PropTypes.func.isRequired,
|
||||
frameData: PropTypes.object.isRequired,
|
||||
type: PropTypes.oneOf(OPTIMIZATION_ITEM_TYPES).isRequired,
|
||||
depth: PropTypes.number.isRequired,
|
||||
arrow: PropTypes.element.isRequired,
|
||||
item: PropTypes.object,
|
||||
focused: PropTypes.bool,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._renderSite = this._renderSite.bind(this);
|
||||
this._renderAttempts = this._renderAttempts.bind(this);
|
||||
this._renderTypes = this._renderTypes.bind(this);
|
||||
this._renderAttempt = this._renderAttempt.bind(this);
|
||||
this._renderType = this._renderType.bind(this);
|
||||
this._renderObservedType = this._renderObservedType.bind(this);
|
||||
}
|
||||
|
||||
_renderSite({ item: site, onViewSourceInDebugger, frameData }) {
|
||||
const attempts = site.data.attempts;
|
||||
const lastStrategy = attempts[attempts.length - 1].strategy;
|
||||
let propString = "";
|
||||
const propertyName = site.data.propertyName;
|
||||
|
||||
// Display property name if it exists
|
||||
if (propertyName) {
|
||||
if (propertyName.length > PROPNAME_MAX_LENGTH) {
|
||||
propString = ` (.${propertyName.substr(0, PROPNAME_MAX_LENGTH)}…)`;
|
||||
} else {
|
||||
propString = ` (.${propertyName})`;
|
||||
}
|
||||
}
|
||||
|
||||
const sampleString = PluralForm.get(site.samples, JIT_SAMPLES).replace(
|
||||
"#1",
|
||||
site.samples
|
||||
);
|
||||
const text = dom.span(
|
||||
{ className: "optimization-site-title" },
|
||||
`${lastStrategy}${propString} – (${sampleString})`
|
||||
);
|
||||
const frame = Frame({
|
||||
onClick: onViewSourceInDebugger,
|
||||
frame: {
|
||||
source: frameData.url,
|
||||
line: +site.data.line,
|
||||
column: site.data.column,
|
||||
},
|
||||
});
|
||||
const children = [text, frame];
|
||||
|
||||
if (!hasSuccessfulOutcome(site)) {
|
||||
children.unshift(dom.span({ className: "opt-icon warning" }));
|
||||
}
|
||||
|
||||
return dom.span({ className: "optimization-site" }, ...children);
|
||||
}
|
||||
|
||||
_renderAttempts({ item: attempts }) {
|
||||
return dom.span(
|
||||
{ className: "optimization-attempts" },
|
||||
`${JIT_ATTEMPTS} (${attempts.length})`
|
||||
);
|
||||
}
|
||||
|
||||
_renderTypes({ item: types }) {
|
||||
return dom.span(
|
||||
{ className: "optimization-types" },
|
||||
`${JIT_TYPES} (${types.length})`
|
||||
);
|
||||
}
|
||||
|
||||
_renderAttempt({ item: attempt }) {
|
||||
const success = isSuccessfulOutcome(attempt.outcome);
|
||||
const { strategy, outcome } = attempt;
|
||||
return dom.span(
|
||||
{ className: "optimization-attempt" },
|
||||
dom.span({ className: "optimization-strategy" }, strategy),
|
||||
" → ",
|
||||
dom.span(
|
||||
{
|
||||
className: `optimization-outcome ${success ? "success" : "failure"}`,
|
||||
},
|
||||
outcome
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_renderType({ item: type }) {
|
||||
return dom.span(
|
||||
{ className: "optimization-ion-type" },
|
||||
`${type.site}:${type.mirType}`
|
||||
);
|
||||
}
|
||||
|
||||
_renderObservedType({ onViewSourceInDebugger, item: type }) {
|
||||
const children = [
|
||||
dom.span(
|
||||
{ className: "optimization-observed-type-keyed" },
|
||||
`${type.keyedBy}${type.name ? ` → ${type.name}` : ""}`
|
||||
),
|
||||
];
|
||||
|
||||
// If we have a line and location, make a link to the debugger
|
||||
if (type.location && type.line) {
|
||||
children.push(
|
||||
Frame({
|
||||
onClick: onViewSourceInDebugger,
|
||||
frame: {
|
||||
source: type.location,
|
||||
line: type.line,
|
||||
column: type.column,
|
||||
},
|
||||
})
|
||||
);
|
||||
// Otherwise if we just have a location, it's probably just a memory location.
|
||||
} else if (type.location) {
|
||||
children.push(`@${type.location}`);
|
||||
}
|
||||
|
||||
return dom.span({ className: "optimization-observed-type" }, ...children);
|
||||
}
|
||||
|
||||
render() {
|
||||
/* eslint-disable no-unused-vars */
|
||||
/**
|
||||
* TODO - Re-enable this eslint rule. The JIT tool is a work in progress, and these
|
||||
* undefined variables may represent intended functionality.
|
||||
*/
|
||||
const {
|
||||
depth,
|
||||
arrow,
|
||||
type,
|
||||
// TODO - The following are currently unused.
|
||||
item,
|
||||
focused,
|
||||
frameData,
|
||||
onViewSourceInDebugger,
|
||||
} = this.props;
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
let content;
|
||||
switch (type) {
|
||||
case "site":
|
||||
content = this._renderSite(this.props);
|
||||
break;
|
||||
case "attempts":
|
||||
content = this._renderAttempts(this.props);
|
||||
break;
|
||||
case "types":
|
||||
content = this._renderTypes(this.props);
|
||||
break;
|
||||
case "attempt":
|
||||
content = this._renderAttempt(this.props);
|
||||
break;
|
||||
case "type":
|
||||
content = this._renderType(this.props);
|
||||
break;
|
||||
case "observedtype":
|
||||
content = this._renderObservedType(this.props);
|
||||
break;
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: `optimization-tree-item optimization-tree-item-${type}`,
|
||||
style: { marginInlineStart: depth * TREE_ROW_HEIGHT },
|
||||
},
|
||||
arrow,
|
||||
content
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JITOptimizationsItem;
|
@ -1,45 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { L10N } = require("devtools/client/performance/modules/global");
|
||||
const { Component } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { button } = dom;
|
||||
|
||||
class RecordingButton extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
onRecordButtonClick: PropTypes.func.isRequired,
|
||||
isRecording: PropTypes.bool,
|
||||
isLocked: PropTypes.bool,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onRecordButtonClick, isRecording, isLocked } = this.props;
|
||||
|
||||
const classList = ["devtools-button", "record-button"];
|
||||
|
||||
if (isRecording) {
|
||||
classList.push("checked");
|
||||
}
|
||||
|
||||
return button(
|
||||
{
|
||||
className: classList.join(" "),
|
||||
onClick: onRecordButtonClick,
|
||||
"data-standalone": "true",
|
||||
"data-text-only": "true",
|
||||
disabled: isLocked,
|
||||
},
|
||||
isRecording
|
||||
? L10N.getStr("recordings.stop")
|
||||
: L10N.getStr("recordings.start")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RecordingButton;
|
@ -1,63 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { L10N } = require("devtools/client/performance/modules/global");
|
||||
const { Component } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { div, button } = dom;
|
||||
|
||||
class RecordingControls extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
onClearButtonClick: PropTypes.func.isRequired,
|
||||
onRecordButtonClick: PropTypes.func.isRequired,
|
||||
onImportButtonClick: PropTypes.func.isRequired,
|
||||
isRecording: PropTypes.bool,
|
||||
isLocked: PropTypes.bool,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
onClearButtonClick,
|
||||
onRecordButtonClick,
|
||||
onImportButtonClick,
|
||||
isRecording,
|
||||
isLocked,
|
||||
} = this.props;
|
||||
|
||||
const recordButtonClassList = ["devtools-button", "record-button"];
|
||||
|
||||
if (isRecording) {
|
||||
recordButtonClassList.push("checked");
|
||||
}
|
||||
|
||||
return div(
|
||||
{ className: "devtools-toolbar" },
|
||||
button({
|
||||
id: "clear-button",
|
||||
className: "devtools-button",
|
||||
title: L10N.getStr("recordings.clear.tooltip"),
|
||||
onClick: onClearButtonClick,
|
||||
}),
|
||||
button({
|
||||
id: "main-record-button",
|
||||
className: recordButtonClassList.join(" "),
|
||||
disabled: isLocked,
|
||||
title: L10N.getStr("recordings.start.tooltip"),
|
||||
onClick: onRecordButtonClick,
|
||||
}),
|
||||
button({
|
||||
id: "import-button",
|
||||
className: "devtools-button",
|
||||
title: L10N.getStr("recordings.import.tooltip"),
|
||||
onClick: onImportButtonClick,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RecordingControls;
|
@ -1,32 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Component } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { L10N } = require("devtools/client/performance/modules/global");
|
||||
const { ul, div } = dom;
|
||||
|
||||
class RecordingList extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
itemComponent: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { items, itemComponent: Item } = this.props;
|
||||
|
||||
return items.length > 0
|
||||
? ul({ className: "recording-list" }, ...items.map(Item))
|
||||
: div(
|
||||
{ className: "recording-list-empty" },
|
||||
L10N.getStr("noRecordingsText")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RecordingList;
|
@ -1,65 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Component } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { div, li, span, button } = dom;
|
||||
const { L10N } = require("devtools/client/performance/modules/global");
|
||||
|
||||
class RecordingListItem extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
label: PropTypes.string.isRequired,
|
||||
duration: PropTypes.string,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
isSelected: PropTypes.bool,
|
||||
isRecording: PropTypes.bool,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
label,
|
||||
duration,
|
||||
onSelect,
|
||||
onSave,
|
||||
isLoading,
|
||||
isSelected,
|
||||
isRecording,
|
||||
} = this.props;
|
||||
|
||||
const className = `recording-list-item ${isSelected ? "selected" : ""}`;
|
||||
|
||||
let durationText;
|
||||
if (isLoading) {
|
||||
durationText = L10N.getStr("recordingsList.loadingLabel");
|
||||
} else if (isRecording) {
|
||||
durationText = L10N.getStr("recordingsList.recordingLabel");
|
||||
} else {
|
||||
durationText = L10N.getFormatStr(
|
||||
"recordingsList.durationLabel",
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
return li(
|
||||
{ className, onClick: onSelect },
|
||||
div({ className: "recording-list-item-label" }, label),
|
||||
div(
|
||||
{ className: "recording-list-item-footer" },
|
||||
span({ className: "recording-list-item-duration" }, durationText),
|
||||
button(
|
||||
{ className: "recording-list-item-save", onClick: onSave },
|
||||
L10N.getStr("recordingsList.saveLabel")
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RecordingListItem;
|
@ -1,42 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the "waterfall" view, essentially a detailed list
|
||||
* of all the markers in the timeline data.
|
||||
*/
|
||||
|
||||
const { createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const WaterfallHeader = createFactory(
|
||||
require("devtools/client/performance/components/WaterfallHeader")
|
||||
);
|
||||
const WaterfallTree = createFactory(
|
||||
require("devtools/client/performance/components/WaterfallTree")
|
||||
);
|
||||
|
||||
function Waterfall(props) {
|
||||
return dom.div(
|
||||
{ className: "waterfall-markers" },
|
||||
WaterfallHeader(props),
|
||||
WaterfallTree(props)
|
||||
);
|
||||
}
|
||||
|
||||
Waterfall.displayName = "Waterfall";
|
||||
|
||||
Waterfall.propTypes = {
|
||||
marker: PropTypes.object.isRequired,
|
||||
startTime: PropTypes.number.isRequired,
|
||||
endTime: PropTypes.number.isRequired,
|
||||
dataScale: PropTypes.number.isRequired,
|
||||
sidebarWidth: PropTypes.number.isRequired,
|
||||
waterfallWidth: PropTypes.number.isRequired,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
};
|
||||
|
||||
module.exports = Waterfall;
|
@ -1,73 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* The "waterfall ticks" view, a header for the markers displayed in the waterfall.
|
||||
*/
|
||||
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { L10N } = require("devtools/client/performance/modules/global");
|
||||
const {
|
||||
TickUtils,
|
||||
} = require("devtools/client/performance/modules/waterfall-ticks");
|
||||
|
||||
const WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_HEADER_TICKS_SPACING_MIN = 50; // px
|
||||
const WATERFALL_HEADER_TEXT_PADDING = 3; // px
|
||||
|
||||
function WaterfallHeader(props) {
|
||||
const { startTime, dataScale, sidebarWidth, waterfallWidth } = props;
|
||||
|
||||
const tickInterval = TickUtils.findOptimalTickInterval({
|
||||
ticksMultiple: WATERFALL_HEADER_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: WATERFALL_HEADER_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale,
|
||||
});
|
||||
|
||||
const ticks = [];
|
||||
for (let x = 0; x < waterfallWidth; x += tickInterval) {
|
||||
const left = x + WATERFALL_HEADER_TEXT_PADDING;
|
||||
const time = Math.round(x / dataScale + startTime);
|
||||
const label = L10N.getFormatStr("timeline.tick", time);
|
||||
|
||||
const node = dom.div(
|
||||
{
|
||||
key: x,
|
||||
className: "plain waterfall-header-tick",
|
||||
style: { transform: `translateX(${left}px)` },
|
||||
},
|
||||
label
|
||||
);
|
||||
ticks.push(node);
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{ className: "waterfall-header" },
|
||||
dom.div(
|
||||
{
|
||||
className: "waterfall-sidebar theme-sidebar waterfall-header-name",
|
||||
style: { width: sidebarWidth + "px" },
|
||||
},
|
||||
L10N.getStr("timeline.records")
|
||||
),
|
||||
dom.div(
|
||||
{ className: "waterfall-header-ticks waterfall-background-ticks" },
|
||||
ticks
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
WaterfallHeader.displayName = "WaterfallHeader";
|
||||
|
||||
WaterfallHeader.propTypes = {
|
||||
startTime: PropTypes.number.isRequired,
|
||||
dataScale: PropTypes.number.isRequired,
|
||||
sidebarWidth: PropTypes.number.isRequired,
|
||||
waterfallWidth: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
module.exports = WaterfallHeader;
|
@ -1,189 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
Component,
|
||||
createFactory,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const Tree = createFactory(
|
||||
require("devtools/client/shared/components/VirtualizedTree")
|
||||
);
|
||||
const WaterfallTreeRow = createFactory(
|
||||
require("devtools/client/performance/components/WaterfallTreeRow")
|
||||
);
|
||||
|
||||
// Keep in sync with var(--waterfall-tree-row-height) in performance.css
|
||||
const WATERFALL_TREE_ROW_HEIGHT = 15; // px
|
||||
|
||||
/**
|
||||
* Checks if a given marker is in the specified time range.
|
||||
*
|
||||
* @param object e
|
||||
* The marker containing the { start, end } timestamps.
|
||||
* @param number start
|
||||
* The earliest allowed time.
|
||||
* @param number end
|
||||
* The latest allowed time.
|
||||
* @return boolean
|
||||
* True if the marker fits inside the specified time range.
|
||||
*/
|
||||
function isMarkerInRange(e, start, end) {
|
||||
const mStart = e.start | 0;
|
||||
const mEnd = e.end | 0;
|
||||
|
||||
return (
|
||||
// bounds inside
|
||||
(mStart >= start && mEnd <= end) ||
|
||||
// bounds outside
|
||||
(mStart < start && mEnd > end) ||
|
||||
// overlap start
|
||||
(mStart < start && mEnd >= start && mEnd <= end) ||
|
||||
// overlap end
|
||||
(mEnd > end && mStart >= start && mStart <= end)
|
||||
);
|
||||
}
|
||||
|
||||
class WaterfallTree extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
marker: PropTypes.object.isRequired,
|
||||
startTime: PropTypes.number.isRequired,
|
||||
endTime: PropTypes.number.isRequired,
|
||||
dataScale: PropTypes.number.isRequired,
|
||||
sidebarWidth: PropTypes.number.isRequired,
|
||||
waterfallWidth: PropTypes.number.isRequired,
|
||||
onFocus: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
focused: null,
|
||||
expanded: new Set(),
|
||||
};
|
||||
|
||||
this._getRoots = this._getRoots.bind(this);
|
||||
this._getParent = this._getParent.bind(this);
|
||||
this._getChildren = this._getChildren.bind(this);
|
||||
this._getKey = this._getKey.bind(this);
|
||||
this._isExpanded = this._isExpanded.bind(this);
|
||||
this._onExpand = this._onExpand.bind(this);
|
||||
this._onCollapse = this._onCollapse.bind(this);
|
||||
this._onFocus = this._onFocus.bind(this);
|
||||
this._filter = this._filter.bind(this);
|
||||
this._renderItem = this._renderItem.bind(this);
|
||||
}
|
||||
|
||||
_getRoots(node) {
|
||||
const roots = this.props.marker.submarkers || [];
|
||||
return roots.filter(this._filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the parent node of 'node' with a depth-first search of the marker tree
|
||||
*/
|
||||
_getParent(node) {
|
||||
function findParent(marker) {
|
||||
if (marker.submarkers) {
|
||||
for (const submarker of marker.submarkers) {
|
||||
if (submarker === node) {
|
||||
return marker;
|
||||
}
|
||||
|
||||
const parent = findParent(submarker);
|
||||
if (parent) {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const rootMarker = this.props.marker;
|
||||
const parent = findParent(rootMarker);
|
||||
|
||||
// We are interested only in parent markers that are rendered,
|
||||
// which rootMarker is not. Return null if the parent is rootMarker.
|
||||
return parent !== rootMarker ? parent : null;
|
||||
}
|
||||
|
||||
_getChildren(node) {
|
||||
const submarkers = node.submarkers || [];
|
||||
return submarkers.filter(this._filter);
|
||||
}
|
||||
|
||||
_getKey(node) {
|
||||
return `marker-${node.index}`;
|
||||
}
|
||||
|
||||
_isExpanded(node) {
|
||||
return this.state.expanded.has(node);
|
||||
}
|
||||
|
||||
_onExpand(node) {
|
||||
this.setState(state => {
|
||||
const expanded = new Set(state.expanded);
|
||||
expanded.add(node);
|
||||
return { expanded };
|
||||
});
|
||||
}
|
||||
|
||||
_onCollapse(node) {
|
||||
this.setState(state => {
|
||||
const expanded = new Set(state.expanded);
|
||||
expanded.delete(node);
|
||||
return { expanded };
|
||||
});
|
||||
}
|
||||
|
||||
_onFocus(node) {
|
||||
this.setState({ focused: node });
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus(node);
|
||||
}
|
||||
}
|
||||
|
||||
_filter(node) {
|
||||
const { startTime, endTime } = this.props;
|
||||
return isMarkerInRange(node, startTime, endTime);
|
||||
}
|
||||
|
||||
_renderItem(marker, level, focused, arrow, expanded) {
|
||||
const { startTime, dataScale, sidebarWidth } = this.props;
|
||||
return WaterfallTreeRow({
|
||||
marker,
|
||||
level,
|
||||
arrow,
|
||||
expanded,
|
||||
focused,
|
||||
startTime,
|
||||
dataScale,
|
||||
sidebarWidth,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return Tree({
|
||||
preventNavigationOnArrowRight: false,
|
||||
getRoots: this._getRoots,
|
||||
getParent: this._getParent,
|
||||
getChildren: this._getChildren,
|
||||
getKey: this._getKey,
|
||||
isExpanded: this._isExpanded,
|
||||
onExpand: this._onExpand,
|
||||
onCollapse: this._onCollapse,
|
||||
onFocus: this._onFocus,
|
||||
renderItem: this._renderItem,
|
||||
focused: this.state.focused,
|
||||
itemHeight: WATERFALL_TREE_ROW_HEIGHT,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WaterfallTree;
|
@ -1,116 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A single row (node) in the waterfall tree
|
||||
*/
|
||||
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const {
|
||||
MarkerBlueprintUtils,
|
||||
} = require("devtools/client/performance/modules/marker-blueprint-utils");
|
||||
|
||||
const LEVEL_INDENT = 10; // px
|
||||
const ARROW_NODE_OFFSET = -14; // px
|
||||
const WATERFALL_MARKER_TIMEBAR_WIDTH_MIN = 5; // px
|
||||
|
||||
function buildMarkerSidebar(blueprint, props) {
|
||||
const { marker, level, sidebarWidth } = props;
|
||||
|
||||
const bullet = dom.div({
|
||||
className: `waterfall-marker-bullet marker-color-${blueprint.colorName}`,
|
||||
style: { transform: `translateX(${level * LEVEL_INDENT}px)` },
|
||||
"data-type": marker.name,
|
||||
});
|
||||
|
||||
const label = MarkerBlueprintUtils.getMarkerLabel(marker);
|
||||
|
||||
const name = dom.div(
|
||||
{
|
||||
className: "plain waterfall-marker-name",
|
||||
style: { transform: `translateX(${level * LEVEL_INDENT}px)` },
|
||||
title: label,
|
||||
},
|
||||
label
|
||||
);
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "waterfall-sidebar theme-sidebar",
|
||||
style: { width: sidebarWidth + "px" },
|
||||
},
|
||||
bullet,
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
function buildMarkerTimebar(blueprint, props) {
|
||||
const { marker, startTime, dataScale, arrow } = props;
|
||||
const offset = (marker.start - startTime) * dataScale + ARROW_NODE_OFFSET;
|
||||
const width = Math.max(
|
||||
(marker.end - marker.start) * dataScale,
|
||||
WATERFALL_MARKER_TIMEBAR_WIDTH_MIN
|
||||
);
|
||||
|
||||
const bar = dom.div(
|
||||
{
|
||||
className: "waterfall-marker-wrap",
|
||||
style: { transform: `translateX(${offset}px)` },
|
||||
},
|
||||
arrow,
|
||||
dom.div({
|
||||
className: `waterfall-marker-bar marker-color-${blueprint.colorName}`,
|
||||
style: { width: `${width}px` },
|
||||
"data-type": marker.name,
|
||||
})
|
||||
);
|
||||
|
||||
return dom.div(
|
||||
{ className: "waterfall-marker waterfall-background-ticks" },
|
||||
bar
|
||||
);
|
||||
}
|
||||
|
||||
function WaterfallTreeRow(props) {
|
||||
const { marker, focused } = props;
|
||||
const blueprint = MarkerBlueprintUtils.getBlueprintFor(marker);
|
||||
|
||||
const attrs = {
|
||||
className: "waterfall-tree-item" + (focused ? " focused" : ""),
|
||||
"data-otmt": marker.isOffMainThread,
|
||||
};
|
||||
|
||||
// Don't render an expando-arrow for leaf nodes.
|
||||
const submarkers = marker.submarkers;
|
||||
const hasDescendants = submarkers && submarkers.length > 0;
|
||||
if (hasDescendants) {
|
||||
attrs["data-expandable"] = "";
|
||||
} else {
|
||||
attrs["data-invisible"] = "";
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
attrs,
|
||||
buildMarkerSidebar(blueprint, props),
|
||||
buildMarkerTimebar(blueprint, props)
|
||||
);
|
||||
}
|
||||
|
||||
WaterfallTreeRow.displayName = "WaterfallTreeRow";
|
||||
|
||||
WaterfallTreeRow.propTypes = {
|
||||
marker: PropTypes.object.isRequired,
|
||||
level: PropTypes.number.isRequired,
|
||||
arrow: PropTypes.element.isRequired,
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
focused: PropTypes.bool.isRequired,
|
||||
startTime: PropTypes.number.isRequired,
|
||||
dataScale: PropTypes.number.isRequired,
|
||||
sidebarWidth: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
module.exports = WaterfallTreeRow;
|
@ -1,5 +0,0 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
head.js
|
||||
|
||||
[test_jit_optimizations_01.html]
|
@ -1,246 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
yield new Promise(function(){});
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/* global window, document, SimpleTest, requestAnimationFrame, is, ok */
|
||||
/* exported Cc, Ci, Cu, Cr, Assert, Task, Toolbox, browserRequire,
|
||||
forceRender, setProps, dumpn, checkOptimizationHeader, checkOptimizationTree */
|
||||
const { require } = ChromeUtils.import(
|
||||
"resource://devtools/shared/loader/Loader.jsm"
|
||||
);
|
||||
let { Assert } = require("resource://testing-common/Assert.jsm");
|
||||
const { BrowserLoader } = ChromeUtils.import(
|
||||
"resource://devtools/shared/loader/browser-loader.js"
|
||||
);
|
||||
const flags = require("devtools/shared/flags");
|
||||
let { Toolbox } = require("devtools/client/framework/toolbox");
|
||||
|
||||
let { require: browserRequire } = BrowserLoader({
|
||||
baseURI: "resource://devtools/client/performance/",
|
||||
window,
|
||||
});
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
const $ = (selector, scope = document) => scope.querySelector(selector);
|
||||
const $$ = (selector, scope = document) => scope.querySelectorAll(selector);
|
||||
|
||||
function forceRender(comp) {
|
||||
return setState(comp, {}).then(() => setState(comp, {}));
|
||||
}
|
||||
|
||||
// All tests are asynchronous.
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function onNextAnimationFrame(fn) {
|
||||
return () => requestAnimationFrame(() => requestAnimationFrame(fn));
|
||||
}
|
||||
|
||||
function setState(component, newState) {
|
||||
return new Promise(resolve => {
|
||||
component.setState(newState, onNextAnimationFrame(resolve));
|
||||
});
|
||||
}
|
||||
|
||||
function setProps(component, newState) {
|
||||
return new Promise(resolve => {
|
||||
component.setProps(newState, onNextAnimationFrame(resolve));
|
||||
});
|
||||
}
|
||||
|
||||
function dumpn(msg) {
|
||||
dump(`PERFORMANCE-COMPONENT-TEST: ${msg}\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default opts data for testing. First site has a simple IonType,
|
||||
* and an IonType with an ObservedType, and a successful outcome.
|
||||
* Second site does not have a successful outcome.
|
||||
*/
|
||||
const OPTS_DATA_GENERAL = [
|
||||
{
|
||||
id: 1,
|
||||
propertyName: "my property name",
|
||||
line: 100,
|
||||
column: 200,
|
||||
samples: 90,
|
||||
data: {
|
||||
attempts: [
|
||||
{
|
||||
id: 1,
|
||||
strategy: "GetElem_TypedObject",
|
||||
outcome: "AccessNotTypedObject",
|
||||
},
|
||||
{ id: 1, strategy: "GetElem_Dense", outcome: "AccessNotDense" },
|
||||
{ id: 1, strategy: "GetElem_TypedStatic", outcome: "Disabled" },
|
||||
{ id: 1, strategy: "GetElem_TypedArray", outcome: "GenericSuccess" },
|
||||
],
|
||||
types: [
|
||||
{
|
||||
id: 1,
|
||||
site: "Receiver",
|
||||
mirType: "Object",
|
||||
typeset: [
|
||||
{
|
||||
id: 1,
|
||||
keyedBy: "constructor",
|
||||
name: "MyView",
|
||||
location: "http://internet.com/file.js",
|
||||
line: "123",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
typeset: void 0,
|
||||
site: "Index",
|
||||
mirType: "Int32",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
propertyName: void 0,
|
||||
line: 50,
|
||||
column: 51,
|
||||
samples: 100,
|
||||
data: {
|
||||
attempts: [
|
||||
{ id: 2, strategy: "Call_Inline", outcome: "CantInlineBigData" },
|
||||
],
|
||||
types: [
|
||||
{
|
||||
id: 2,
|
||||
site: "Call_Target",
|
||||
mirType: "Object",
|
||||
typeset: [
|
||||
{ id: 2, keyedBy: "primitive" },
|
||||
{
|
||||
id: 2,
|
||||
keyedBy: "constructor",
|
||||
name: "B",
|
||||
location: "http://mypage.com/file.js",
|
||||
line: "2",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
keyedBy: "constructor",
|
||||
name: "C",
|
||||
location: "http://mypage.com/file.js",
|
||||
line: "3",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
keyedBy: "constructor",
|
||||
name: "D",
|
||||
location: "http://mypage.com/file.js",
|
||||
line: "4",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
OPTS_DATA_GENERAL.forEach(site => {
|
||||
site.data.types.forEach(type => {
|
||||
if (type.typeset) {
|
||||
type.typeset.id = site.id;
|
||||
}
|
||||
});
|
||||
site.data.attempts.id = site.id;
|
||||
site.data.types.id = site.id;
|
||||
});
|
||||
|
||||
function checkOptimizationHeader(name, file, line) {
|
||||
is(
|
||||
$(".optimization-header .header-function-name").textContent,
|
||||
name,
|
||||
"correct optimization header function name"
|
||||
);
|
||||
is(
|
||||
$(".optimization-header .frame-link-filename").textContent,
|
||||
file,
|
||||
"correct optimization header file name"
|
||||
);
|
||||
is(
|
||||
$(".optimization-header .frame-link-line").textContent,
|
||||
`:${line}`,
|
||||
"correct optimization header line"
|
||||
);
|
||||
}
|
||||
|
||||
function checkOptimizationTree(rowData) {
|
||||
const rows = $$(".tree .tree-node");
|
||||
|
||||
for (let i = 0; i < rowData.length; i++) {
|
||||
const row = rows[i];
|
||||
const expected = rowData[i];
|
||||
|
||||
switch (expected.type) {
|
||||
case "site":
|
||||
is(
|
||||
$(".optimization-site-title", row).textContent,
|
||||
`${expected.strategy} – (${expected.samples} samples)`,
|
||||
`row ${i}th: correct optimization site row`
|
||||
);
|
||||
|
||||
is(
|
||||
!!$(".opt-icon.warning", row),
|
||||
!!expected.failureIcon,
|
||||
`row ${i}th: expected visibility of failure icon for unsuccessful outcomes`
|
||||
);
|
||||
break;
|
||||
case "types":
|
||||
is(
|
||||
$(".optimization-types", row).textContent,
|
||||
`Types (${expected.count})`,
|
||||
`row ${i}th: correct types row`
|
||||
);
|
||||
break;
|
||||
case "attempts":
|
||||
is(
|
||||
$(".optimization-attempts", row).textContent,
|
||||
`Attempts (${expected.count})`,
|
||||
`row ${i}th: correct attempts row`
|
||||
);
|
||||
break;
|
||||
case "type":
|
||||
is(
|
||||
$(".optimization-ion-type", row).textContent,
|
||||
`${expected.site}:${expected.mirType}`,
|
||||
`row ${i}th: correct ion type row`
|
||||
);
|
||||
break;
|
||||
case "observedtype":
|
||||
is(
|
||||
$(".optimization-observed-type-keyed", row).textContent,
|
||||
expected.name
|
||||
? `${expected.keyedBy} → ${expected.name}`
|
||||
: expected.keyedBy,
|
||||
`row ${i}th: correct observed type row`
|
||||
);
|
||||
break;
|
||||
case "attempt":
|
||||
is(
|
||||
$(".optimization-strategy", row).textContent,
|
||||
expected.strategy,
|
||||
`row ${i}th: correct attempt row, attempt item`
|
||||
);
|
||||
is(
|
||||
$(".optimization-outcome", row).textContent,
|
||||
expected.outcome,
|
||||
`row ${i}th: correct attempt row, outcome item`
|
||||
);
|
||||
ok(
|
||||
$(".optimization-outcome", row).classList.contains(
|
||||
expected.success ? "success" : "failure"
|
||||
),
|
||||
`row ${i}th: correct attempt row, failure/success status`
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test the rendering of the JIT Optimizations tree. Tests when jit data has observed types, multiple observed types, multiple sites, a site with a successful strategy, site with no successful strategy.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JITOptimizations component test</title>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body style="height: 10000px;">
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript"></script>
|
||||
<script type="application/javascript">
|
||||
"use strict";
|
||||
|
||||
window.onload = async function () {
|
||||
try {
|
||||
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
|
||||
const React = browserRequire("devtools/client/shared/vendor/react");
|
||||
const JITOptimizations = React.createFactory(browserRequire("devtools/client/performance/components/JITOptimizations"));
|
||||
ok(JITOptimizations, "Should get JITOptimizations");
|
||||
const opts = ReactDOM.render(JITOptimizations({
|
||||
onViewSourceInDebugger: function(){},
|
||||
frameData: {
|
||||
isMetaCategory: false,
|
||||
url: "http://internet.com/file.js",
|
||||
line: 1,
|
||||
functionName: "myfunc",
|
||||
},
|
||||
optimizationSites: OPTS_DATA_GENERAL,
|
||||
autoExpandDepth: 1000,
|
||||
}), window.document.body);
|
||||
await forceRender(opts);
|
||||
|
||||
checkOptimizationHeader("myfunc", "file.js", "1");
|
||||
|
||||
checkOptimizationTree([
|
||||
{ type: "site", strategy: "GetElem_TypedArray", samples: "90" },
|
||||
{ type: "types", count: "2" },
|
||||
{ type: "type", site: "Receiver", mirType: "Object" },
|
||||
{ type: "observedtype", keyedBy: "constructor", name: "MyView" },
|
||||
{ type: "type", site: "Index", mirType: "Int32" },
|
||||
{ type: "attempts", count: "4" },
|
||||
{ type: "attempt", strategy: "GetElem_TypedObject", outcome: "AccessNotTypedObject" },
|
||||
{ type: "attempt", strategy: "GetElem_Dense", outcome: "AccessNotDense" },
|
||||
{ type: "attempt", strategy: "GetElem_TypedStatic", outcome: "Disabled" },
|
||||
{ type: "attempt", strategy: "GetElem_TypedArray", outcome: "GenericSuccess", success: true },
|
||||
{ type: "site", strategy: "Call_Inline", samples: "100", failureIcon: true },
|
||||
{ type: "types", count: "1" },
|
||||
{ type: "type", site: "Call_Target", mirType: "Object" },
|
||||
{ type: "observedtype", keyedBy: "primitive" },
|
||||
{ type: "observedtype", keyedBy: "constructor", name: "B" },
|
||||
{ type: "observedtype", keyedBy: "constructor", name: "C" },
|
||||
{ type: "observedtype", keyedBy: "constructor", name: "D" },
|
||||
{ type: "attempts", count: "1" },
|
||||
{ type: "attempt", strategy: "Call_Inline", outcome: "CantInlineBigData" },
|
||||
]);
|
||||
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -1,19 +0,0 @@
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
"JITOptimizations.js",
|
||||
"JITOptimizationsItem.js",
|
||||
"RecordingButton.js",
|
||||
"RecordingControls.js",
|
||||
"RecordingList.js",
|
||||
"RecordingListItem.js",
|
||||
"Waterfall.js",
|
||||
"WaterfallHeader.js",
|
||||
"WaterfallTree.js",
|
||||
"WaterfallTreeRow.js",
|
||||
)
|
||||
|
||||
MOCHITEST_CHROME_MANIFESTS += ["chrome/chrome.ini"]
|
@ -1,171 +0,0 @@
|
||||
# Timeline Markers
|
||||
|
||||
## Common
|
||||
|
||||
* DOMHighResTimeStamp start
|
||||
* DOMHighResTimeStamp end
|
||||
* DOMString name
|
||||
* object? stack
|
||||
* object? endStack
|
||||
* unsigned short processType;
|
||||
* boolean isOffMainThread;
|
||||
|
||||
The `processType` a GeckoProcessType enum listed in xpcom/build/nsXULAppAPI.h,
|
||||
specifying if this marker originates in a content, chrome, plugin etc. process.
|
||||
Additionally, markers may be created from any thread on those processes, and
|
||||
`isOffMainThread` highights whether or not they're from the main thread. The most
|
||||
common type of marker is probably going to be from a GeckoProcessType_Content's
|
||||
main thread when debugging content.
|
||||
|
||||
## DOMEvent
|
||||
|
||||
Triggered when a DOM event occurs, like a click or a keypress.
|
||||
|
||||
* unsigned short eventPhase - a number indicating what phase this event is
|
||||
in (target, bubbling, capturing, maps to Event constants)
|
||||
* DOMString type - the type of event, like "keypress" or "click"
|
||||
|
||||
## Reflow
|
||||
|
||||
Reflow markers (labeled as "Layout") indicate when a change has occurred to
|
||||
a DOM element's positioning that requires the frame tree (rendering
|
||||
representation of the DOM) to figure out the new position of a handful of
|
||||
elements. Fired via `PresShell::DoReflow`
|
||||
|
||||
## Paint
|
||||
|
||||
* sequence<{ long height, long width, long x, long y }> rectangles - An array
|
||||
of rectangle objects indicating where painting has occurred.
|
||||
|
||||
## Styles
|
||||
|
||||
Style markers (labeled as "Recalculating Styles") are triggered when Gecko
|
||||
needs to figure out the computational style of an element. Fired via
|
||||
`RestyleTracker::DoProcessRestyles` when there are elements to restyle.
|
||||
|
||||
## Javascript
|
||||
|
||||
`Javascript` markers are emitted indicating when JS execution begins and ends,
|
||||
with a reason that triggered it (causeName), like a requestAnimationFrame or
|
||||
a setTimeout.
|
||||
|
||||
* string causeName - The reason that JS was entered. There are many possible
|
||||
reasons, and the interesting ones to show web developers (triggered by content) are:
|
||||
|
||||
* "\<script\> element"
|
||||
* "EventListener.handleEvent"
|
||||
* "setInterval handler"
|
||||
* "setTimeout handler"
|
||||
* "FrameRequestCallback"
|
||||
* "EventHandlerNonNull"
|
||||
* "promise callback"
|
||||
* "promise initializer"
|
||||
* "Worker runnable"
|
||||
|
||||
There are also many more potential JS causes, some which are just internally
|
||||
used and won't emit a marker, but the below ones are only of interest to
|
||||
Gecko hackers, most likely
|
||||
|
||||
* "promise thenable"
|
||||
* "worker runnable"
|
||||
* "nsHTTPIndex set HTTPIndex property"
|
||||
* "XPCWrappedJS method call"
|
||||
* "nsHTTPIndex OnFTPControlLog"
|
||||
* "XPCWrappedJS QueryInterface"
|
||||
* "xpcshell argument processing”
|
||||
* "XPConnect sandbox evaluation "
|
||||
* "component loader report global"
|
||||
* "component loader load module"
|
||||
* "Cross-Process Object Wrapper call/construct"
|
||||
* "Cross-Process Object Wrapper ’set'"
|
||||
* "Cross-Process Object Wrapper 'get'"
|
||||
* "nsXULTemplateBuilder creation"
|
||||
* "TestShellCommand"
|
||||
* "precompiled XUL \<script\> element"
|
||||
* "XBL \<field\> initialization "
|
||||
* "NPAPI NPN_evaluate"
|
||||
* "NPAPI get"
|
||||
* "NPAPI set"
|
||||
* "NPAPI doInvoke"
|
||||
* "javascript: URI"
|
||||
* "geolocation.always_precise indexing"
|
||||
* "geolocation.app_settings enumeration"
|
||||
* "WebIDL dictionary creation"
|
||||
* "XBL \<constructor\>/\<destructor\> invocation"
|
||||
* "message manager script load"
|
||||
* "message handler script load"
|
||||
* "nsGlobalWindow report new global"
|
||||
|
||||
## GarbageCollection
|
||||
|
||||
Emitted after a full GC cycle has completed (which is after any number of
|
||||
incremental slices).
|
||||
|
||||
* DOMString causeName - The reason for a GC event to occur. A full list of
|
||||
GC reasons can be found in [docs](https://firefox-source-docs.mozilla.org/devtools-user/debugger-api/debugger.memory/).
|
||||
* DOMString nonincremenetalReason - If the GC could not do an incremental
|
||||
GC (smaller, quick GC events), and we have to walk the entire heap and
|
||||
GC everything marked, then the reason listed here is why.
|
||||
|
||||
## nsCycleCollector::Collect
|
||||
|
||||
An `nsCycleCollector::Collect` marker is emitted for each incremental cycle
|
||||
collection slice and each non-incremental cycle collection.
|
||||
|
||||
# nsCycleCollector::ForgetSkippable
|
||||
|
||||
`nsCycleCollector::ForgetSkippable` is presented as "Cycle Collection", but in
|
||||
reality it is preparation/pre-optimization for cycle collection, and not the
|
||||
actual tracing of edges and collecting of cycles.
|
||||
|
||||
## ConsoleTime
|
||||
|
||||
A marker generated via `console.time()` and `console.timeEnd()`.
|
||||
|
||||
* DOMString causeName - the label passed into `console.time(label)` and
|
||||
`console.timeEnd(label)` if passed in.
|
||||
|
||||
## TimeStamp
|
||||
|
||||
A marker generated via `console.timeStamp(label)`.
|
||||
|
||||
* DOMString causeName - the label passed into `console.timeStamp(label)`
|
||||
if passed in.
|
||||
|
||||
## document::DOMContentLoaded
|
||||
|
||||
A marker generated when the DOMContentLoaded event is fired.
|
||||
|
||||
## document::Load
|
||||
|
||||
A marker generated when the document's "load" event is fired.
|
||||
|
||||
## Parse HTML
|
||||
|
||||
## Parse XML
|
||||
|
||||
## Worker
|
||||
|
||||
Emitted whenever there's an operation dealing with Workers (any kind of worker,
|
||||
Web Workers, Service Workers etc.). Currently there are 4 types of operations
|
||||
being tracked: serializing/deserializing data on the main thread, and also
|
||||
serializing/deserializing data off the main thread.
|
||||
|
||||
* ProfileTimelineWorkerOperationType operationType - the type of operation
|
||||
being done by the Worker or the main thread when dealing with workers.
|
||||
Can be one of the following enums defined in ProfileTimelineMarker.webidl
|
||||
* "serializeDataOffMainThread"
|
||||
* "serializeDataOnMainThread"
|
||||
* "deserializeDataOffMainThread"
|
||||
* "deserializeDataOnMainThread"
|
||||
|
||||
## Composite
|
||||
|
||||
Composite markers trace the actual time an inner composite operation
|
||||
took on the compositor thread. Currently, these markers are only especially
|
||||
interesting for Gecko platform developers, and thus disabled by default.
|
||||
|
||||
## CompositeForwardTransaction
|
||||
|
||||
Markers generated when the IPC request was made to the compositor from
|
||||
the child process's main thread.
|
@ -1,110 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const ControllerEvents = {
|
||||
// Fired when a performance pref changes (either because the user changed it
|
||||
// via the tool's UI, by changing something about:config or programatically).
|
||||
PREF_CHANGED: "Performance:PrefChanged",
|
||||
|
||||
// Fired when the devtools theme changes.
|
||||
THEME_CHANGED: "Performance:ThemeChanged",
|
||||
|
||||
// When a new recording model is received by the controller.
|
||||
RECORDING_ADDED: "Performance:RecordingAdded",
|
||||
|
||||
// When a recording model gets removed from the controller.
|
||||
RECORDING_DELETED: "Performance:RecordingDeleted",
|
||||
|
||||
// When a recording model becomes "started", "stopping" or "stopped".
|
||||
RECORDING_STATE_CHANGE: "Performance:RecordingStateChange",
|
||||
|
||||
// When a recording is offering information on the profiler's circular buffer.
|
||||
RECORDING_PROFILER_STATUS_UPDATE: "Performance:RecordingProfilerStatusUpdate",
|
||||
|
||||
// When a recording model becomes marked as selected.
|
||||
RECORDING_SELECTED: "Performance:RecordingSelected",
|
||||
|
||||
// When starting a recording is attempted and fails because the backend
|
||||
// does not permit it at this time.
|
||||
BACKEND_FAILED_AFTER_RECORDING_START:
|
||||
"Performance:BackendFailedRecordingStart",
|
||||
|
||||
// When a recording is started and the backend has started working.
|
||||
BACKEND_READY_AFTER_RECORDING_START: "Performance:BackendReadyRecordingStart",
|
||||
|
||||
// When a recording is stopped and the backend has finished cleaning up.
|
||||
BACKEND_READY_AFTER_RECORDING_STOP: "Performance:BackendReadyRecordingStop",
|
||||
|
||||
// When a recording is exported.
|
||||
RECORDING_EXPORTED: "Performance:RecordingExported",
|
||||
|
||||
// When a recording is imported.
|
||||
RECORDING_IMPORTED: "Performance:RecordingImported",
|
||||
|
||||
// When a source is shown in the JavaScript Debugger at a specific location.
|
||||
SOURCE_SHOWN_IN_JS_DEBUGGER: "Performance:UI:SourceShownInJsDebugger",
|
||||
SOURCE_NOT_FOUND_IN_JS_DEBUGGER: "Performance:UI:SourceNotFoundInJsDebugger",
|
||||
|
||||
// Fired by the PerformanceController when `populateWithRecordings` is finished.
|
||||
RECORDINGS_SEEDED: "Performance:RecordingsSeeded",
|
||||
};
|
||||
|
||||
const ViewEvents = {
|
||||
// Emitted by the `ToolbarView` when a preference changes.
|
||||
UI_PREF_CHANGED: "Performance:UI:PrefChanged",
|
||||
|
||||
// When the state (display mode) changes, for example when switching between
|
||||
// "empty", "recording" or "recorded". This causes certain parts of the UI
|
||||
// to be hidden or visible.
|
||||
UI_STATE_CHANGED: "Performance:UI:StateChanged",
|
||||
|
||||
// Emitted by the `PerformanceView` on clear button click.
|
||||
UI_CLEAR_RECORDINGS: "Performance:UI:ClearRecordings",
|
||||
|
||||
// Emitted by the `PerformanceView` on record button click.
|
||||
UI_START_RECORDING: "Performance:UI:StartRecording",
|
||||
UI_STOP_RECORDING: "Performance:UI:StopRecording",
|
||||
|
||||
// Emitted by the `PerformanceView` on import/export button click.
|
||||
UI_IMPORT_RECORDING: "Performance:UI:ImportRecording",
|
||||
UI_EXPORT_RECORDING: "Performance:UI:ExportRecording",
|
||||
|
||||
// Emitted by the `PerformanceView` when the profiler's circular buffer
|
||||
// status has been rendered.
|
||||
UI_RECORDING_PROFILER_STATUS_RENDERED:
|
||||
"Performance:UI:RecordingProfilerStatusRendered",
|
||||
|
||||
// When a recording is selected in the UI.
|
||||
UI_RECORDING_SELECTED: "Performance:UI:RecordingSelected",
|
||||
|
||||
// Emitted by the `DetailsView` when a subview is selected
|
||||
UI_DETAILS_VIEW_SELECTED: "Performance:UI:DetailsViewSelected",
|
||||
|
||||
// Emitted by the `OverviewView` after something has been rendered.
|
||||
UI_OVERVIEW_RENDERED: "Performance:UI:OverviewRendered",
|
||||
UI_MARKERS_GRAPH_RENDERED: "Performance:UI:OverviewMarkersRendered",
|
||||
UI_MEMORY_GRAPH_RENDERED: "Performance:UI:OverviewMemoryRendered",
|
||||
UI_FRAMERATE_GRAPH_RENDERED: "Performance:UI:OverviewFramerateRendered",
|
||||
|
||||
// Emitted by the `OverviewView` when a range has been selected in the graphs.
|
||||
UI_OVERVIEW_RANGE_SELECTED: "Performance:UI:OverviewRangeSelected",
|
||||
|
||||
// Emitted by the `WaterfallView` when it has been rendered.
|
||||
UI_WATERFALL_RENDERED: "Performance:UI:WaterfallRendered",
|
||||
|
||||
// Emitted by the `JsCallTreeView` when it has been rendered.
|
||||
UI_JS_CALL_TREE_RENDERED: "Performance:UI:JsCallTreeRendered",
|
||||
|
||||
// Emitted by the `JsFlameGraphView` when it has been rendered.
|
||||
UI_JS_FLAMEGRAPH_RENDERED: "Performance:UI:JsFlameGraphRendered",
|
||||
|
||||
// Emitted by the `MemoryCallTreeView` when it has been rendered.
|
||||
UI_MEMORY_CALL_TREE_RENDERED: "Performance:UI:MemoryCallTreeRendered",
|
||||
|
||||
// Emitted by the `MemoryFlameGraphView` when it has been rendered.
|
||||
UI_MEMORY_FLAMEGRAPH_RENDERED: "Performance:UI:MemoryFlameGraphRendered",
|
||||
};
|
||||
|
||||
module.exports = Object.assign({}, ControllerEvents, ViewEvents);
|
@ -1,354 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/content/shared/toolbarbutton.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/performance.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/jit-optimizations.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/components-frame.css" type="text/css"?>
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % performanceDTD SYSTEM "chrome://devtools/locale/performance.dtd">
|
||||
%performanceDTD;
|
||||
]>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
<script src="chrome://devtools/content/shared/theme-switching.js"/>
|
||||
<script src="resource://devtools/client/performance/initializer.js"/>
|
||||
|
||||
<popupset id="performance-options-popupset">
|
||||
<menupopup id="performance-filter-menupopup" position="before_start"/>
|
||||
<menupopup id="performance-options-menupopup" position="before_end">
|
||||
<menuitem id="option-show-platform-data"
|
||||
type="checkbox"
|
||||
data-pref="show-platform-data"
|
||||
label="&performanceUI.showPlatformData;"
|
||||
tooltiptext="&performanceUI.showPlatformData.tooltiptext;"/>
|
||||
<menuitem id="option-show-jit-optimizations"
|
||||
class="experimental-option"
|
||||
type="checkbox"
|
||||
data-pref="show-jit-optimizations"
|
||||
label="&performanceUI.showJITOptimizations;"
|
||||
tooltiptext="&performanceUI.showJITOptimizations.tooltiptext;"/>
|
||||
<menuitem id="option-enable-memory"
|
||||
class="experimental-option"
|
||||
type="checkbox"
|
||||
data-pref="enable-memory"
|
||||
label="&performanceUI.enableMemory;"
|
||||
tooltiptext="&performanceUI.enableMemory.tooltiptext;"/>
|
||||
<menuitem id="option-enable-allocations"
|
||||
type="checkbox"
|
||||
data-pref="enable-allocations"
|
||||
label="&performanceUI.enableAllocations;"
|
||||
tooltiptext="&performanceUI.enableAllocations.tooltiptext;"/>
|
||||
<menuitem id="option-enable-framerate"
|
||||
type="checkbox"
|
||||
data-pref="enable-framerate"
|
||||
label="&performanceUI.enableFramerate;"
|
||||
tooltiptext="&performanceUI.enableFramerate.tooltiptext;"/>
|
||||
<menuitem id="option-invert-call-tree"
|
||||
type="checkbox"
|
||||
data-pref="invert-call-tree"
|
||||
label="&performanceUI.invertTree;"
|
||||
tooltiptext="&performanceUI.invertTree.tooltiptext;"/>
|
||||
<menuitem id="option-invert-flame-graph"
|
||||
type="checkbox"
|
||||
data-pref="invert-flame-graph"
|
||||
label="&performanceUI.invertFlameGraph;"
|
||||
tooltiptext="&performanceUI.invertFlameGraph.tooltiptext;"/>
|
||||
<menuitem id="option-flatten-tree-recursion"
|
||||
type="checkbox"
|
||||
data-pref="flatten-tree-recursion"
|
||||
label="&performanceUI.flattenTreeRecursion;"
|
||||
tooltiptext="&performanceUI.flattenTreeRecursion.tooltiptext;"/>
|
||||
</menupopup>
|
||||
</popupset>
|
||||
|
||||
<hbox id="body" class="theme-body performance-tool" flex="1">
|
||||
|
||||
<!-- Sidebar: controls and recording list -->
|
||||
<vbox id="recordings-pane">
|
||||
<hbox id="recordings-controls">
|
||||
<html:div id='recording-controls-mount'/>
|
||||
</hbox>
|
||||
<vbox id="recordings-list" class="theme-sidebar" flex="1">
|
||||
<html:div id="recording-list-mount"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
|
||||
<!-- Main panel content -->
|
||||
<vbox id="performance-pane" flex="1">
|
||||
|
||||
<!-- Top toolbar controls -->
|
||||
<toolbar id="performance-toolbar"
|
||||
class="devtools-toolbar">
|
||||
<hbox id="performance-toolbar-controls-other"
|
||||
class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="filter-button"
|
||||
class="devtools-toolbarbutton"
|
||||
popup="performance-filter-menupopup"
|
||||
tooltiptext="&performanceUI.options.filter.tooltiptext;"/>
|
||||
</hbox>
|
||||
<hbox id="performance-toolbar-controls-detail-views"
|
||||
class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="select-waterfall-view"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&performanceUI.toolbar.waterfall;"
|
||||
hidden="true"
|
||||
data-view="waterfall"
|
||||
tooltiptext="&performanceUI.toolbar.waterfall.tooltiptext;" />
|
||||
<toolbarbutton id="select-js-calltree-view"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&performanceUI.toolbar.js-calltree;"
|
||||
hidden="true"
|
||||
data-view="js-calltree"
|
||||
tooltiptext="&performanceUI.toolbar.js-calltree.tooltiptext;" />
|
||||
<toolbarbutton id="select-js-flamegraph-view"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&performanceUI.toolbar.js-flamegraph;"
|
||||
hidden="true"
|
||||
data-view="js-flamegraph"
|
||||
tooltiptext="&performanceUI.toolbar.js-flamegraph.tooltiptext;" />
|
||||
<toolbarbutton id="select-memory-calltree-view"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&performanceUI.toolbar.memory-calltree;"
|
||||
hidden="true"
|
||||
data-view="memory-calltree"
|
||||
tooltiptext="&performanceUI.toolbar.allocations.tooltiptext;" />
|
||||
<toolbarbutton id="select-memory-flamegraph-view"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&performanceUI.toolbar.memory-flamegraph;"
|
||||
hidden="true"
|
||||
data-view="memory-flamegraph" />
|
||||
</hbox>
|
||||
<spacer flex="1"></spacer>
|
||||
<hbox id="performance-toolbar-controls-options"
|
||||
class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="performance-options-button"
|
||||
class="devtools-toolbarbutton devtools-option-toolbarbutton"
|
||||
popup="performance-options-menupopup"
|
||||
tooltiptext="&performanceUI.options.gear.tooltiptext;"/>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
<!-- Recording contents and general notice messages -->
|
||||
<deck id="performance-view" flex="1">
|
||||
|
||||
<!-- A default notice, shown while initially opening the tool.
|
||||
Keep this element the first child of #performance-view. -->
|
||||
<hbox id="tool-loading-notice"
|
||||
class="notice-container"
|
||||
flex="1">
|
||||
</hbox>
|
||||
|
||||
<!-- "Unavailable" notice, shown when the entire tool is disabled,
|
||||
for example, when in private browsing mode. -->
|
||||
<vbox id="unavailable-notice"
|
||||
class="notice-container"
|
||||
align="center"
|
||||
pack="center"
|
||||
flex="1">
|
||||
<hbox pack="center">
|
||||
<html:div class='recording-button-mount'/>
|
||||
</hbox>
|
||||
<description class="tool-disabled-message">
|
||||
&performanceUI.unavailableNoticePB;
|
||||
</description>
|
||||
</vbox>
|
||||
|
||||
<!-- "Empty" notice, shown when there's no recordings available -->
|
||||
<hbox id="empty-notice"
|
||||
class="notice-container"
|
||||
align="center"
|
||||
pack="center"
|
||||
flex="1">
|
||||
<hbox pack="center">
|
||||
<html:div class='recording-button-mount'/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
|
||||
<!-- Recording contents -->
|
||||
<vbox id="performance-view-content" flex="1">
|
||||
|
||||
<!-- Overview graphs -->
|
||||
<vbox id="overview-pane">
|
||||
<hbox id="markers-overview"/>
|
||||
<hbox id="memory-overview"/>
|
||||
<hbox id="time-framerate"/>
|
||||
</vbox>
|
||||
|
||||
<!-- Detail views and specific notice messages -->
|
||||
<deck id="details-pane-container" flex="1">
|
||||
|
||||
<!-- "Loading" notice, shown when a recording is being loaded -->
|
||||
<hbox id="loading-notice"
|
||||
class="notice-container devtools-throbber"
|
||||
align="center"
|
||||
pack="center"
|
||||
flex="1">
|
||||
<label value="&performanceUI.loadingNotice;"/>
|
||||
</hbox>
|
||||
|
||||
<!-- "Recording" notice, shown when a recording is in progress -->
|
||||
<vbox id="recording-notice"
|
||||
class="notice-container"
|
||||
align="center"
|
||||
pack="center"
|
||||
flex="1">
|
||||
<hbox pack="center">
|
||||
<html:div class='recording-button-mount'/>
|
||||
</hbox>
|
||||
<label class="realtime-disabled-on-e10s-message"
|
||||
value="&performanceUI.disabledRealTime.disabledE10S;"/>
|
||||
<label class="buffer-status-message"
|
||||
tooltiptext="&performanceUI.bufferStatusTooltip;"/>
|
||||
<label class="buffer-status-message-full"
|
||||
value="&performanceUI.bufferStatusFull;"/>
|
||||
</vbox>
|
||||
|
||||
<!-- "Console" notice, shown when a console recording is in progress -->
|
||||
<vbox id="console-recording-notice"
|
||||
class="notice-container"
|
||||
align="center"
|
||||
pack="center"
|
||||
flex="1">
|
||||
<hbox class="console-profile-recording-notice">
|
||||
<label value="&performanceUI.console.recordingNoticeStart;" />
|
||||
<label class="console-profile-command" />
|
||||
<label value="&performanceUI.console.recordingNoticeEnd;" />
|
||||
</hbox>
|
||||
<hbox class="console-profile-stop-notice">
|
||||
<label value="&performanceUI.console.stopCommandStart;" />
|
||||
<label class="console-profile-command" />
|
||||
<label value="&performanceUI.console.stopCommandEnd;" />
|
||||
</hbox>
|
||||
<label class="realtime-disabled-on-e10s-message"
|
||||
value="&performanceUI.disabledRealTime.disabledE10S;"/>
|
||||
<label class="buffer-status-message"
|
||||
tooltiptext="&performanceUI.bufferStatusTooltip;"/>
|
||||
<label class="buffer-status-message-full"
|
||||
value="&performanceUI.bufferStatusFull;"/>
|
||||
</vbox>
|
||||
|
||||
<!-- Detail views -->
|
||||
<deck id="details-pane" flex="1">
|
||||
|
||||
<!-- Waterfall -->
|
||||
<hbox id="waterfall-view" flex="1">
|
||||
<html:div xmlns="http://www.w3.org/1999/xhtml" id="waterfall-tree" />
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<vbox id="waterfall-details"
|
||||
class="theme-sidebar"/>
|
||||
</hbox>
|
||||
|
||||
<!-- JS Tree and JIT view -->
|
||||
<hbox id="js-profile-view" flex="1">
|
||||
<vbox id="js-calltree-view" flex="1">
|
||||
<hbox class="call-tree-headers-container">
|
||||
<label class="plain call-tree-header"
|
||||
type="duration"
|
||||
crop="end"
|
||||
value="&performanceUI.table.totalDuration;"
|
||||
tooltiptext="&performanceUI.table.totalDuration.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="percentage"
|
||||
crop="end"
|
||||
value="&performanceUI.table.totalPercentage;"
|
||||
tooltiptext="&performanceUI.table.totalPercentage.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-duration"
|
||||
crop="end"
|
||||
value="&performanceUI.table.selfDuration;"
|
||||
tooltiptext="&performanceUI.table.selfDuration.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-percentage"
|
||||
crop="end"
|
||||
value="&performanceUI.table.selfPercentage;"
|
||||
tooltiptext="&performanceUI.table.selfPercentage.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="samples"
|
||||
crop="end"
|
||||
value="&performanceUI.table.samples;"
|
||||
tooltiptext="&performanceUI.table.samples.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="function"
|
||||
crop="end"
|
||||
value="&performanceUI.table.function;"
|
||||
tooltiptext="&performanceUI.table.function.tooltip;"/>
|
||||
</hbox>
|
||||
<vbox class="call-tree-cells-container" flex="1"/>
|
||||
</vbox>
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<!-- Optimizations Panel -->
|
||||
<vbox id="jit-optimizations-view"
|
||||
class="hidden">
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
<!-- JS FlameChart -->
|
||||
<hbox id="js-flamegraph-view" flex="1">
|
||||
</hbox>
|
||||
|
||||
<!-- Memory Tree -->
|
||||
<vbox id="memory-calltree-view" flex="1">
|
||||
<hbox class="call-tree-headers-container">
|
||||
<label class="plain call-tree-header"
|
||||
type="self-size"
|
||||
crop="end"
|
||||
value="Self Bytes"
|
||||
tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-size-percentage"
|
||||
crop="end"
|
||||
value="Self Bytes %"
|
||||
tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-count"
|
||||
crop="end"
|
||||
value="Self Count"
|
||||
tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-count-percentage"
|
||||
crop="end"
|
||||
value="Self Count %"
|
||||
tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="size"
|
||||
crop="end"
|
||||
value="Total Size"
|
||||
tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="size-percentage"
|
||||
crop="end"
|
||||
value="Total Size %"
|
||||
tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="count"
|
||||
crop="end"
|
||||
value="Total Count"
|
||||
tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="count-percentage"
|
||||
crop="end"
|
||||
value="Total Count %"
|
||||
tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="function"
|
||||
crop="end"
|
||||
value="&performanceUI.table.function;"/>
|
||||
</hbox>
|
||||
<vbox class="call-tree-cells-container" flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<!-- Memory FlameChart -->
|
||||
<hbox id="memory-flamegraph-view" flex="1"></hbox>
|
||||
</deck>
|
||||
</deck>
|
||||
</vbox>
|
||||
</deck>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</window>
|
@ -1,80 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { BrowserLoader } = ChromeUtils.import(
|
||||
"resource://devtools/shared/loader/browser-loader.js"
|
||||
);
|
||||
const { require } = BrowserLoader({
|
||||
baseURI: "resource://devtools/client/performance/",
|
||||
window: window,
|
||||
});
|
||||
|
||||
const {
|
||||
PerformanceController,
|
||||
} = require("devtools/client/performance/performance-controller");
|
||||
const {
|
||||
PerformanceView,
|
||||
} = require("devtools/client/performance/performance-view");
|
||||
const { DetailsView } = require("devtools/client/performance/views/details");
|
||||
const {
|
||||
DetailsSubview,
|
||||
} = require("devtools/client/performance/views/details-abstract-subview");
|
||||
const {
|
||||
JsCallTreeView,
|
||||
} = require("devtools/client/performance/views/details-js-call-tree");
|
||||
const {
|
||||
JsFlameGraphView,
|
||||
} = require("devtools/client/performance/views/details-js-flamegraph");
|
||||
const {
|
||||
MemoryCallTreeView,
|
||||
} = require("devtools/client/performance/views/details-memory-call-tree");
|
||||
const {
|
||||
MemoryFlameGraphView,
|
||||
} = require("devtools/client/performance/views/details-memory-flamegraph");
|
||||
const { OverviewView } = require("devtools/client/performance/views/overview");
|
||||
const {
|
||||
RecordingsView,
|
||||
} = require("devtools/client/performance/views/recordings");
|
||||
const { ToolbarView } = require("devtools/client/performance/views/toolbar");
|
||||
const {
|
||||
WaterfallView,
|
||||
} = require("devtools/client/performance/views/details-waterfall");
|
||||
|
||||
const EVENTS = require("devtools/client/performance/events");
|
||||
|
||||
/**
|
||||
* The performance panel used to only share modules through references on the window
|
||||
* object. We started cleaning this up and to require() explicitly in Bug 1524982, but
|
||||
* some modules and tests are still relying on those references so we keep exposing them
|
||||
* for the moment. Bug 1528777.
|
||||
*/
|
||||
window.PerformanceController = PerformanceController;
|
||||
window.PerformanceView = PerformanceView;
|
||||
window.DetailsView = DetailsView;
|
||||
window.DetailsSubview = DetailsSubview;
|
||||
window.JsCallTreeView = JsCallTreeView;
|
||||
window.JsFlameGraphView = JsFlameGraphView;
|
||||
window.MemoryCallTreeView = MemoryCallTreeView;
|
||||
window.MemoryFlameGraphView = MemoryFlameGraphView;
|
||||
window.OverviewView = OverviewView;
|
||||
window.RecordingsView = RecordingsView;
|
||||
window.ToolbarView = ToolbarView;
|
||||
window.WaterfallView = WaterfallView;
|
||||
|
||||
window.EVENTS = EVENTS;
|
||||
|
||||
/**
|
||||
* DOM query helpers.
|
||||
*/
|
||||
/* exported $, $$ */
|
||||
function $(selector, target = document) {
|
||||
return target.querySelector(selector);
|
||||
}
|
||||
window.$ = $;
|
||||
function $$(selector, target = document) {
|
||||
return target.querySelectorAll(selector);
|
||||
}
|
||||
window.$$ = $$;
|
@ -1,87 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { L10N } = require("devtools/client/performance/modules/global");
|
||||
|
||||
/**
|
||||
* Details about each label stack frame category.
|
||||
* To be kept in sync with the JS::ProfilingCategory enum in ProfilingCategory.h
|
||||
*/
|
||||
const CATEGORIES = [
|
||||
{
|
||||
color: "#d99b28",
|
||||
abbrev: "idle",
|
||||
label: L10N.getStr("category.idle"),
|
||||
},
|
||||
{
|
||||
color: "#5e88b0",
|
||||
abbrev: "other",
|
||||
label: L10N.getStr("category.other"),
|
||||
},
|
||||
{
|
||||
color: "#46afe3",
|
||||
abbrev: "layout",
|
||||
label: L10N.getStr("category.layout"),
|
||||
},
|
||||
{
|
||||
color: "#d96629",
|
||||
abbrev: "js",
|
||||
label: L10N.getStr("category.js"),
|
||||
},
|
||||
{
|
||||
color: "#eb5368",
|
||||
abbrev: "gc",
|
||||
label: L10N.getStr("category.gc"),
|
||||
},
|
||||
{
|
||||
color: "#df80ff",
|
||||
abbrev: "network",
|
||||
label: L10N.getStr("category.network"),
|
||||
},
|
||||
{
|
||||
color: "#70bf53",
|
||||
abbrev: "graphics",
|
||||
label: L10N.getStr("category.graphics"),
|
||||
},
|
||||
{
|
||||
color: "#8fa1b2",
|
||||
abbrev: "dom",
|
||||
label: L10N.getStr("category.dom"),
|
||||
},
|
||||
{
|
||||
// The devtools-only "tools" category which gets computed by
|
||||
// computeIsContentAndCategory and is not part of the category list in
|
||||
// ProfilingStack.h.
|
||||
color: "#8fa1b2",
|
||||
abbrev: "tools",
|
||||
label: L10N.getStr("category.tools"),
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the numeric index for the given category abbreviation.
|
||||
* See `CATEGORIES` above.
|
||||
*/
|
||||
const CATEGORY_INDEX = (() => {
|
||||
const indexForCategory = {};
|
||||
for (
|
||||
let categoryIndex = 0;
|
||||
categoryIndex < CATEGORIES.length;
|
||||
categoryIndex++
|
||||
) {
|
||||
const category = CATEGORIES[categoryIndex];
|
||||
indexForCategory[category.abbrev] = categoryIndex;
|
||||
}
|
||||
|
||||
return function(name) {
|
||||
if (!(name in indexForCategory)) {
|
||||
throw new Error(`Category abbreviation "${name}" does not exist.`);
|
||||
}
|
||||
return indexForCategory[name];
|
||||
};
|
||||
})();
|
||||
|
||||
exports.CATEGORIES = CATEGORIES;
|
||||
exports.CATEGORY_INDEX = CATEGORY_INDEX;
|
@ -1,11 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
exports.Constants = {
|
||||
// ms
|
||||
FRAMERATE_GRAPH_LOW_RES_INTERVAL: 100,
|
||||
// ms
|
||||
FRAMERATE_GRAPH_HIGH_RES_INTERVAL: 16,
|
||||
};
|
@ -1,36 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { MultiLocalizationHelper } = require("devtools/shared/l10n");
|
||||
const { PrefsHelper } = require("devtools/client/shared/prefs");
|
||||
|
||||
/**
|
||||
* Localization convenience methods.
|
||||
*/
|
||||
exports.L10N = new MultiLocalizationHelper(
|
||||
"devtools/client/locales/markers.properties",
|
||||
"devtools/client/locales/performance.properties"
|
||||
);
|
||||
|
||||
/**
|
||||
* A list of preferences for this tool. The values automatically update
|
||||
* if somebody edits edits about:config or the prefs change somewhere else.
|
||||
*
|
||||
* This needs to be registered and unregistered when used for the auto-update
|
||||
* functionality to work. The PerformanceController handles this, but if you
|
||||
* just use this module in a test independently, ensure you call
|
||||
* `registerObserver()` and `unregisterUnobserver()`.
|
||||
*/
|
||||
exports.PREFS = new PrefsHelper("devtools.performance", {
|
||||
"show-triggers-for-gc-types": ["Char", "ui.show-triggers-for-gc-types"],
|
||||
"show-platform-data": ["Bool", "ui.show-platform-data"],
|
||||
"hidden-markers": ["Json", "timeline.hidden-markers"],
|
||||
"memory-sample-probability": ["Float", "memory.sample-probability"],
|
||||
"memory-max-log-length": ["Int", "memory.max-log-length"],
|
||||
"profiler-buffer-size": ["Int", "profiler.buffer-size"],
|
||||
"profiler-sample-frequency": ["Int", "profiler.sample-frequency-hz"],
|
||||
// TODO: re-enable once we flame charts via bug 1148663.
|
||||
"enable-memory-flame": ["Bool", "ui.enable-memory-flame"],
|
||||
});
|
@ -1,173 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
|
||||
const RecordingUtils = require("devtools/shared/performance/recording-utils");
|
||||
const { FileUtils } = require("resource://gre/modules/FileUtils.jsm");
|
||||
const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
// This identifier string is used to tentatively ascertain whether or not
|
||||
// a JSON loaded from disk is actually something generated by this tool.
|
||||
// It isn't, of course, a definitive verification, but a Good Enough™
|
||||
// approximation before continuing the import. Don't localize this.
|
||||
const PERF_TOOL_SERIALIZER_IDENTIFIER = "Recorded Performance Data";
|
||||
const PERF_TOOL_SERIALIZER_LEGACY_VERSION = 1;
|
||||
const PERF_TOOL_SERIALIZER_CURRENT_VERSION = 2;
|
||||
|
||||
/**
|
||||
* Helpers for importing/exporting JSON.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets a nsIScriptableUnicodeConverter instance with a default UTF-8 charset.
|
||||
* @return object
|
||||
*/
|
||||
function getUnicodeConverter() {
|
||||
const cname = "@mozilla.org/intl/scriptableunicodeconverter";
|
||||
const converter = Cc[cname].createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
return converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a recording as JSON to a file. The provided data is assumed to be
|
||||
* acyclical, so that it can be properly serialized.
|
||||
*
|
||||
* @param object recordingData
|
||||
* The recording data to stream as JSON.
|
||||
* @param nsIFile file
|
||||
* The file to stream the data into.
|
||||
* @return object
|
||||
* A promise that is resolved once streaming finishes, or rejected
|
||||
* if there was an error.
|
||||
*/
|
||||
function saveRecordingToFile(recordingData, file) {
|
||||
recordingData.fileType = PERF_TOOL_SERIALIZER_IDENTIFIER;
|
||||
recordingData.version = PERF_TOOL_SERIALIZER_CURRENT_VERSION;
|
||||
|
||||
const string = JSON.stringify(recordingData);
|
||||
const inputStream = getUnicodeConverter().convertToInputStream(string);
|
||||
const outputStream = FileUtils.openSafeFileOutputStream(file);
|
||||
|
||||
return new Promise(resolve => {
|
||||
NetUtil.asyncCopy(inputStream, outputStream, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a recording stored as JSON from a file.
|
||||
*
|
||||
* @param nsIFile file
|
||||
* The file to import the data from.
|
||||
* @return object
|
||||
* A promise that is resolved once importing finishes, or rejected
|
||||
* if there was an error.
|
||||
*/
|
||||
function loadRecordingFromFile(file) {
|
||||
const channel = NetUtil.newChannel({
|
||||
uri: NetUtil.newURI(file),
|
||||
loadUsingSystemPrincipal: true,
|
||||
});
|
||||
|
||||
channel.contentType = "text/plain";
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
NetUtil.asyncFetch(channel, inputStream => {
|
||||
let recordingData;
|
||||
|
||||
try {
|
||||
const string = NetUtil.readInputStreamToString(
|
||||
inputStream,
|
||||
inputStream.available()
|
||||
);
|
||||
recordingData = JSON.parse(string);
|
||||
} catch (e) {
|
||||
reject(new Error("Could not read recording data file."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (recordingData.fileType != PERF_TOOL_SERIALIZER_IDENTIFIER) {
|
||||
reject(new Error("Unrecognized recording data file."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValidSerializerVersion(recordingData.version)) {
|
||||
reject(new Error("Unsupported recording data file version."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (recordingData.version === PERF_TOOL_SERIALIZER_LEGACY_VERSION) {
|
||||
recordingData = convertLegacyData(recordingData);
|
||||
}
|
||||
|
||||
if (recordingData.profile.meta.version === 2) {
|
||||
RecordingUtils.deflateProfile(recordingData.profile);
|
||||
}
|
||||
|
||||
// If the recording has no label, set it to be the
|
||||
// filename without its extension.
|
||||
if (!recordingData.label) {
|
||||
recordingData.label = file.leafName.replace(/\.[^.]+$/, "");
|
||||
}
|
||||
|
||||
resolve(recordingData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether or not the passed in `version`
|
||||
* is supported by this serializer.
|
||||
*
|
||||
* @param number version
|
||||
* @return boolean
|
||||
*/
|
||||
function isValidSerializerVersion(version) {
|
||||
return !!~[
|
||||
PERF_TOOL_SERIALIZER_LEGACY_VERSION,
|
||||
PERF_TOOL_SERIALIZER_CURRENT_VERSION,
|
||||
].indexOf(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes recording data (with version `1`, from the original profiler tool),
|
||||
* and massages the data to be line with the current performance tool's
|
||||
* property names and values.
|
||||
*
|
||||
* @param object legacyData
|
||||
* @return object
|
||||
*/
|
||||
function convertLegacyData(legacyData) {
|
||||
const { profilerData, ticksData, recordingDuration } = legacyData;
|
||||
|
||||
// The `profilerData` and `ticksData` stay, but the previously unrecorded
|
||||
// fields just are empty arrays or objects.
|
||||
const data = {
|
||||
label: profilerData.profilerLabel,
|
||||
duration: recordingDuration,
|
||||
markers: [],
|
||||
frames: [],
|
||||
memory: [],
|
||||
ticks: ticksData,
|
||||
allocations: { sites: [], timestamps: [], frames: [], sizes: [] },
|
||||
profile: profilerData.profile,
|
||||
// Fake a configuration object here if there's tick data,
|
||||
// so that it can be rendered.
|
||||
configuration: {
|
||||
withTicks: !!ticksData.length,
|
||||
withMarkers: false,
|
||||
withMemory: false,
|
||||
withAllocations: false,
|
||||
},
|
||||
systemHost: {},
|
||||
systemClient: {},
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
exports.saveRecordingToFile = saveRecordingToFile;
|
||||
exports.loadRecordingFromFile = loadRecordingFromFile;
|
@ -1,510 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const global = require("devtools/client/performance/modules/global");
|
||||
const demangle = require("devtools/client/shared/demangle");
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const {
|
||||
isChromeScheme,
|
||||
isContentScheme,
|
||||
isWASM,
|
||||
parseURL,
|
||||
} = require("devtools/client/shared/source-utils");
|
||||
|
||||
const {
|
||||
CATEGORY_INDEX,
|
||||
CATEGORIES,
|
||||
} = require("devtools/client/performance/modules/categories");
|
||||
|
||||
// Character codes used in various parsing helper functions.
|
||||
const CHAR_CODE_R = "r".charCodeAt(0);
|
||||
const CHAR_CODE_0 = "0".charCodeAt(0);
|
||||
const CHAR_CODE_9 = "9".charCodeAt(0);
|
||||
const CHAR_CODE_CAP_Z = "Z".charCodeAt(0);
|
||||
|
||||
const CHAR_CODE_LPAREN = "(".charCodeAt(0);
|
||||
const CHAR_CODE_RPAREN = ")".charCodeAt(0);
|
||||
const CHAR_CODE_COLON = ":".charCodeAt(0);
|
||||
const CHAR_CODE_SPACE = " ".charCodeAt(0);
|
||||
const CHAR_CODE_UNDERSCORE = "_".charCodeAt(0);
|
||||
|
||||
const EVAL_TOKEN = "%20%3E%20eval";
|
||||
|
||||
// The cache used to store inflated frames.
|
||||
const gInflatedFrameStore = new WeakMap();
|
||||
|
||||
// The cache used to store frame data from `getInfo`.
|
||||
const gFrameData = new WeakMap();
|
||||
|
||||
/**
|
||||
* Parses the raw location of this function call to retrieve the actual
|
||||
* function name, source url, host name, line and column.
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
function parseLocation(location, fallbackLine, fallbackColumn) {
|
||||
// Parse the `location` for the function name, source url, line, column etc.
|
||||
|
||||
let line, column, url;
|
||||
|
||||
// These two indices are used to extract the resource substring, which is
|
||||
// location[parenIndex + 1 .. lineAndColumnIndex].
|
||||
//
|
||||
// There are 3 variants of location strings in the profiler (with optional
|
||||
// column numbers):
|
||||
// 1) "name (resource:line)"
|
||||
// 2) "resource:line"
|
||||
// 3) "resource"
|
||||
//
|
||||
// For example for (1), take "foo (bar.js:1)".
|
||||
// ^ ^
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// parenIndex will point to ------+ |
|
||||
// |
|
||||
// lineAndColumnIndex will point to -----+
|
||||
//
|
||||
// For an example without parentheses, take "bar.js:2".
|
||||
// ^ ^
|
||||
// | |
|
||||
// parenIndex will point to ----------------+ |
|
||||
// |
|
||||
// lineAndColumIndex will point to ----------------+
|
||||
//
|
||||
// To parse, we look for the last occurrence of the string ' ('.
|
||||
//
|
||||
// For 1), all occurrences of space ' ' characters in the resource string
|
||||
// are urlencoded, so the last occurrence of ' (' is the separator between
|
||||
// the function name and the resource.
|
||||
//
|
||||
// For 2) and 3), there can be no occurences of ' (' since ' ' characters
|
||||
// are urlencoded in the resource string.
|
||||
//
|
||||
// XXX: Note that 3) is ambiguous with Gecko Profiler marker locations like
|
||||
// "EnterJIT". We can't distinguish the two, so we treat 3) like a function
|
||||
// name.
|
||||
let parenIndex = -1;
|
||||
let lineAndColumnIndex = -1;
|
||||
|
||||
const lastCharCode = location.charCodeAt(location.length - 1);
|
||||
let i;
|
||||
if (lastCharCode === CHAR_CODE_RPAREN) {
|
||||
// Case 1)
|
||||
i = location.length - 2;
|
||||
} else if (isNumeric(lastCharCode)) {
|
||||
// Case 2)
|
||||
i = location.length - 1;
|
||||
} else {
|
||||
// Case 3)
|
||||
i = 0;
|
||||
}
|
||||
|
||||
if (i !== 0) {
|
||||
// Look for a :number.
|
||||
let end = i;
|
||||
while (isNumeric(location.charCodeAt(i))) {
|
||||
i--;
|
||||
}
|
||||
if (location.charCodeAt(i) === CHAR_CODE_COLON) {
|
||||
column = location.substr(i + 1, end - i);
|
||||
i--;
|
||||
}
|
||||
|
||||
// Look for a preceding :number.
|
||||
end = i;
|
||||
while (isNumeric(location.charCodeAt(i))) {
|
||||
i--;
|
||||
}
|
||||
|
||||
// If two were found, the first is the line and the second is the
|
||||
// column. If only a single :number was found, then it is the line number.
|
||||
if (location.charCodeAt(i) === CHAR_CODE_COLON) {
|
||||
line = location.substr(i + 1, end - i);
|
||||
lineAndColumnIndex = i;
|
||||
i--;
|
||||
} else {
|
||||
lineAndColumnIndex = i + 1;
|
||||
line = column;
|
||||
column = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the last occurrence of ' (' in case 1).
|
||||
if (lastCharCode === CHAR_CODE_RPAREN) {
|
||||
for (; i >= 0; i--) {
|
||||
if (
|
||||
location.charCodeAt(i) === CHAR_CODE_LPAREN &&
|
||||
i > 0 &&
|
||||
location.charCodeAt(i - 1) === CHAR_CODE_SPACE
|
||||
) {
|
||||
parenIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let parsedUrl;
|
||||
if (lineAndColumnIndex > 0) {
|
||||
const resource = location.substring(parenIndex + 1, lineAndColumnIndex);
|
||||
url = resource.split(" -> ").pop();
|
||||
if (url) {
|
||||
parsedUrl = parseURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
let functionName, fileName, port, host;
|
||||
line = line || fallbackLine;
|
||||
column = column || fallbackColumn;
|
||||
|
||||
// If the URL digged out from the `location` is valid, this is a JS frame.
|
||||
if (parsedUrl) {
|
||||
functionName = location.substring(0, parenIndex - 1);
|
||||
fileName = parsedUrl.fileName;
|
||||
port = parsedUrl.port;
|
||||
host = parsedUrl.host;
|
||||
|
||||
// Check for the case of the filename containing eval
|
||||
// e.g. "file.js%20line%2065%20%3E%20eval"
|
||||
const evalIndex = fileName.indexOf(EVAL_TOKEN);
|
||||
if (evalIndex !== -1 && evalIndex === fileName.length - EVAL_TOKEN.length) {
|
||||
// Match the filename
|
||||
const evalLine = line;
|
||||
const [, _fileName, , _line] =
|
||||
fileName.match(/(.+)(%20line%20(\d+)%20%3E%20eval)/) || [];
|
||||
fileName = `${_fileName} (eval:${evalLine})`;
|
||||
line = _line;
|
||||
assert(
|
||||
_fileName !== undefined,
|
||||
"Filename could not be found from an eval location site"
|
||||
);
|
||||
assert(
|
||||
_line !== undefined,
|
||||
"Line could not be found from an eval location site"
|
||||
);
|
||||
|
||||
// Match the url as well
|
||||
[, url] = url.match(/(.+)( line (\d+) > eval)/) || [];
|
||||
assert(
|
||||
url !== undefined,
|
||||
"The URL could not be parsed correctly from an eval location site"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
functionName = location;
|
||||
url = null;
|
||||
}
|
||||
|
||||
return { functionName, fileName, host, port, url, line, column };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the properties of `isContent` and `category` on a frame.
|
||||
*
|
||||
* @param {InflatedFrame} frame
|
||||
*/
|
||||
function computeIsContentAndCategory(frame) {
|
||||
const location = frame.location;
|
||||
|
||||
// There are 3 variants of location strings in the profiler (with optional
|
||||
// column numbers):
|
||||
// 1) "name (resource:line)"
|
||||
// 2) "resource:line"
|
||||
// 3) "resource"
|
||||
const lastCharCode = location.charCodeAt(location.length - 1);
|
||||
let schemeStartIndex = -1;
|
||||
if (lastCharCode === CHAR_CODE_RPAREN) {
|
||||
// Case 1)
|
||||
//
|
||||
// Need to search for the last occurrence of ' (' to find the start of the
|
||||
// resource string.
|
||||
for (let i = location.length - 2; i >= 0; i--) {
|
||||
if (
|
||||
location.charCodeAt(i) === CHAR_CODE_LPAREN &&
|
||||
i > 0 &&
|
||||
location.charCodeAt(i - 1) === CHAR_CODE_SPACE
|
||||
) {
|
||||
schemeStartIndex = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Cases 2) and 3)
|
||||
schemeStartIndex = 0;
|
||||
}
|
||||
|
||||
// We can't know if WASM frames are content or not at the time of this writing, so label
|
||||
// them all as content.
|
||||
if (isContentScheme(location, schemeStartIndex) || isWASM(location)) {
|
||||
frame.isContent = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.category !== null && frame.category !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (schemeStartIndex !== 0) {
|
||||
for (let j = schemeStartIndex; j < location.length; j++) {
|
||||
if (
|
||||
location.charCodeAt(j) === CHAR_CODE_R &&
|
||||
isChromeScheme(location, j) &&
|
||||
(location.includes("resource://devtools") ||
|
||||
location.includes("resource://devtools"))
|
||||
) {
|
||||
frame.category = CATEGORY_INDEX("tools");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (location === "EnterJIT") {
|
||||
frame.category = CATEGORY_INDEX("js");
|
||||
return;
|
||||
}
|
||||
|
||||
frame.category = CATEGORY_INDEX("other");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get caches to cache inflated frames and computed frame keys of a frame
|
||||
* table.
|
||||
*
|
||||
* @param object framesTable
|
||||
* @return object
|
||||
*/
|
||||
function getInflatedFrameCache(frameTable) {
|
||||
let inflatedCache = gInflatedFrameStore.get(frameTable);
|
||||
if (inflatedCache !== undefined) {
|
||||
return inflatedCache;
|
||||
}
|
||||
|
||||
// Fill with nulls to ensure no holes.
|
||||
inflatedCache = Array.from({ length: frameTable.data.length }, () => null);
|
||||
gInflatedFrameStore.set(frameTable, inflatedCache);
|
||||
return inflatedCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or add an inflated frame to a cache.
|
||||
*
|
||||
* @param object cache
|
||||
* @param number index
|
||||
* @param object frameTable
|
||||
* @param object stringTable
|
||||
*/
|
||||
function getOrAddInflatedFrame(cache, index, frameTable, stringTable) {
|
||||
let inflatedFrame = cache[index];
|
||||
if (inflatedFrame === null) {
|
||||
inflatedFrame = cache[index] = new InflatedFrame(
|
||||
index,
|
||||
frameTable,
|
||||
stringTable
|
||||
);
|
||||
}
|
||||
return inflatedFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* An intermediate data structured used to hold inflated frames.
|
||||
*
|
||||
* @param number index
|
||||
* @param object frameTable
|
||||
* @param object stringTable
|
||||
*/
|
||||
function InflatedFrame(index, frameTable, stringTable) {
|
||||
const LOCATION_SLOT = frameTable.schema.location;
|
||||
const IMPLEMENTATION_SLOT = frameTable.schema.implementation;
|
||||
const OPTIMIZATIONS_SLOT = frameTable.schema.optimizations;
|
||||
const LINE_SLOT = frameTable.schema.line;
|
||||
const CATEGORY_SLOT = frameTable.schema.category;
|
||||
|
||||
const frame = frameTable.data[index];
|
||||
const category = frame[CATEGORY_SLOT];
|
||||
this.location = stringTable[frame[LOCATION_SLOT]];
|
||||
this.implementation = frame[IMPLEMENTATION_SLOT];
|
||||
this.optimizations = frame[OPTIMIZATIONS_SLOT];
|
||||
this.line = frame[LINE_SLOT];
|
||||
this.column = undefined;
|
||||
this.category = category;
|
||||
this.isContent = false;
|
||||
|
||||
// Attempt to compute if this frame is a content frame, and if not,
|
||||
// its category.
|
||||
//
|
||||
// Since only C++ stack frames have associated category information,
|
||||
// attempt to generate a useful category, fallback to the one provided
|
||||
// by the profiling data, or fallback to an unknown category.
|
||||
computeIsContentAndCategory(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the frame key (i.e., equivalence group) according to options. Content
|
||||
* frames are always identified by location. Chrome frames are identified by
|
||||
* location if content-only filtering is off. If content-filtering is on, they
|
||||
* are identified by their category.
|
||||
*
|
||||
* @param object options
|
||||
* @return string
|
||||
*/
|
||||
InflatedFrame.prototype.getFrameKey = function getFrameKey(options) {
|
||||
if (this.isContent || !options.contentOnly || options.isRoot) {
|
||||
options.isMetaCategoryOut = false;
|
||||
return this.location;
|
||||
}
|
||||
|
||||
if (options.isLeaf) {
|
||||
// We only care about leaf platform frames if we are displaying content
|
||||
// only. If no category is present, give the default category of "other".
|
||||
//
|
||||
// 1. The leaf is where time is _actually_ being spent, so we _need_ to
|
||||
// show it to developers in some way to give them accurate profiling
|
||||
// data. We decide to split the platform into various category buckets
|
||||
// and just show time spent in each bucket.
|
||||
//
|
||||
// 2. The calls leading to the leaf _aren't_ where we are spending time,
|
||||
// but _do_ give the developer context for how they got to the leaf
|
||||
// where they _are_ spending time. For non-platform hackers, the
|
||||
// non-leaf platform frames don't give any meaningful context, and so we
|
||||
// can safely filter them out.
|
||||
options.isMetaCategoryOut = true;
|
||||
return this.category;
|
||||
}
|
||||
|
||||
// Return an empty string denoting that this frame should be skipped.
|
||||
return "";
|
||||
};
|
||||
|
||||
function isNumeric(c) {
|
||||
return c >= CHAR_CODE_0 && c <= CHAR_CODE_9;
|
||||
}
|
||||
|
||||
function shouldDemangle(name) {
|
||||
return (
|
||||
name?.charCodeAt &&
|
||||
name.charCodeAt(0) === CHAR_CODE_UNDERSCORE &&
|
||||
name.charCodeAt(1) === CHAR_CODE_UNDERSCORE &&
|
||||
name.charCodeAt(2) === CHAR_CODE_CAP_Z
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the relative costs of this frame compared to a root,
|
||||
* and generates allocations information if specified. Uses caching
|
||||
* if possible.
|
||||
*
|
||||
* @param {ThreadNode|FrameNode} node
|
||||
* The node we are calculating.
|
||||
* @param {ThreadNode} options.root
|
||||
* The root thread node to calculate relative costs.
|
||||
* Generates [self|total] [duration|percentage] values.
|
||||
* @param {boolean} options.allocations
|
||||
* Generates `totalAllocations` and `selfAllocations`.
|
||||
*
|
||||
* @return {object}
|
||||
*/
|
||||
function getFrameInfo(node, options) {
|
||||
let data = gFrameData.get(node);
|
||||
|
||||
if (!data) {
|
||||
if (node.nodeType === "Thread") {
|
||||
data = Object.create(null);
|
||||
data.functionName = global.L10N.getStr("table.root");
|
||||
} else {
|
||||
data = parseLocation(node.location, node.line, node.column);
|
||||
data.hasOptimizations = node.hasOptimizations();
|
||||
data.isContent = node.isContent;
|
||||
data.isMetaCategory = node.isMetaCategory;
|
||||
}
|
||||
data.samples = node.youngestFrameSamples;
|
||||
const hasCategory = node.category !== null && node.category !== undefined;
|
||||
data.categoryData = hasCategory
|
||||
? CATEGORIES[node.category] || CATEGORIES[CATEGORY_INDEX("other")]
|
||||
: {};
|
||||
data.nodeType = node.nodeType;
|
||||
|
||||
// Frame name (function location or some meta information)
|
||||
if (data.isMetaCategory) {
|
||||
data.name = data.categoryData.label;
|
||||
} else if (shouldDemangle(data.functionName)) {
|
||||
data.name = demangle(data.functionName);
|
||||
} else {
|
||||
data.name = data.functionName;
|
||||
}
|
||||
|
||||
data.tooltiptext = data.isMetaCategory
|
||||
? data.categoryData.label
|
||||
: node.location || "";
|
||||
|
||||
gFrameData.set(node, data);
|
||||
}
|
||||
|
||||
// If no options specified, we can't calculate relative values, abort here
|
||||
if (!options) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// If a root specified, calculate the relative costs in the context of
|
||||
// this call tree. The cached store may already have this, but generate
|
||||
// if it does not.
|
||||
const totalSamples = options.root.samples;
|
||||
const totalDuration = options.root.duration;
|
||||
if (options?.root && !data.COSTS_CALCULATED) {
|
||||
data.selfDuration =
|
||||
(node.youngestFrameSamples / totalSamples) * totalDuration;
|
||||
data.selfPercentage = (node.youngestFrameSamples / totalSamples) * 100;
|
||||
data.totalDuration = (node.samples / totalSamples) * totalDuration;
|
||||
data.totalPercentage = (node.samples / totalSamples) * 100;
|
||||
data.COSTS_CALCULATED = true;
|
||||
}
|
||||
|
||||
if (options?.allocations && !data.ALLOCATION_DATA_CALCULATED) {
|
||||
const totalBytes = options.root.byteSize;
|
||||
data.selfCount = node.youngestFrameSamples;
|
||||
data.totalCount = node.samples;
|
||||
data.selfCountPercentage = (node.youngestFrameSamples / totalSamples) * 100;
|
||||
data.totalCountPercentage = (node.samples / totalSamples) * 100;
|
||||
data.selfSize = node.youngestFrameByteSize;
|
||||
data.totalSize = node.byteSize;
|
||||
data.selfSizePercentage = (node.youngestFrameByteSize / totalBytes) * 100;
|
||||
data.totalSizePercentage = (node.byteSize / totalBytes) * 100;
|
||||
data.ALLOCATION_DATA_CALCULATED = true;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
exports.getFrameInfo = getFrameInfo;
|
||||
|
||||
/**
|
||||
* Takes an inverted ThreadNode and searches its youngest frames for
|
||||
* a FrameNode with matching location.
|
||||
*
|
||||
* @param {ThreadNode} threadNode
|
||||
* @param {string} location
|
||||
* @return {?FrameNode}
|
||||
*/
|
||||
function findFrameByLocation(threadNode, location) {
|
||||
if (!threadNode.inverted) {
|
||||
throw new Error(
|
||||
"FrameUtils.findFrameByLocation only supports leaf nodes in an inverted tree."
|
||||
);
|
||||
}
|
||||
|
||||
const calls = threadNode.calls;
|
||||
for (let i = 0; i < calls.length; i++) {
|
||||
if (calls[i].location === location) {
|
||||
return calls[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
exports.findFrameByLocation = findFrameByLocation;
|
||||
exports.computeIsContentAndCategory = computeIsContentAndCategory;
|
||||
exports.parseLocation = parseLocation;
|
||||
exports.getInflatedFrameCache = getInflatedFrameCache;
|
||||
exports.getOrAddInflatedFrame = getOrAddInflatedFrame;
|
||||
exports.InflatedFrame = InflatedFrame;
|
||||
exports.shouldDemangle = shouldDemangle;
|
@ -1,350 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// An outcome of an OptimizationAttempt that is considered successful.
|
||||
const SUCCESSFUL_OUTCOMES = [
|
||||
"GenericSuccess",
|
||||
"Inlined",
|
||||
"DOM",
|
||||
"Monomorphic",
|
||||
"Polymorphic",
|
||||
];
|
||||
|
||||
/**
|
||||
* Model representing JIT optimization sites from the profiler
|
||||
* for a frame (represented by a FrameNode). Requires optimization data from
|
||||
* a profile, which is an array of RawOptimizationSites.
|
||||
*
|
||||
* When the ThreadNode for the profile iterates over the samples' frames, each
|
||||
* frame's optimizations are accumulated in their respective FrameNodes. Each
|
||||
* FrameNode may contain many different optimization sites. One sample may
|
||||
* pick up optimization X on line Y in the frame, with the next sample
|
||||
* containing optimization Z on line W in the same frame, as each frame is
|
||||
* only function.
|
||||
*
|
||||
* An OptimizationSite contains a record of how many times the
|
||||
* RawOptimizationSite was sampled, as well as the unique id based off of the
|
||||
* original profiler array, and the RawOptimizationSite itself as a reference.
|
||||
* @see devtools/client/performance/modules/logic/tree-model.js
|
||||
*
|
||||
* @struct RawOptimizationSite
|
||||
* A structure describing a location in a script that was attempted to be optimized.
|
||||
* Contains all the IonTypes observed, and the sequence of OptimizationAttempts that
|
||||
* were attempted, and the line and column in the script. This is retrieved from the
|
||||
* profiler after a recording, and our base data structure. Should always be referenced,
|
||||
* and unmodified.
|
||||
*
|
||||
* Note that propertyName is an index into a string table, which needs to be
|
||||
* provided in order for the raw optimization site to be inflated.
|
||||
*
|
||||
* @type {Array<IonType>} types
|
||||
* @type {Array<OptimizationAttempt>} attempts
|
||||
* @type {?number} propertyName
|
||||
* @type {number} line
|
||||
* @type {number} column
|
||||
*
|
||||
*
|
||||
* @struct IonType
|
||||
* IonMonkey attempts to classify each value in an optimization site by some type.
|
||||
* Based off of the observed types for a value (like a variable that could be a
|
||||
* string or an instance of an object), it determines what kind of type it should be
|
||||
* classified as. Each IonType here contains an array of all ObservedTypes under `types`,
|
||||
* the Ion type that IonMonkey decided this value should be (Int32, Object, etc.) as
|
||||
* `mirType`, and the component of this optimization type that this value refers to --
|
||||
* like a "getter" optimization, `a[b]`, has site `a` (the "Receiver") and `b`
|
||||
* (the "Index").
|
||||
*
|
||||
* Generally the more ObservedTypes, the more deoptimized this OptimizationSite is.
|
||||
* There could be no ObservedTypes, in which case `typeset` is undefined.
|
||||
*
|
||||
* @type {?Array<ObservedType>} typeset
|
||||
* @type {string} site
|
||||
* @type {string} mirType
|
||||
*
|
||||
*
|
||||
* @struct ObservedType
|
||||
* When IonMonkey attempts to determine what type a value is, it checks on each sample.
|
||||
* The ObservedType can be thought of in more of JavaScripty-terms, rather than C++.
|
||||
* The `keyedBy` property is a high level description of the type, like "primitive",
|
||||
* "constructor", "function", "singleton", "alloc-site" (that one is a bit more weird).
|
||||
* If the `keyedBy` type is a function or constructor, the ObservedType should have a
|
||||
* `name` property, referring to the function or constructor name from the JS source.
|
||||
* If IonMonkey can determine the origin of this type (like where the constructor is
|
||||
* defined), the ObservedType will also have `location` and `line` properties, but
|
||||
* `location` can sometimes be non-URL strings like "self-hosted" or a memory location
|
||||
* like "102ca7880", or no location at all, and maybe `line` is 0 or undefined.
|
||||
*
|
||||
* @type {string} keyedBy
|
||||
* @type {?string} name
|
||||
* @type {?string} location
|
||||
* @type {?string} line
|
||||
*
|
||||
*
|
||||
* @struct OptimizationAttempt
|
||||
* Each RawOptimizationSite contains an array of OptimizationAttempts. Generally,
|
||||
* IonMonkey goes through a series of strategies for each kind of optimization, starting
|
||||
* from most-niche and optimized, to the less-optimized, but more general strategies --
|
||||
* for example, a getter opt may first try to optimize for the scenario of a getter on an
|
||||
* `arguments` object -- that will fail most of the time, as most objects are not
|
||||
* arguments objects, but it will attempt several strategies in order until it finds a
|
||||
* strategy that works, or fails. Even in the best scenarios, some attempts will fail
|
||||
* (like the arguments getter example), which is OK, as long as some attempt succeeds
|
||||
* (with the earlier attempts preferred, as those are more optimized). In an
|
||||
* OptimizationAttempt structure, we store just the `strategy` name and `outcome` name,
|
||||
* both from enums in js/public/TrackedOptimizationInfo.h as TRACKED_STRATEGY_LIST and
|
||||
* TRACKED_OUTCOME_LIST, respectively. An array of successful outcome strings are above
|
||||
* in SUCCESSFUL_OUTCOMES.
|
||||
*
|
||||
* @see js/public/TrackedOptimizationInfo.h
|
||||
*
|
||||
* @type {string} strategy
|
||||
* @type {string} outcome
|
||||
*/
|
||||
|
||||
/*
|
||||
* A wrapper around RawOptimizationSite to record sample count and ID (referring to the
|
||||
* index of where this is in the initially seeded optimizations data), so we don't mutate
|
||||
* the original data from the profiler. Provides methods to access the underlying
|
||||
* optimization data easily, so understanding the semantics of JIT data isn't necessary.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @param {Array<RawOptimizationSite>} optimizations
|
||||
* @param {number} optsIndex
|
||||
*
|
||||
* @type {RawOptimizationSite} data
|
||||
* @type {number} samples
|
||||
* @type {number} id
|
||||
*/
|
||||
|
||||
const OptimizationSite = function(id, opts) {
|
||||
this.id = id;
|
||||
this.data = opts;
|
||||
this.samples = 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for JITOptimizations. A collection of OptimizationSites for a frame.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Array<RawOptimizationSite>} rawSites
|
||||
* Array of raw optimization sites.
|
||||
* @param {Array<string>} stringTable
|
||||
* Array of strings from the profiler used to inflate
|
||||
* JIT optimizations. Do not modify this!
|
||||
*/
|
||||
|
||||
const JITOptimizations = function(rawSites, stringTable) {
|
||||
// Build a histogram of optimization sites.
|
||||
const sites = [];
|
||||
|
||||
for (const rawSite of rawSites) {
|
||||
const existingSite = sites.find(site => site.data === rawSite);
|
||||
if (existingSite) {
|
||||
existingSite.samples++;
|
||||
} else {
|
||||
sites.push(new OptimizationSite(sites.length, rawSite));
|
||||
}
|
||||
}
|
||||
|
||||
// Inflate the optimization information.
|
||||
for (const site of sites) {
|
||||
const data = site.data;
|
||||
const STRATEGY_SLOT = data.attempts.schema.strategy;
|
||||
const OUTCOME_SLOT = data.attempts.schema.outcome;
|
||||
const attempts = data.attempts.data.map(a => {
|
||||
return {
|
||||
id: site.id,
|
||||
strategy: stringTable[a[STRATEGY_SLOT]],
|
||||
outcome: stringTable[a[OUTCOME_SLOT]],
|
||||
};
|
||||
});
|
||||
const types = data.types.map(t => {
|
||||
const typeset = maybeTypeset(t.typeset, stringTable);
|
||||
if (typeset) {
|
||||
typeset.forEach(ts => {
|
||||
ts.id = site.id;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: site.id,
|
||||
typeset,
|
||||
site: stringTable[t.site],
|
||||
mirType: stringTable[t.mirType],
|
||||
};
|
||||
});
|
||||
// Add IDs to to all children objects, so we can correllate sites when
|
||||
// just looking at a specific type, attempt, etc..
|
||||
attempts.id = types.id = site.id;
|
||||
|
||||
site.data = {
|
||||
attempts,
|
||||
types,
|
||||
propertyName: maybeString(stringTable, data.propertyName),
|
||||
line: data.line,
|
||||
column: data.column,
|
||||
};
|
||||
}
|
||||
|
||||
this.optimizationSites = sites.sort((a, b) => b.samples - a.samples);
|
||||
};
|
||||
|
||||
/**
|
||||
* Make JITOptimizations iterable.
|
||||
*/
|
||||
JITOptimizations.prototype = {
|
||||
[Symbol.iterator]: function*() {
|
||||
yield* this.optimizationSites;
|
||||
},
|
||||
|
||||
get length() {
|
||||
return this.optimizationSites.length;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes an "outcome" string from an OptimizationAttempt and returns
|
||||
* a boolean indicating whether or not its a successful outcome.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
function isSuccessfulOutcome(outcome) {
|
||||
return !!~SUCCESSFUL_OUTCOMES.indexOf(outcome);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an OptimizationSite. Returns a boolean indicating if the passed
|
||||
* in OptimizationSite has a "good" outcome at the end of its attempted strategies.
|
||||
*
|
||||
* @param {OptimizationSite} optimizationSite
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
function hasSuccessfulOutcome(optimizationSite) {
|
||||
const attempts = optimizationSite.data.attempts;
|
||||
const lastOutcome = attempts[attempts.length - 1].outcome;
|
||||
return isSuccessfulOutcome(lastOutcome);
|
||||
}
|
||||
|
||||
function maybeString(stringTable, index) {
|
||||
return index ? stringTable[index] : undefined;
|
||||
}
|
||||
|
||||
function maybeTypeset(typeset, stringTable) {
|
||||
if (!typeset) {
|
||||
return undefined;
|
||||
}
|
||||
return typeset.map(ty => {
|
||||
return {
|
||||
keyedBy: maybeString(stringTable, ty.keyedBy),
|
||||
name: maybeString(stringTable, ty.name),
|
||||
location: maybeString(stringTable, ty.location),
|
||||
line: ty.line,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Map of optimization implementation names to an enum.
|
||||
const IMPLEMENTATION_MAP = {
|
||||
interpreter: 0,
|
||||
baseline: 1,
|
||||
ion: 2,
|
||||
};
|
||||
const IMPLEMENTATION_NAMES = Object.keys(IMPLEMENTATION_MAP);
|
||||
|
||||
/**
|
||||
* Takes data from a FrameNode and computes rendering positions for
|
||||
* a stacked mountain graph, to visualize JIT optimization tiers over time.
|
||||
*
|
||||
* @param {FrameNode} frameNode
|
||||
* The FrameNode who's optimizations we're iterating.
|
||||
* @param {Array<number>} sampleTimes
|
||||
* An array of every sample time within the range we're counting.
|
||||
* From a ThreadNode's `sampleTimes` property.
|
||||
* @param {number} bucketSize
|
||||
* Size of each bucket in milliseconds.
|
||||
* `duration / resolution = bucketSize` in OptimizationsGraph.
|
||||
* @return {?Array<object>}
|
||||
*/
|
||||
function createTierGraphDataFromFrameNode(frameNode, sampleTimes, bucketSize) {
|
||||
const tierData = frameNode.getTierData();
|
||||
const stringTable = frameNode._stringTable;
|
||||
const output = [];
|
||||
let implEnum;
|
||||
|
||||
let tierDataIndex = 0;
|
||||
let nextOptSample = tierData[tierDataIndex];
|
||||
|
||||
// Bucket data
|
||||
let samplesInCurrentBucket = 0;
|
||||
let currentBucketStartTime = sampleTimes[0];
|
||||
let bucket = [];
|
||||
|
||||
// Store previous data point so we can have straight vertical lines
|
||||
let previousValues;
|
||||
|
||||
// Iterate one after the samples, so we can finalize the last bucket
|
||||
for (let i = 0; i <= sampleTimes.length; i++) {
|
||||
const sampleTime = sampleTimes[i];
|
||||
|
||||
// If this sample is in the next bucket, or we're done
|
||||
// checking sampleTimes and on the last iteration, finalize previous bucket
|
||||
if (
|
||||
sampleTime >= currentBucketStartTime + bucketSize ||
|
||||
i >= sampleTimes.length
|
||||
) {
|
||||
const dataPoint = {};
|
||||
dataPoint.values = [];
|
||||
dataPoint.delta = currentBucketStartTime;
|
||||
|
||||
// Map the opt site counts as a normalized percentage (0-1)
|
||||
// of its count in context of total samples this bucket
|
||||
for (let j = 0; j < IMPLEMENTATION_NAMES.length; j++) {
|
||||
dataPoint.values[j] = (bucket[j] || 0) / (samplesInCurrentBucket || 1);
|
||||
}
|
||||
|
||||
// Push the values from the previous bucket to the same time
|
||||
// as the current bucket so we get a straight vertical line.
|
||||
if (previousValues) {
|
||||
const data = Object.create(null);
|
||||
data.values = previousValues;
|
||||
data.delta = currentBucketStartTime;
|
||||
output.push(data);
|
||||
}
|
||||
|
||||
output.push(dataPoint);
|
||||
|
||||
// Set the new start time of this bucket and reset its count
|
||||
currentBucketStartTime += bucketSize;
|
||||
samplesInCurrentBucket = 0;
|
||||
previousValues = dataPoint.values;
|
||||
bucket = [];
|
||||
}
|
||||
|
||||
// If this sample observed an optimization in this frame, record it
|
||||
if (nextOptSample && nextOptSample.time === sampleTime) {
|
||||
// If no implementation defined, it was the "interpreter".
|
||||
implEnum =
|
||||
IMPLEMENTATION_MAP[
|
||||
stringTable[nextOptSample.implementation] || "interpreter"
|
||||
];
|
||||
bucket[implEnum] = (bucket[implEnum] || 0) + 1;
|
||||
nextOptSample = tierData[++tierDataIndex];
|
||||
}
|
||||
|
||||
samplesInCurrentBucket++;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
exports.createTierGraphDataFromFrameNode = createTierGraphDataFromFrameNode;
|
||||
exports.OptimizationSite = OptimizationSite;
|
||||
exports.JITOptimizations = JITOptimizations;
|
||||
exports.hasSuccessfulOutcome = hasSuccessfulOutcome;
|
||||
exports.isSuccessfulOutcome = isSuccessfulOutcome;
|
||||
exports.SUCCESSFUL_OUTCOMES = SUCCESSFUL_OUTCOMES;
|
@ -1,12 +0,0 @@
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
"frame-utils.js",
|
||||
"jit.js",
|
||||
"telemetry.js",
|
||||
"tree-model.js",
|
||||
"waterfall-utils.js",
|
||||
)
|
@ -1,106 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Telemetry = require("devtools/client/shared/telemetry");
|
||||
const EVENTS = require("devtools/client/performance/events");
|
||||
|
||||
const EVENT_MAP_FLAGS = new Map([
|
||||
[EVENTS.RECORDING_IMPORTED, "DEVTOOLS_PERFTOOLS_RECORDING_IMPORT_FLAG"],
|
||||
[EVENTS.RECORDING_EXPORTED, "DEVTOOLS_PERFTOOLS_RECORDING_EXPORT_FLAG"],
|
||||
]);
|
||||
|
||||
const RECORDING_FEATURES = [
|
||||
"withMarkers",
|
||||
"withTicks",
|
||||
"withMemory",
|
||||
"withAllocations",
|
||||
];
|
||||
|
||||
const SELECTED_VIEW_HISTOGRAM_NAME = "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS";
|
||||
|
||||
function PerformanceTelemetry(emitter) {
|
||||
this._emitter = emitter;
|
||||
this._telemetry = new Telemetry();
|
||||
this.onFlagEvent = this.onFlagEvent.bind(this);
|
||||
this.onRecordingStateChange = this.onRecordingStateChange.bind(this);
|
||||
this.onViewSelected = this.onViewSelected.bind(this);
|
||||
|
||||
for (const [event] of EVENT_MAP_FLAGS) {
|
||||
this._emitter.on(event, this.onFlagEvent.bind(this, event));
|
||||
}
|
||||
|
||||
this._emitter.on(EVENTS.RECORDING_STATE_CHANGE, this.onRecordingStateChange);
|
||||
this._emitter.on(EVENTS.UI_DETAILS_VIEW_SELECTED, this.onViewSelected);
|
||||
}
|
||||
|
||||
PerformanceTelemetry.prototype.destroy = function() {
|
||||
if (this._previousView) {
|
||||
this._telemetry.finishKeyed(
|
||||
SELECTED_VIEW_HISTOGRAM_NAME,
|
||||
this._previousView,
|
||||
this,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
for (const [event] of EVENT_MAP_FLAGS) {
|
||||
this._emitter.off(event, this.onFlagEvent);
|
||||
}
|
||||
this._emitter.off(EVENTS.RECORDING_STATE_CHANGE, this.onRecordingStateChange);
|
||||
this._emitter.off(EVENTS.UI_DETAILS_VIEW_SELECTED, this.onViewSelected);
|
||||
this._emitter = null;
|
||||
};
|
||||
|
||||
PerformanceTelemetry.prototype.onFlagEvent = function(eventName, ...data) {
|
||||
this._telemetry.getHistogramById(EVENT_MAP_FLAGS.get(eventName)).add(true);
|
||||
};
|
||||
|
||||
PerformanceTelemetry.prototype.onRecordingStateChange = function(
|
||||
status,
|
||||
model
|
||||
) {
|
||||
if (status != "recording-stopped") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.isConsole()) {
|
||||
this._telemetry
|
||||
.getHistogramById("DEVTOOLS_PERFTOOLS_CONSOLE_RECORDING_COUNT")
|
||||
.add(true);
|
||||
} else {
|
||||
this._telemetry
|
||||
.getHistogramById("DEVTOOLS_PERFTOOLS_RECORDING_COUNT")
|
||||
.add(true);
|
||||
}
|
||||
|
||||
this._telemetry
|
||||
.getHistogramById("DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS")
|
||||
.add(model.getDuration());
|
||||
|
||||
const config = model.getConfiguration();
|
||||
for (const k in config) {
|
||||
if (RECORDING_FEATURES.includes(k)) {
|
||||
this._telemetry
|
||||
.getKeyedHistogramById("DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED")
|
||||
.add(k, config[k]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PerformanceTelemetry.prototype.onViewSelected = function(viewName) {
|
||||
if (this._previousView) {
|
||||
this._telemetry.finishKeyed(
|
||||
SELECTED_VIEW_HISTOGRAM_NAME,
|
||||
this._previousView,
|
||||
this,
|
||||
false
|
||||
);
|
||||
}
|
||||
this._previousView = viewName;
|
||||
this._telemetry.startKeyed(SELECTED_VIEW_HISTOGRAM_NAME, viewName, this);
|
||||
};
|
||||
|
||||
exports.PerformanceTelemetry = PerformanceTelemetry;
|
@ -1,589 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
JITOptimizations,
|
||||
} = require("devtools/client/performance/modules/logic/jit");
|
||||
const FrameUtils = require("devtools/client/performance/modules/logic/frame-utils");
|
||||
|
||||
/**
|
||||
* A call tree for a thread. This is essentially a linkage between all frames
|
||||
* of all samples into a single tree structure, with additional information
|
||||
* on each node, like the time spent (in milliseconds) and samples count.
|
||||
*
|
||||
* @param object thread
|
||||
* The raw thread object received from the backend. Contains samples,
|
||||
* stackTable, frameTable, and stringTable.
|
||||
* @param object options
|
||||
* Additional supported options
|
||||
* - number startTime
|
||||
* - number endTime
|
||||
* - boolean contentOnly [optional]
|
||||
* - boolean invertTree [optional]
|
||||
* - boolean flattenRecursion [optional]
|
||||
*/
|
||||
function ThreadNode(thread, options = {}) {
|
||||
if (options.endTime == void 0 || options.startTime == void 0) {
|
||||
throw new Error("ThreadNode requires both `startTime` and `endTime`.");
|
||||
}
|
||||
this.samples = 0;
|
||||
this.sampleTimes = [];
|
||||
this.youngestFrameSamples = 0;
|
||||
this.calls = [];
|
||||
this.duration = options.endTime - options.startTime;
|
||||
this.nodeType = "Thread";
|
||||
this.inverted = options.invertTree;
|
||||
|
||||
// Total bytesize of all allocations if enabled
|
||||
this.byteSize = 0;
|
||||
this.youngestFrameByteSize = 0;
|
||||
|
||||
const { samples, stackTable, frameTable, stringTable } = thread;
|
||||
|
||||
// Nothing to do if there are no samples.
|
||||
if (samples.data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._buildInverted(samples, stackTable, frameTable, stringTable, options);
|
||||
if (!options.invertTree) {
|
||||
this._uninvert();
|
||||
}
|
||||
}
|
||||
|
||||
ThreadNode.prototype = {
|
||||
/**
|
||||
* Build an inverted call tree from profile samples. The format of the
|
||||
* samples is described in tools/profiler/ProfileEntry.h, under the heading
|
||||
* "Thread profile JSON Format".
|
||||
*
|
||||
* The profile data is naturally presented inverted. Inverting the call tree
|
||||
* is also the default in the Performance tool.
|
||||
*
|
||||
* @param object samples
|
||||
* The raw samples array received from the backend.
|
||||
* @param object stackTable
|
||||
* The table of deduplicated stacks from the backend.
|
||||
* @param object frameTable
|
||||
* The table of deduplicated frames from the backend.
|
||||
* @param object stringTable
|
||||
* The table of deduplicated strings from the backend.
|
||||
* @param object options
|
||||
* Additional supported options
|
||||
* - number startTime
|
||||
* - number endTime
|
||||
* - boolean contentOnly [optional]
|
||||
* - boolean invertTree [optional]
|
||||
*/
|
||||
_buildInverted: function buildInverted(
|
||||
samples,
|
||||
stackTable,
|
||||
frameTable,
|
||||
stringTable,
|
||||
options
|
||||
) {
|
||||
function getOrAddFrameNode(
|
||||
calls,
|
||||
isLeaf,
|
||||
frameKey,
|
||||
inflatedFrame,
|
||||
isMetaCategory,
|
||||
leafTable
|
||||
) {
|
||||
// Insert the inflated frame into the call tree at the current level.
|
||||
let frameNode;
|
||||
|
||||
// Leaf nodes have fan out much greater than non-leaf nodes, thus the
|
||||
// use of a hash table. Otherwise, do linear search.
|
||||
//
|
||||
// Note that this method is very hot, thus the manual looping over
|
||||
// Array.prototype.find.
|
||||
if (isLeaf) {
|
||||
frameNode = leafTable[frameKey];
|
||||
} else {
|
||||
for (let i = 0; i < calls.length; i++) {
|
||||
if (calls[i].key === frameKey) {
|
||||
frameNode = calls[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!frameNode) {
|
||||
frameNode = new FrameNode(frameKey, inflatedFrame, isMetaCategory);
|
||||
if (isLeaf) {
|
||||
leafTable[frameKey] = frameNode;
|
||||
}
|
||||
calls.push(frameNode);
|
||||
}
|
||||
|
||||
return frameNode;
|
||||
}
|
||||
|
||||
const SAMPLE_STACK_SLOT = samples.schema.stack;
|
||||
const SAMPLE_TIME_SLOT = samples.schema.time;
|
||||
const SAMPLE_BYTESIZE_SLOT = samples.schema.size;
|
||||
|
||||
const STACK_PREFIX_SLOT = stackTable.schema.prefix;
|
||||
const STACK_FRAME_SLOT = stackTable.schema.frame;
|
||||
|
||||
const getOrAddInflatedFrame = FrameUtils.getOrAddInflatedFrame;
|
||||
|
||||
const samplesData = samples.data;
|
||||
const stacksData = stackTable.data;
|
||||
|
||||
// Caches.
|
||||
const inflatedFrameCache = FrameUtils.getInflatedFrameCache(frameTable);
|
||||
const leafTable = Object.create(null);
|
||||
|
||||
const startTime = options.startTime;
|
||||
const endTime = options.endTime;
|
||||
const flattenRecursion = options.flattenRecursion;
|
||||
|
||||
// Reused options object passed to InflatedFrame.prototype.getFrameKey.
|
||||
const mutableFrameKeyOptions = {
|
||||
contentOnly: options.contentOnly,
|
||||
isRoot: false,
|
||||
isLeaf: false,
|
||||
isMetaCategoryOut: false,
|
||||
};
|
||||
|
||||
let byteSize = 0;
|
||||
for (let i = 0; i < samplesData.length; i++) {
|
||||
const sample = samplesData[i];
|
||||
const sampleTime = sample[SAMPLE_TIME_SLOT];
|
||||
|
||||
if (SAMPLE_BYTESIZE_SLOT !== void 0) {
|
||||
byteSize = sample[SAMPLE_BYTESIZE_SLOT];
|
||||
}
|
||||
|
||||
// A sample's end time is considered to be its time of sampling. Its
|
||||
// start time is the sampling time of the previous sample.
|
||||
//
|
||||
// Thus, we compare sampleTime <= start instead of < to filter out
|
||||
// samples that end exactly at the start time.
|
||||
if (!sampleTime || sampleTime <= startTime || sampleTime > endTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let stackIndex = sample[SAMPLE_STACK_SLOT];
|
||||
let calls = this.calls;
|
||||
let prevCalls = this.calls;
|
||||
let prevFrameKey;
|
||||
let isLeaf = (mutableFrameKeyOptions.isLeaf = true);
|
||||
const skipRoot = options.invertTree;
|
||||
|
||||
// Inflate the stack and build the FrameNode call tree directly.
|
||||
//
|
||||
// In the profiler data, each frame's stack is referenced by an index
|
||||
// into stackTable.
|
||||
//
|
||||
// Each entry in stackTable is a pair [ prefixIndex, frameIndex ]. The
|
||||
// prefixIndex is itself an index into stackTable, referencing the
|
||||
// prefix of the current stack (that is, the younger frames). In other
|
||||
// words, the stackTable is encoded as a trie of the inverted
|
||||
// callstack. The frameIndex is an index into frameTable, describing the
|
||||
// frame at the current depth.
|
||||
//
|
||||
// This algorithm inflates each frame in the frame table while walking
|
||||
// the stack trie as described above.
|
||||
//
|
||||
// The frame key is then computed from the inflated frame /and/ the
|
||||
// current depth in the FrameNode call tree. That is, the frame key is
|
||||
// not wholly determinable from just the inflated frame.
|
||||
//
|
||||
// For content frames, the frame key is just its location. For chrome
|
||||
// frames, the key may be a metacategory or its location, depending on
|
||||
// rendering options and its position in the FrameNode call tree.
|
||||
//
|
||||
// The frame key is then used to build up the inverted FrameNode call
|
||||
// tree.
|
||||
//
|
||||
// Note that various filtering functions, such as filtering for content
|
||||
// frames or flattening recursion, are inlined into the stack inflation
|
||||
// loop. This is important for performance as it avoids intermediate
|
||||
// structures and multiple passes.
|
||||
while (stackIndex !== null) {
|
||||
const stackEntry = stacksData[stackIndex];
|
||||
const frameIndex = stackEntry[STACK_FRAME_SLOT];
|
||||
|
||||
// Fetch the stack prefix (i.e. older frames) index.
|
||||
stackIndex = stackEntry[STACK_PREFIX_SLOT];
|
||||
|
||||
// Do not include the (root) node in this sample, as the costs of each frame
|
||||
// will make it clear to differentiate (root)->B vs (root)->A->B
|
||||
// when a tree is inverted, a revert of bug 1147604
|
||||
if (stackIndex === null && skipRoot) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Inflate the frame.
|
||||
const inflatedFrame = getOrAddInflatedFrame(
|
||||
inflatedFrameCache,
|
||||
frameIndex,
|
||||
frameTable,
|
||||
stringTable
|
||||
);
|
||||
|
||||
// Compute the frame key.
|
||||
mutableFrameKeyOptions.isRoot = stackIndex === null;
|
||||
const frameKey = inflatedFrame.getFrameKey(mutableFrameKeyOptions);
|
||||
|
||||
// An empty frame key means this frame should be skipped.
|
||||
if (frameKey === "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we shouldn't flatten the current frame into the previous one, advance a
|
||||
// level in the call tree.
|
||||
const shouldFlatten = flattenRecursion && frameKey === prevFrameKey;
|
||||
if (!shouldFlatten) {
|
||||
calls = prevCalls;
|
||||
}
|
||||
|
||||
const frameNode = getOrAddFrameNode(
|
||||
calls,
|
||||
isLeaf,
|
||||
frameKey,
|
||||
inflatedFrame,
|
||||
mutableFrameKeyOptions.isMetaCategoryOut,
|
||||
leafTable
|
||||
);
|
||||
if (isLeaf) {
|
||||
frameNode.youngestFrameSamples++;
|
||||
frameNode._addOptimizations(
|
||||
inflatedFrame.optimizations,
|
||||
inflatedFrame.implementation,
|
||||
sampleTime,
|
||||
stringTable
|
||||
);
|
||||
|
||||
if (byteSize) {
|
||||
frameNode.youngestFrameByteSize += byteSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't overcount flattened recursive frames.
|
||||
if (!shouldFlatten) {
|
||||
frameNode.samples++;
|
||||
if (byteSize) {
|
||||
frameNode.byteSize += byteSize;
|
||||
}
|
||||
}
|
||||
|
||||
prevFrameKey = frameKey;
|
||||
prevCalls = frameNode.calls;
|
||||
isLeaf = mutableFrameKeyOptions.isLeaf = false;
|
||||
}
|
||||
|
||||
this.samples++;
|
||||
this.sampleTimes.push(sampleTime);
|
||||
if (byteSize) {
|
||||
this.byteSize += byteSize;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Uninverts the call tree after its having been built.
|
||||
*/
|
||||
_uninvert: function uninvert() {
|
||||
function mergeOrAddFrameNode(calls, node, samples, size) {
|
||||
// Unlike the inverted call tree, we don't use a root table for the top
|
||||
// level, as in general, there are many fewer entry points than
|
||||
// leaves. Instead, linear search is used regardless of level.
|
||||
for (let i = 0; i < calls.length; i++) {
|
||||
if (calls[i].key === node.key) {
|
||||
const foundNode = calls[i];
|
||||
foundNode._merge(node, samples, size);
|
||||
return foundNode.calls;
|
||||
}
|
||||
}
|
||||
const copy = node._clone(samples, size);
|
||||
calls.push(copy);
|
||||
return copy.calls;
|
||||
}
|
||||
|
||||
const workstack = [{ node: this, level: 0 }];
|
||||
const spine = [];
|
||||
let entry;
|
||||
|
||||
// The new root.
|
||||
const rootCalls = [];
|
||||
|
||||
// Walk depth-first and keep the current spine (e.g., callstack).
|
||||
do {
|
||||
entry = workstack.pop();
|
||||
if (entry) {
|
||||
spine[entry.level] = entry;
|
||||
|
||||
const node = entry.node;
|
||||
const calls = node.calls;
|
||||
let callSamples = 0;
|
||||
let callByteSize = 0;
|
||||
|
||||
// Continue the depth-first walk.
|
||||
for (let i = 0; i < calls.length; i++) {
|
||||
workstack.push({ node: calls[i], level: entry.level + 1 });
|
||||
callSamples += calls[i].samples;
|
||||
callByteSize += calls[i].byteSize;
|
||||
}
|
||||
|
||||
// The sample delta is used to distinguish stacks.
|
||||
//
|
||||
// Suppose we have the following stack samples:
|
||||
//
|
||||
// A -> B
|
||||
// A -> C
|
||||
// A
|
||||
//
|
||||
// The inverted tree is:
|
||||
//
|
||||
// A
|
||||
// / \
|
||||
// B C
|
||||
//
|
||||
// with A.samples = 3, B.samples = 1, C.samples = 1.
|
||||
//
|
||||
// A is distinguished as being its own stack because
|
||||
// A.samples - (B.samples + C.samples) > 0.
|
||||
//
|
||||
// Note that bottoming out is a degenerate where callSamples = 0.
|
||||
|
||||
const samplesDelta = node.samples - callSamples;
|
||||
const byteSizeDelta = node.byteSize - callByteSize;
|
||||
if (samplesDelta > 0) {
|
||||
// Reverse the spine and add them to the uninverted call tree.
|
||||
let uninvertedCalls = rootCalls;
|
||||
for (let level = entry.level; level > 0; level--) {
|
||||
const callee = spine[level];
|
||||
uninvertedCalls = mergeOrAddFrameNode(
|
||||
uninvertedCalls,
|
||||
callee.node,
|
||||
samplesDelta,
|
||||
byteSizeDelta
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (entry);
|
||||
|
||||
// Replace the toplevel calls with rootCalls, which now contains the
|
||||
// uninverted roots.
|
||||
this.calls = rootCalls;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets additional details about this node.
|
||||
* @see FrameNode.prototype.getInfo for more information.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
getInfo: function(options) {
|
||||
return FrameUtils.getFrameInfo(this, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Mimicks the interface of FrameNode, and a ThreadNode can never have
|
||||
* optimization data (at the moment, anyway), so provide a function
|
||||
* to return null so we don't need to check if a frame node is a thread
|
||||
* or not everytime we fetch optimization data.
|
||||
*
|
||||
* @return {null}
|
||||
*/
|
||||
|
||||
hasOptimizations: function() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A function call node in a tree. Represents a function call with a unique context,
|
||||
* resulting in each FrameNode having its own row in the corresponding tree view.
|
||||
* Take samples:
|
||||
* A()->B()->C()
|
||||
* A()->B()
|
||||
* Q()->B()
|
||||
*
|
||||
* In inverted tree, A()->B()->C() would have one frame node, and A()->B() and
|
||||
* Q()->B() would share a frame node.
|
||||
* In an uninverted tree, A()->B()->C() and A()->B() would share a frame node,
|
||||
* with Q()->B() having its own.
|
||||
*
|
||||
* In all cases, all the frame nodes originated from the same InflatedFrame.
|
||||
*
|
||||
* @param string frameKey
|
||||
* The key associated with this frame. The key determines identity of
|
||||
* the node.
|
||||
* @param string location
|
||||
* The location of this function call. Note that this isn't sanitized,
|
||||
* so it may very well (not?) include the function name, url, etc.
|
||||
* @param number line
|
||||
* The line number inside the source containing this function call.
|
||||
* @param number category
|
||||
* The category type of this function call ("js", "graphics" etc.).
|
||||
* @param number allocations
|
||||
* The number of memory allocations performed in this frame.
|
||||
* @param number isContent
|
||||
* Whether this frame is content.
|
||||
* @param boolean isMetaCategory
|
||||
* Whether or not this is a platform node that should appear as a
|
||||
* generalized meta category or not.
|
||||
*/
|
||||
function FrameNode(
|
||||
frameKey,
|
||||
{ location, line, category, isContent },
|
||||
isMetaCategory
|
||||
) {
|
||||
this.key = frameKey;
|
||||
this.location = location;
|
||||
this.line = line;
|
||||
this.youngestFrameSamples = 0;
|
||||
this.samples = 0;
|
||||
this.calls = [];
|
||||
this.isContent = !!isContent;
|
||||
this._optimizations = null;
|
||||
this._tierData = [];
|
||||
this._stringTable = null;
|
||||
this.isMetaCategory = !!isMetaCategory;
|
||||
this.category = category;
|
||||
this.nodeType = "Frame";
|
||||
this.byteSize = 0;
|
||||
this.youngestFrameByteSize = 0;
|
||||
}
|
||||
|
||||
FrameNode.prototype = {
|
||||
/**
|
||||
* Take optimization data observed for this frame.
|
||||
*
|
||||
* @param object optimizationSite
|
||||
* Any JIT optimization information attached to the current
|
||||
* sample. Lazily inflated via stringTable.
|
||||
* @param number implementation
|
||||
* JIT implementation used for this observed frame (baseline, ion);
|
||||
* can be null indicating "interpreter"
|
||||
* @param number time
|
||||
* The time this optimization occurred.
|
||||
* @param object stringTable
|
||||
* The string table used to inflate the optimizationSite.
|
||||
*/
|
||||
_addOptimizations: function(site, implementation, time, stringTable) {
|
||||
// Simply accumulate optimization sites for now. Processing is done lazily
|
||||
// by JITOptimizations, if optimization information is actually displayed.
|
||||
if (site) {
|
||||
let opts = this._optimizations;
|
||||
if (opts === null) {
|
||||
opts = this._optimizations = [];
|
||||
}
|
||||
opts.push(site);
|
||||
}
|
||||
|
||||
if (!this._stringTable) {
|
||||
this._stringTable = stringTable;
|
||||
}
|
||||
|
||||
// Record type of implementation used and the sample time
|
||||
this._tierData.push({ implementation, time });
|
||||
},
|
||||
|
||||
_clone: function(samples, size) {
|
||||
const newNode = new FrameNode(this.key, this, this.isMetaCategory);
|
||||
newNode._merge(this, samples, size);
|
||||
return newNode;
|
||||
},
|
||||
|
||||
_merge: function(otherNode, samples, size) {
|
||||
if (this === otherNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.samples += samples;
|
||||
this.byteSize += size;
|
||||
if (otherNode.youngestFrameSamples > 0) {
|
||||
this.youngestFrameSamples += samples;
|
||||
}
|
||||
|
||||
if (otherNode.youngestFrameByteSize > 0) {
|
||||
this.youngestFrameByteSize += otherNode.youngestFrameByteSize;
|
||||
}
|
||||
|
||||
if (this._stringTable === null) {
|
||||
this._stringTable = otherNode._stringTable;
|
||||
}
|
||||
|
||||
if (otherNode._optimizations) {
|
||||
if (!this._optimizations) {
|
||||
this._optimizations = [];
|
||||
}
|
||||
const opts = this._optimizations;
|
||||
const otherOpts = otherNode._optimizations;
|
||||
for (let i = 0; i < otherOpts.length; i++) {
|
||||
opts.push(otherOpts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (otherNode._tierData.length) {
|
||||
const tierData = this._tierData;
|
||||
const otherTierData = otherNode._tierData;
|
||||
for (let i = 0; i < otherTierData.length; i++) {
|
||||
tierData.push(otherTierData[i]);
|
||||
}
|
||||
tierData.sort((a, b) => a.time - b.time);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the parsed location and additional data describing
|
||||
* this frame. Uses cached data if possible. Takes the following
|
||||
* options:
|
||||
*
|
||||
* @param {ThreadNode} options.root
|
||||
* The root thread node to calculate relative costs.
|
||||
* Generates [self|total] [duration|percentage] values.
|
||||
* @param {boolean} options.allocations
|
||||
* Generates `totalAllocations` and `selfAllocations`.
|
||||
*
|
||||
* @return object
|
||||
* The computed { name, file, url, line } properties for this
|
||||
* function call, as well as additional params if options specified.
|
||||
*/
|
||||
getInfo: function(options) {
|
||||
return FrameUtils.getFrameInfo(this, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether or not the frame node has an JITOptimizations model.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
hasOptimizations: function() {
|
||||
return !this.isMetaCategory && !!this._optimizations;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the underlying JITOptimizations model representing
|
||||
* the optimization attempts occuring in this frame.
|
||||
*
|
||||
* @return {JITOptimizations|null}
|
||||
*/
|
||||
getOptimizations: function() {
|
||||
if (!this._optimizations) {
|
||||
return null;
|
||||
}
|
||||
return new JITOptimizations(this._optimizations, this._stringTable);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the tiers used overtime.
|
||||
*
|
||||
* @return {Array<object>}
|
||||
*/
|
||||
getTierData: function() {
|
||||
return this._tierData;
|
||||
},
|
||||
};
|
||||
|
||||
exports.ThreadNode = ThreadNode;
|
||||
exports.FrameNode = FrameNode;
|
@ -1,171 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Utility functions for collapsing markers into a waterfall.
|
||||
*/
|
||||
|
||||
const {
|
||||
MarkerBlueprintUtils,
|
||||
} = require("devtools/client/performance/modules/marker-blueprint-utils");
|
||||
|
||||
/**
|
||||
* Creates a parent marker, which functions like a regular marker,
|
||||
* but is able to hold additional child markers.
|
||||
*
|
||||
* The marker is seeded with values from `marker`.
|
||||
* @param object marker
|
||||
* @return object
|
||||
*/
|
||||
function createParentNode(marker) {
|
||||
return Object.assign({}, marker, { submarkers: [] });
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses markers into a tree-like structure.
|
||||
* @param object rootNode
|
||||
* @param array markersList
|
||||
* @param array filter
|
||||
*/
|
||||
function collapseMarkersIntoNode({ rootNode, markersList, filter }) {
|
||||
const {
|
||||
getCurrentParentNode,
|
||||
pushNode,
|
||||
popParentNode,
|
||||
} = createParentNodeFactory(rootNode);
|
||||
|
||||
for (let i = 0, len = markersList.length; i < len; i++) {
|
||||
const curr = markersList[i];
|
||||
|
||||
// If this marker type should not be displayed, just skip
|
||||
if (!MarkerBlueprintUtils.shouldDisplayMarker(curr, filter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let parentNode = getCurrentParentNode();
|
||||
const blueprint = MarkerBlueprintUtils.getBlueprintFor(curr);
|
||||
|
||||
const nestable = "nestable" in blueprint ? blueprint.nestable : true;
|
||||
const collapsible =
|
||||
"collapsible" in blueprint ? blueprint.collapsible : true;
|
||||
|
||||
let finalized = false;
|
||||
|
||||
// Extend the marker with extra properties needed in the marker tree
|
||||
const extendedProps = { index: i };
|
||||
if (collapsible) {
|
||||
extendedProps.submarkers = [];
|
||||
}
|
||||
Object.assign(curr, extendedProps);
|
||||
|
||||
// If not nestible, just push it inside the root node. Additionally,
|
||||
// markers originating outside the main thread are considered to be
|
||||
// "never collapsible", to avoid confusion.
|
||||
// A beter solution would be to collapse every marker with its siblings
|
||||
// from the same thread, but that would require a thread id attached
|
||||
// to all markers, which is potentially expensive and rather useless at
|
||||
// the moment, since we don't really have that many OTMT markers.
|
||||
if (!nestable || curr.isOffMainThread) {
|
||||
pushNode(rootNode, curr);
|
||||
continue;
|
||||
}
|
||||
|
||||
// First off, if any parent nodes exist, finish them off
|
||||
// recursively upwards if this marker is outside their ranges and nestable.
|
||||
while (!finalized && parentNode) {
|
||||
// If this marker is eclipsed by the current parent marker,
|
||||
// make it a child of the current parent and stop going upwards.
|
||||
// If the markers aren't from the same process, attach them to the root
|
||||
// node as well. Every process has its own main thread.
|
||||
if (
|
||||
nestable &&
|
||||
curr.start >= parentNode.start &&
|
||||
curr.end <= parentNode.end &&
|
||||
curr.processType == parentNode.processType
|
||||
) {
|
||||
pushNode(parentNode, curr);
|
||||
finalized = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If this marker is still nestable, but outside of the range
|
||||
// of the current parent, iterate upwards on the next parent
|
||||
// and finalize the current parent.
|
||||
if (nestable) {
|
||||
popParentNode();
|
||||
parentNode = getCurrentParentNode();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!finalized) {
|
||||
pushNode(rootNode, curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a root marker node and creates a hash of functions used
|
||||
* to manage the creation and nesting of additional parent markers.
|
||||
*
|
||||
* @param {object} root
|
||||
* @return {object}
|
||||
*/
|
||||
function createParentNodeFactory(root) {
|
||||
const parentMarkers = [];
|
||||
const factory = {
|
||||
/**
|
||||
* Pops the most recent parent node off the stack, finalizing it.
|
||||
* Sets the `end` time based on the most recent child if not defined.
|
||||
*/
|
||||
popParentNode: () => {
|
||||
if (parentMarkers.length === 0) {
|
||||
throw new Error("Cannot pop parent markers when none exist.");
|
||||
}
|
||||
|
||||
const lastParent = parentMarkers.pop();
|
||||
|
||||
// If this finished parent marker doesn't have an end time,
|
||||
// so probably a synthesized marker, use the last marker's end time.
|
||||
if (lastParent.end == void 0) {
|
||||
lastParent.end =
|
||||
lastParent.submarkers[lastParent.submarkers.length - 1].end;
|
||||
}
|
||||
|
||||
// If no children were ever pushed into this parent node,
|
||||
// remove its submarkers so it behaves like a non collapsible
|
||||
// node.
|
||||
if (!lastParent.submarkers.length) {
|
||||
delete lastParent.submarkers;
|
||||
}
|
||||
|
||||
return lastParent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the most recent parent node.
|
||||
*/
|
||||
getCurrentParentNode: () =>
|
||||
parentMarkers.length ? parentMarkers[parentMarkers.length - 1] : null,
|
||||
|
||||
/**
|
||||
* Push this marker into the most recent parent node.
|
||||
*/
|
||||
pushNode: (parent, marker) => {
|
||||
parent.submarkers.push(marker);
|
||||
|
||||
// If pushing a parent marker, track it as the top of
|
||||
// the parent stack.
|
||||
if (marker.submarkers) {
|
||||
parentMarkers.push(marker);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
exports.createParentNode = createParentNode;
|
||||
exports.collapseMarkersIntoNode = collapseMarkersIntoNode;
|
@ -1,110 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
TIMELINE_BLUEPRINT,
|
||||
} = require("devtools/client/performance/modules/markers");
|
||||
|
||||
/**
|
||||
* This file contains utilities for parsing out the markers blueprint
|
||||
* to generate strings to be displayed in the UI.
|
||||
*/
|
||||
|
||||
exports.MarkerBlueprintUtils = {
|
||||
/**
|
||||
* Takes a marker and a list of marker names that should be hidden, and
|
||||
* determines if this marker should be filtered or not.
|
||||
*
|
||||
* @param object marker
|
||||
* @return boolean
|
||||
*/
|
||||
shouldDisplayMarker: function(marker, hiddenMarkerNames) {
|
||||
if (!hiddenMarkerNames || hiddenMarkerNames.length == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If this marker isn't yet defined in the blueprint, simply check if the
|
||||
// entire category of "UNKNOWN" markers are supposed to be visible or not.
|
||||
const isUnknown = !(marker.name in TIMELINE_BLUEPRINT);
|
||||
if (isUnknown) {
|
||||
return !hiddenMarkerNames.includes("UNKNOWN");
|
||||
}
|
||||
|
||||
return !hiddenMarkerNames.includes(marker.name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a marker and returns the blueprint definition for that marker type,
|
||||
* falling back to the UNKNOWN blueprint definition if undefined.
|
||||
*
|
||||
* @param object marker
|
||||
* @return object
|
||||
*/
|
||||
getBlueprintFor: function(marker) {
|
||||
return TIMELINE_BLUEPRINT[marker.name] || TIMELINE_BLUEPRINT.UNKNOWN;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the label to display for a marker, based off the blueprints.
|
||||
*
|
||||
* @param object marker
|
||||
* @return string
|
||||
*/
|
||||
getMarkerLabel: function(marker) {
|
||||
const blueprint = this.getBlueprintFor(marker);
|
||||
const dynamic = typeof blueprint.label === "function";
|
||||
const label = dynamic ? blueprint.label(marker) : blueprint.label;
|
||||
return label;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the generic label to display for a marker name.
|
||||
* (e.g. "Function Call" for JS markers, rather than "setTimeout", etc.)
|
||||
*
|
||||
* @param string type
|
||||
* @return string
|
||||
*/
|
||||
getMarkerGenericName: function(markerName) {
|
||||
const blueprint = this.getBlueprintFor({ name: markerName });
|
||||
const dynamic = typeof blueprint.label === "function";
|
||||
const generic = dynamic ? blueprint.label() : blueprint.label;
|
||||
|
||||
// If no class name found, attempt to throw a descriptive error as to
|
||||
// how the marker implementor can fix this.
|
||||
if (!generic) {
|
||||
let message = `Could not find marker generic name for "${markerName}".`;
|
||||
if (typeof blueprint.label === "function") {
|
||||
message +=
|
||||
` The following function must return a generic name string when no` +
|
||||
` marker passed: ${blueprint.label}`;
|
||||
} else {
|
||||
message += ` ${markerName}.label must be defined in the marker blueprint.`;
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return generic;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an array of objects with key/value pairs of what should be rendered
|
||||
* in the marker details view.
|
||||
*
|
||||
* @param object marker
|
||||
* @return array<object>
|
||||
*/
|
||||
getMarkerFields: function(marker) {
|
||||
const blueprint = this.getBlueprintFor(marker);
|
||||
const dynamic = typeof blueprint.fields === "function";
|
||||
const fields = dynamic ? blueprint.fields(marker) : blueprint.fields;
|
||||
|
||||
return Object.entries(fields || {})
|
||||
.filter(([_, value]) => (dynamic ? true : value in marker))
|
||||
.map(([label, value]) => ({
|
||||
label,
|
||||
value: dynamic ? value : marker[value],
|
||||
}));
|
||||
},
|
||||
};
|
@ -1,275 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains utilities for creating DOM nodes for markers
|
||||
* to be displayed in the UI.
|
||||
*/
|
||||
|
||||
const { L10N, PREFS } = require("devtools/client/performance/modules/global");
|
||||
const {
|
||||
MarkerBlueprintUtils,
|
||||
} = require("devtools/client/performance/modules/marker-blueprint-utils");
|
||||
const { getSourceNames } = require("devtools/client/shared/source-utils");
|
||||
|
||||
/**
|
||||
* Utilites for creating elements for markers.
|
||||
*/
|
||||
exports.MarkerDOMUtils = {
|
||||
/**
|
||||
* Builds all the fields possible for the given marker. Returns an
|
||||
* array of elements to be appended to a parent element.
|
||||
*
|
||||
* @param document doc
|
||||
* @param object marker
|
||||
* @return array<Node>
|
||||
*/
|
||||
buildFields: function(doc, marker) {
|
||||
const fields = MarkerBlueprintUtils.getMarkerFields(marker);
|
||||
return fields.map(({ label, value }) =>
|
||||
this.buildNameValueLabel(doc, label, value)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds the label representing the marker's type.
|
||||
*
|
||||
* @param document doc
|
||||
* @param object marker
|
||||
* @return Node
|
||||
*/
|
||||
buildTitle: function(doc, marker) {
|
||||
const blueprint = MarkerBlueprintUtils.getBlueprintFor(marker);
|
||||
|
||||
const hbox = doc.createXULElement("hbox");
|
||||
hbox.setAttribute("align", "center");
|
||||
|
||||
const bullet = doc.createXULElement("hbox");
|
||||
bullet.className = `marker-details-bullet marker-color-${blueprint.colorName}`;
|
||||
|
||||
const title = MarkerBlueprintUtils.getMarkerLabel(marker);
|
||||
const label = doc.createXULElement("label");
|
||||
label.className = "marker-details-type";
|
||||
label.setAttribute("value", title);
|
||||
|
||||
hbox.appendChild(bullet);
|
||||
hbox.appendChild(label);
|
||||
|
||||
return hbox;
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds the label representing the marker's duration.
|
||||
*
|
||||
* @param document doc
|
||||
* @param object marker
|
||||
* @return Node
|
||||
*/
|
||||
buildDuration: function(doc, marker) {
|
||||
const label = L10N.getStr("marker.field.duration");
|
||||
const start = L10N.getFormatStrWithNumbers("timeline.tick", marker.start);
|
||||
const end = L10N.getFormatStrWithNumbers("timeline.tick", marker.end);
|
||||
const duration = L10N.getFormatStrWithNumbers(
|
||||
"timeline.tick",
|
||||
marker.end - marker.start
|
||||
);
|
||||
|
||||
const el = this.buildNameValueLabel(doc, label, duration);
|
||||
el.classList.add("marker-details-duration");
|
||||
el.setAttribute("tooltiptext", `${start} → ${end}`);
|
||||
|
||||
return el;
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds labels for name:value pairs.
|
||||
* E.g. "Start: 100ms", "Duration: 200ms", ...
|
||||
*
|
||||
* @param document doc
|
||||
* @param string field
|
||||
* @param string value
|
||||
* @return Node
|
||||
*/
|
||||
buildNameValueLabel: function(doc, field, value) {
|
||||
const hbox = doc.createXULElement("hbox");
|
||||
hbox.className = "marker-details-labelcontainer";
|
||||
|
||||
const nameLabel = doc.createXULElement("label");
|
||||
nameLabel.className = "plain marker-details-name-label";
|
||||
nameLabel.setAttribute("value", field);
|
||||
hbox.appendChild(nameLabel);
|
||||
|
||||
const valueLabel = doc.createXULElement("label");
|
||||
valueLabel.className = "plain marker-details-value-label";
|
||||
valueLabel.setAttribute("value", value);
|
||||
hbox.appendChild(valueLabel);
|
||||
|
||||
return hbox;
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds a stack trace in an element.
|
||||
*
|
||||
* @param document doc
|
||||
* @param object params
|
||||
* An options object with the following members:
|
||||
* - string type: string identifier for type of stack ("stack", "startStack"
|
||||
or "endStack"
|
||||
* - number frameIndex: the index of the topmost stack frame
|
||||
* - array frames: array of stack frames
|
||||
*/
|
||||
buildStackTrace: function(doc, { type, frameIndex, frames }) {
|
||||
const container = doc.createXULElement("vbox");
|
||||
container.className = "marker-details-stack";
|
||||
container.setAttribute("type", type);
|
||||
|
||||
const nameLabel = doc.createXULElement("label");
|
||||
nameLabel.className = "plain marker-details-name-label";
|
||||
nameLabel.setAttribute("value", L10N.getStr(`marker.field.${type}`));
|
||||
container.appendChild(nameLabel);
|
||||
|
||||
// Workaround for profiles that have looping stack traces. See
|
||||
// bug 1246555.
|
||||
let wasAsyncParent = false;
|
||||
const seen = new Set();
|
||||
|
||||
while (frameIndex > 0) {
|
||||
if (seen.has(frameIndex)) {
|
||||
break;
|
||||
}
|
||||
seen.add(frameIndex);
|
||||
|
||||
const frame = frames[frameIndex];
|
||||
const url = frame.source;
|
||||
const displayName = frame.functionDisplayName;
|
||||
const line = frame.line;
|
||||
|
||||
// If the previous frame had an async parent, then the async
|
||||
// cause is in this frame and should be displayed.
|
||||
if (wasAsyncParent) {
|
||||
const asyncStr = L10N.getFormatStr(
|
||||
"marker.field.asyncStack",
|
||||
frame.asyncCause
|
||||
);
|
||||
const asyncBox = doc.createXULElement("hbox");
|
||||
const asyncLabel = doc.createXULElement("label");
|
||||
asyncLabel.className = "devtools-monospace";
|
||||
asyncLabel.setAttribute("value", asyncStr);
|
||||
asyncBox.appendChild(asyncLabel);
|
||||
container.appendChild(asyncBox);
|
||||
wasAsyncParent = false;
|
||||
}
|
||||
|
||||
const hbox = doc.createXULElement("hbox");
|
||||
|
||||
if (displayName) {
|
||||
const functionLabel = doc.createXULElement("label");
|
||||
functionLabel.className = "devtools-monospace";
|
||||
functionLabel.setAttribute("value", displayName);
|
||||
hbox.appendChild(functionLabel);
|
||||
}
|
||||
|
||||
if (url) {
|
||||
const linkNode = doc.createXULElement("a");
|
||||
linkNode.className = "waterfall-marker-location devtools-source-link";
|
||||
linkNode.href = url;
|
||||
linkNode.draggable = false;
|
||||
linkNode.setAttribute("title", url);
|
||||
|
||||
const urlLabel = doc.createXULElement("label");
|
||||
urlLabel.className = "filename";
|
||||
urlLabel.setAttribute("value", getSourceNames(url).short);
|
||||
linkNode.appendChild(urlLabel);
|
||||
|
||||
const lineLabel = doc.createXULElement("label");
|
||||
lineLabel.className = "line-number";
|
||||
lineLabel.setAttribute("value", `:${line}`);
|
||||
linkNode.appendChild(lineLabel);
|
||||
|
||||
hbox.appendChild(linkNode);
|
||||
|
||||
// Clicking here will bubble up to the parent,
|
||||
// which handles the view source.
|
||||
linkNode.setAttribute(
|
||||
"data-action",
|
||||
JSON.stringify({
|
||||
url: url,
|
||||
line: line,
|
||||
action: "view-source",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (!displayName && !url) {
|
||||
const unknownLabel = doc.createXULElement("label");
|
||||
unknownLabel.setAttribute(
|
||||
"value",
|
||||
L10N.getStr("marker.value.unknownFrame")
|
||||
);
|
||||
hbox.appendChild(unknownLabel);
|
||||
}
|
||||
|
||||
container.appendChild(hbox);
|
||||
|
||||
if (frame.asyncParent) {
|
||||
frameIndex = frame.asyncParent;
|
||||
wasAsyncParent = true;
|
||||
} else {
|
||||
frameIndex = frame.parent;
|
||||
}
|
||||
}
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds any custom fields specific to the marker.
|
||||
*
|
||||
* @param document doc
|
||||
* @param object marker
|
||||
* @param object options
|
||||
* @return array<Node>
|
||||
*/
|
||||
buildCustom: function(doc, marker, options) {
|
||||
const elements = [];
|
||||
|
||||
if (options.allocations && shouldShowAllocationsTrigger(marker)) {
|
||||
const hbox = doc.createXULElement("hbox");
|
||||
hbox.className = "marker-details-customcontainer";
|
||||
|
||||
const label = doc.createXULElement("label");
|
||||
label.className = "custom-button";
|
||||
label.setAttribute("value", "Show allocation triggers");
|
||||
label.setAttribute("type", "show-allocations");
|
||||
label.setAttribute(
|
||||
"data-action",
|
||||
JSON.stringify({
|
||||
endTime: marker.start,
|
||||
action: "show-allocations",
|
||||
})
|
||||
);
|
||||
|
||||
hbox.appendChild(label);
|
||||
elements.push(hbox);
|
||||
}
|
||||
|
||||
return elements;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a marker and determines if this marker should display
|
||||
* the allocations trigger button.
|
||||
*
|
||||
* @param object marker
|
||||
* @return boolean
|
||||
*/
|
||||
function shouldShowAllocationsTrigger(marker) {
|
||||
if (marker.name == "GarbageCollection") {
|
||||
const showTriggers = PREFS["show-triggers-for-gc-types"];
|
||||
return showTriggers.split(" ").includes(marker.causeName);
|
||||
}
|
||||
return false;
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains utilities for creating elements for markers to be displayed,
|
||||
* and parsing out the blueprint to generate correct values for markers.
|
||||
*/
|
||||
const { L10N, PREFS } = require("devtools/client/performance/modules/global");
|
||||
|
||||
// String used to fill in platform data when it should be hidden.
|
||||
const GECKO_SYMBOL = "(Gecko)";
|
||||
|
||||
/**
|
||||
* Mapping of JS marker causes to a friendlier form. Only
|
||||
* markers that are considered "from content" should be labeled here.
|
||||
*/
|
||||
const JS_MARKER_MAP = {
|
||||
"<script> element": L10N.getStr("marker.label.javascript.scriptElement"),
|
||||
"promise callback": L10N.getStr("marker.label.javascript.promiseCallback"),
|
||||
"promise initializer": L10N.getStr("marker.label.javascript.promiseInit"),
|
||||
"Worker runnable": L10N.getStr("marker.label.javascript.workerRunnable"),
|
||||
"javascript: URI": L10N.getStr("marker.label.javascript.jsURI"),
|
||||
// The difference between these two event handler markers are differences
|
||||
// in their WebIDL implementation, so distinguishing them is not necessary.
|
||||
EventHandlerNonNull: L10N.getStr("marker.label.javascript.eventHandler"),
|
||||
"EventListener.handleEvent": L10N.getStr(
|
||||
"marker.label.javascript.eventHandler"
|
||||
),
|
||||
// These markers do not get L10N'd because they're JS names.
|
||||
"setInterval handler": "setInterval",
|
||||
"setTimeout handler": "setTimeout",
|
||||
FrameRequestCallback: "requestAnimationFrame",
|
||||
};
|
||||
|
||||
/**
|
||||
* A series of formatters used by the blueprint.
|
||||
*/
|
||||
exports.Formatters = {
|
||||
/**
|
||||
* Uses the marker name as the label for markers that do not have
|
||||
* a blueprint entry. Uses "Other" in the marker filter menu.
|
||||
*/
|
||||
UnknownLabel: function(marker = {}) {
|
||||
return marker.name || L10N.getStr("marker.label.unknown");
|
||||
},
|
||||
|
||||
/* Group 0 - Reflow and Rendering pipeline */
|
||||
|
||||
StylesFields: function(marker) {
|
||||
if ("isAnimationOnly" in marker) {
|
||||
return {
|
||||
[L10N.getStr("marker.field.isAnimationOnly")]: marker.isAnimationOnly,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/* Group 1 - JS */
|
||||
|
||||
DOMEventFields: function(marker) {
|
||||
const fields = Object.create(null);
|
||||
|
||||
if ("type" in marker) {
|
||||
fields[L10N.getStr("marker.field.DOMEventType")] = marker.type;
|
||||
}
|
||||
|
||||
if ("eventPhase" in marker) {
|
||||
let label;
|
||||
switch (marker.eventPhase) {
|
||||
case Event.AT_TARGET:
|
||||
label = L10N.getStr("marker.value.DOMEventTargetPhase");
|
||||
break;
|
||||
case Event.CAPTURING_PHASE:
|
||||
label = L10N.getStr("marker.value.DOMEventCapturingPhase");
|
||||
break;
|
||||
case Event.BUBBLING_PHASE:
|
||||
label = L10N.getStr("marker.value.DOMEventBubblingPhase");
|
||||
break;
|
||||
}
|
||||
fields[L10N.getStr("marker.field.DOMEventPhase")] = label;
|
||||
}
|
||||
|
||||
return fields;
|
||||
},
|
||||
|
||||
JSLabel: function(marker = {}) {
|
||||
const generic = L10N.getStr("marker.label.javascript");
|
||||
if ("causeName" in marker) {
|
||||
return JS_MARKER_MAP[marker.causeName] || generic;
|
||||
}
|
||||
return generic;
|
||||
},
|
||||
|
||||
JSFields: function(marker) {
|
||||
if ("causeName" in marker && !JS_MARKER_MAP[marker.causeName]) {
|
||||
const label = PREFS["show-platform-data"]
|
||||
? marker.causeName
|
||||
: GECKO_SYMBOL;
|
||||
return {
|
||||
[L10N.getStr("marker.field.causeName")]: label,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
GCLabel: function(marker) {
|
||||
if (!marker) {
|
||||
return L10N.getStr("marker.label.garbageCollection2");
|
||||
}
|
||||
// Only if a `nonincrementalReason` exists, do we want to label
|
||||
// this as a non incremental GC event.
|
||||
if ("nonincrementalReason" in marker) {
|
||||
return L10N.getStr("marker.label.garbageCollection.nonIncremental");
|
||||
}
|
||||
return L10N.getStr("marker.label.garbageCollection.incremental");
|
||||
},
|
||||
|
||||
GCFields: function(marker) {
|
||||
const fields = Object.create(null);
|
||||
|
||||
if ("causeName" in marker) {
|
||||
const cause = marker.causeName;
|
||||
const label = L10N.getStr(`marker.gcreason.label.${cause}`) || cause;
|
||||
fields[L10N.getStr("marker.field.causeName")] = label;
|
||||
}
|
||||
|
||||
if ("nonincrementalReason" in marker) {
|
||||
const label = marker.nonincrementalReason;
|
||||
fields[L10N.getStr("marker.field.nonIncrementalCause")] = label;
|
||||
}
|
||||
|
||||
return fields;
|
||||
},
|
||||
|
||||
MinorGCFields: function(marker) {
|
||||
const fields = Object.create(null);
|
||||
|
||||
if ("causeName" in marker) {
|
||||
const cause = marker.causeName;
|
||||
const label = L10N.getStr(`marker.gcreason.label.${cause}`) || cause;
|
||||
fields[L10N.getStr("marker.field.causeName")] = label;
|
||||
}
|
||||
|
||||
fields[L10N.getStr("marker.field.type")] = L10N.getStr(
|
||||
"marker.nurseryCollection"
|
||||
);
|
||||
|
||||
return fields;
|
||||
},
|
||||
|
||||
CycleCollectionFields: function(marker) {
|
||||
const label = marker.name.replace(/nsCycleCollector::/g, "");
|
||||
return {
|
||||
[L10N.getStr("marker.field.type")]: label,
|
||||
};
|
||||
},
|
||||
|
||||
WorkerFields: function(marker) {
|
||||
if ("workerOperation" in marker) {
|
||||
const label = L10N.getStr(`marker.worker.${marker.workerOperation}`);
|
||||
return {
|
||||
[L10N.getStr("marker.field.type")]: label,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
MessagePortFields: function(marker) {
|
||||
if ("messagePortOperation" in marker) {
|
||||
const label = L10N.getStr(
|
||||
`marker.messagePort.${marker.messagePortOperation}`
|
||||
);
|
||||
return {
|
||||
[L10N.getStr("marker.field.type")]: label,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/* Group 2 - User Controlled */
|
||||
|
||||
ConsoleTimeFields: {
|
||||
[L10N.getStr("marker.field.consoleTimerName")]: "causeName",
|
||||
},
|
||||
|
||||
TimeStampFields: {
|
||||
[L10N.getStr("marker.field.label")]: "causeName",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a main label (e.g. "Timestamp") and a property name (e.g. "causeName"),
|
||||
* and returns a string that represents that property value for a marker if it
|
||||
* exists (e.g. "Timestamp (rendering)"), or just the main label if it does not.
|
||||
*
|
||||
* @param string mainLabel
|
||||
* @param string propName
|
||||
*/
|
||||
exports.Formatters.labelForProperty = function(mainLabel, propName) {
|
||||
return (marker = {}) =>
|
||||
marker[propName] ? `${mainLabel} (${marker[propName]})` : mainLabel;
|
||||
};
|
@ -1,180 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { L10N } = require("devtools/client/performance/modules/global");
|
||||
const {
|
||||
Formatters,
|
||||
} = require("devtools/client/performance/modules/marker-formatters");
|
||||
|
||||
/**
|
||||
* A simple schema for mapping markers to the timeline UI. The keys correspond
|
||||
* to marker names, while the values are objects with the following format:
|
||||
*
|
||||
* - group: The row index in the overview graph; multiple markers
|
||||
* can be added on the same row. @see <overview.js/buildGraphImage>
|
||||
* - label: The label used in the waterfall to identify the marker. Can be a
|
||||
* string or just a function that accepts the marker and returns a
|
||||
* string (if you want to use a dynamic property for the main label).
|
||||
* If you use a function for a label, it *must* handle the case where
|
||||
* no marker is provided, to get a generic label used to describe
|
||||
* all markers of this type.
|
||||
* - fields: The fields used in the marker details view to display more
|
||||
* information about a currently selected marker. Can either be an
|
||||
* object of fields, or simply a function that accepts the marker and
|
||||
* returns such an object (if you want to use properties dynamically).
|
||||
* For example, a field in the object such as { "Cause": "causeName" }
|
||||
* would render something like `Cause: ${marker.causeName}` in the UI.
|
||||
* - colorName: The label of the DevTools color used for this marker. If
|
||||
* adding a new color, be sure to check that there's an entry
|
||||
* for `.marker-color-graphs-{COLORNAME}` for the equivilent
|
||||
* entry in "./devtools/client/themes/performance.css"
|
||||
* - collapsible: Whether or not this marker can contain other markers it
|
||||
* eclipses, and becomes collapsible to reveal its nestable
|
||||
* children. Defaults to true.
|
||||
* - nestable: Whether or not this marker can be nested inside an eclipsing
|
||||
* collapsible marker. Defaults to true.
|
||||
*/
|
||||
const TIMELINE_BLUEPRINT = {
|
||||
/* Default definition used for markers that occur but are not defined here.
|
||||
* Should ultimately be defined, but this gives us room to work on the
|
||||
* front end separately from the platform. */
|
||||
UNKNOWN: {
|
||||
group: 2,
|
||||
colorName: "graphs-grey",
|
||||
label: Formatters.UnknownLabel,
|
||||
},
|
||||
|
||||
/* Group 0 - Reflow and Rendering pipeline */
|
||||
|
||||
Styles: {
|
||||
group: 0,
|
||||
colorName: "graphs-purple",
|
||||
label: L10N.getStr("marker.label.styles"),
|
||||
fields: Formatters.StylesFields,
|
||||
},
|
||||
StylesApplyChanges: {
|
||||
group: 0,
|
||||
colorName: "graphs-purple",
|
||||
label: L10N.getStr("marker.label.stylesApplyChanges"),
|
||||
},
|
||||
Reflow: {
|
||||
group: 0,
|
||||
colorName: "graphs-purple",
|
||||
label: L10N.getStr("marker.label.reflow"),
|
||||
},
|
||||
Paint: {
|
||||
group: 0,
|
||||
colorName: "graphs-green",
|
||||
label: L10N.getStr("marker.label.paint"),
|
||||
},
|
||||
Composite: {
|
||||
group: 0,
|
||||
colorName: "graphs-green",
|
||||
label: L10N.getStr("marker.label.composite"),
|
||||
},
|
||||
CompositeForwardTransaction: {
|
||||
group: 0,
|
||||
colorName: "graphs-bluegrey",
|
||||
label: L10N.getStr("marker.label.compositeForwardTransaction"),
|
||||
},
|
||||
|
||||
/* Group 1 - JS */
|
||||
|
||||
DOMEvent: {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
label: L10N.getStr("marker.label.domevent"),
|
||||
fields: Formatters.DOMEventFields,
|
||||
},
|
||||
"document::DOMContentLoaded": {
|
||||
group: 1,
|
||||
colorName: "graphs-full-red",
|
||||
label: "DOMContentLoaded",
|
||||
},
|
||||
"document::Load": {
|
||||
group: 1,
|
||||
colorName: "graphs-full-blue",
|
||||
label: "Load",
|
||||
},
|
||||
Javascript: {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
label: Formatters.JSLabel,
|
||||
fields: Formatters.JSFields,
|
||||
},
|
||||
"Parse HTML": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
label: L10N.getStr("marker.label.parseHTML"),
|
||||
},
|
||||
"Parse XML": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
label: L10N.getStr("marker.label.parseXML"),
|
||||
},
|
||||
GarbageCollection: {
|
||||
group: 1,
|
||||
colorName: "graphs-red",
|
||||
label: Formatters.GCLabel,
|
||||
fields: Formatters.GCFields,
|
||||
},
|
||||
MinorGC: {
|
||||
group: 1,
|
||||
colorName: "graphs-red",
|
||||
label: L10N.getStr("marker.label.minorGC"),
|
||||
fields: Formatters.MinorGCFields,
|
||||
},
|
||||
"nsCycleCollector::Collect": {
|
||||
group: 1,
|
||||
colorName: "graphs-red",
|
||||
label: L10N.getStr("marker.label.cycleCollection"),
|
||||
fields: Formatters.CycleCollectionFields,
|
||||
},
|
||||
"nsCycleCollector::ForgetSkippable": {
|
||||
group: 1,
|
||||
colorName: "graphs-red",
|
||||
label: L10N.getStr("marker.label.cycleCollection.forgetSkippable"),
|
||||
fields: Formatters.CycleCollectionFields,
|
||||
},
|
||||
Worker: {
|
||||
group: 1,
|
||||
colorName: "graphs-orange",
|
||||
label: L10N.getStr("marker.label.worker"),
|
||||
fields: Formatters.WorkerFields,
|
||||
},
|
||||
MessagePort: {
|
||||
group: 1,
|
||||
colorName: "graphs-orange",
|
||||
label: L10N.getStr("marker.label.messagePort"),
|
||||
fields: Formatters.MessagePortFields,
|
||||
},
|
||||
|
||||
/* Group 2 - User Controlled */
|
||||
|
||||
ConsoleTime: {
|
||||
group: 2,
|
||||
colorName: "graphs-blue",
|
||||
label: Formatters.labelForProperty(
|
||||
L10N.getStr("marker.label.consoleTime"),
|
||||
"causeName"
|
||||
),
|
||||
fields: Formatters.ConsoleTimeFields,
|
||||
nestable: false,
|
||||
collapsible: false,
|
||||
},
|
||||
TimeStamp: {
|
||||
group: 2,
|
||||
colorName: "graphs-blue",
|
||||
label: Formatters.labelForProperty(
|
||||
L10N.getStr("marker.label.timestamp"),
|
||||
"causeName"
|
||||
),
|
||||
fields: Formatters.TimeStampFields,
|
||||
collapsible: false,
|
||||
},
|
||||
};
|
||||
|
||||
// Exported symbols.
|
||||
exports.TIMELINE_BLUEPRINT = TIMELINE_BLUEPRINT;
|
@ -1,22 +0,0 @@
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += [
|
||||
"logic",
|
||||
"widgets",
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
"categories.js",
|
||||
"constants.js",
|
||||
"global.js",
|
||||
"io.js",
|
||||
"marker-blueprint-utils.js",
|
||||
"marker-dom-utils.js",
|
||||
"marker-formatters.js",
|
||||
"markers.js",
|
||||
"utils.js",
|
||||
"waterfall-ticks.js",
|
||||
)
|
@ -1,24 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* globals document */
|
||||
|
||||
/**
|
||||
* React components grab the namespace of the element they are mounting to. This function
|
||||
* takes a XUL element, and makes sure to create a properly namespaced HTML element to
|
||||
* avoid React creating XUL elements.
|
||||
*
|
||||
* {XULElement} xulElement
|
||||
* return {HTMLElement} div
|
||||
*/
|
||||
|
||||
exports.createHtmlMount = function(xulElement) {
|
||||
const htmlElement = document.createElementNS(
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"div"
|
||||
);
|
||||
xulElement.appendChild(htmlElement);
|
||||
return htmlElement;
|
||||
};
|
@ -1,98 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_BACKGROUND_TICKS_SCALES = 3;
|
||||
const WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
|
||||
const WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
|
||||
|
||||
const FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS = 100;
|
||||
|
||||
/**
|
||||
* Creates the background displayed on the marker's waterfall.
|
||||
*/
|
||||
function drawWaterfallBackground(doc, dataScale, waterfallWidth) {
|
||||
const canvas = doc.createElementNS(HTML_NS, "canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
// Nuke the context.
|
||||
const canvasWidth = (canvas.width = Math.max(waterfallWidth, 1));
|
||||
// Awww yeah, 1px, repeats on Y axis.
|
||||
const canvasHeight = (canvas.height = 1);
|
||||
|
||||
// Start over.
|
||||
const imageData = ctx.createImageData(canvasWidth, canvasHeight);
|
||||
const pixelArray = imageData.data;
|
||||
|
||||
const buf = new ArrayBuffer(pixelArray.length);
|
||||
const view8bit = new Uint8ClampedArray(buf);
|
||||
const view32bit = new Uint32Array(buf);
|
||||
|
||||
// Build new millisecond tick lines...
|
||||
const [r, g, b] = WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
|
||||
let alphaComponent = WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
|
||||
const tickInterval = findOptimalTickInterval({
|
||||
ticksMultiple: WATERFALL_BACKGROUND_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: WATERFALL_BACKGROUND_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale,
|
||||
});
|
||||
|
||||
// Insert one pixel for each division on each scale.
|
||||
for (let i = 1; i <= WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
|
||||
const increment = tickInterval * Math.pow(2, i);
|
||||
for (let x = 0; x < canvasWidth; x += increment) {
|
||||
const position = x | 0;
|
||||
view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
alphaComponent += WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
|
||||
}
|
||||
|
||||
// Flush the image data and cache the waterfall background.
|
||||
pixelArray.set(view8bit);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
doc.mozSetImageElement("waterfall-background", canvas);
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the optimal tick interval between time markers in this timeline.
|
||||
*
|
||||
* @param number ticksMultiple
|
||||
* @param number ticksSpacingMin
|
||||
* @param number dataScale
|
||||
* @return number
|
||||
*/
|
||||
function findOptimalTickInterval({
|
||||
ticksMultiple,
|
||||
ticksSpacingMin,
|
||||
dataScale,
|
||||
}) {
|
||||
let timingStep = ticksMultiple;
|
||||
const maxIters = FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS;
|
||||
let numIters = 0;
|
||||
|
||||
if (dataScale > ticksSpacingMin) {
|
||||
return dataScale;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const scaledStep = dataScale * timingStep;
|
||||
if (++numIters > maxIters) {
|
||||
return scaledStep;
|
||||
}
|
||||
if (scaledStep < ticksSpacingMin) {
|
||||
timingStep <<= 1;
|
||||
continue;
|
||||
}
|
||||
return scaledStep;
|
||||
}
|
||||
}
|
||||
|
||||
exports.TickUtils = { findOptimalTickInterval, drawWaterfallBackground };
|
@ -1,527 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the base line graph that all Performance line graphs use.
|
||||
*/
|
||||
|
||||
const { extend } = require("devtools/shared/extend");
|
||||
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
|
||||
const MountainGraphWidget = require("devtools/client/shared/widgets/MountainGraphWidget");
|
||||
const { CanvasGraphUtils } = require("devtools/client/shared/widgets/Graphs");
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const { colorUtils } = require("devtools/shared/css/color");
|
||||
const { getColor } = require("devtools/client/shared/theme");
|
||||
const ProfilerGlobal = require("devtools/client/performance/modules/global");
|
||||
const {
|
||||
MarkersOverview,
|
||||
} = require("devtools/client/performance/modules/widgets/markers-overview");
|
||||
const {
|
||||
createTierGraphDataFromFrameNode,
|
||||
} = require("devtools/client/performance/modules/logic/jit");
|
||||
|
||||
/**
|
||||
* For line graphs
|
||||
*/
|
||||
const HEIGHT = 35; // px
|
||||
const STROKE_WIDTH = 1; // px
|
||||
const DAMPEN_VALUES = 0.95;
|
||||
const CLIPHEAD_LINE_COLOR = "#666";
|
||||
const SELECTION_LINE_COLOR = "#555";
|
||||
const SELECTION_BACKGROUND_COLOR_NAME = "graphs-blue";
|
||||
const FRAMERATE_GRAPH_COLOR_NAME = "graphs-green";
|
||||
const MEMORY_GRAPH_COLOR_NAME = "graphs-blue";
|
||||
|
||||
/**
|
||||
* For timeline overview
|
||||
*/
|
||||
const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
|
||||
const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
|
||||
const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
|
||||
|
||||
/**
|
||||
* For optimization graph
|
||||
*/
|
||||
const OPTIMIZATIONS_GRAPH_RESOLUTION = 100;
|
||||
|
||||
/**
|
||||
* A base class for performance graphs to inherit from.
|
||||
*
|
||||
* @param Node parent
|
||||
* The parent node holding the overview.
|
||||
* @param string metric
|
||||
* The unit of measurement for this graph.
|
||||
*/
|
||||
function PerformanceGraph(parent, metric) {
|
||||
LineGraphWidget.call(this, parent, { metric });
|
||||
this.setTheme();
|
||||
}
|
||||
|
||||
PerformanceGraph.prototype = extend(LineGraphWidget.prototype, {
|
||||
strokeWidth: STROKE_WIDTH,
|
||||
dampenValuesFactor: DAMPEN_VALUES,
|
||||
fixedHeight: HEIGHT,
|
||||
clipheadLineColor: CLIPHEAD_LINE_COLOR,
|
||||
selectionLineColor: SELECTION_LINE_COLOR,
|
||||
withTooltipArrows: false,
|
||||
withFixedTooltipPositions: true,
|
||||
|
||||
/**
|
||||
* Disables selection and empties this graph.
|
||||
*/
|
||||
clearView: function() {
|
||||
this.selectionEnabled = false;
|
||||
this.dropSelection();
|
||||
this.setData([]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the theme via `theme` to either "light" or "dark",
|
||||
* and updates the internal styling to match. Requires a redraw
|
||||
* to see the effects.
|
||||
*/
|
||||
setTheme: function(theme) {
|
||||
theme = theme || "light";
|
||||
const mainColor = getColor(this.mainColor || "graphs-blue", theme);
|
||||
this.backgroundColor = getColor("body-background", theme);
|
||||
this.strokeColor = mainColor;
|
||||
this.backgroundGradientStart = colorUtils.setAlpha(mainColor, 0.2);
|
||||
this.backgroundGradientEnd = colorUtils.setAlpha(mainColor, 0.2);
|
||||
this.selectionBackgroundColor = colorUtils.setAlpha(
|
||||
getColor(SELECTION_BACKGROUND_COLOR_NAME, theme),
|
||||
0.25
|
||||
);
|
||||
this.selectionStripesColor = "rgba(255, 255, 255, 0.1)";
|
||||
this.maximumLineColor = colorUtils.setAlpha(mainColor, 0.4);
|
||||
this.averageLineColor = colorUtils.setAlpha(mainColor, 0.7);
|
||||
this.minimumLineColor = colorUtils.setAlpha(mainColor, 0.9);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Constructor for the framerate graph. Inherits from PerformanceGraph.
|
||||
*
|
||||
* @param Node parent
|
||||
* The parent node holding the overview.
|
||||
*/
|
||||
function FramerateGraph(parent) {
|
||||
PerformanceGraph.call(this, parent, ProfilerGlobal.L10N.getStr("graphs.fps"));
|
||||
}
|
||||
|
||||
FramerateGraph.prototype = extend(PerformanceGraph.prototype, {
|
||||
mainColor: FRAMERATE_GRAPH_COLOR_NAME,
|
||||
setPerformanceData: function({ duration, ticks }, resolution) {
|
||||
this.dataDuration = duration;
|
||||
return this.setDataFromTimestamps(ticks, resolution, duration);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Constructor for the memory graph. Inherits from PerformanceGraph.
|
||||
*
|
||||
* @param Node parent
|
||||
* The parent node holding the overview.
|
||||
*/
|
||||
function MemoryGraph(parent) {
|
||||
PerformanceGraph.call(
|
||||
this,
|
||||
parent,
|
||||
ProfilerGlobal.L10N.getStr("graphs.memory")
|
||||
);
|
||||
}
|
||||
|
||||
MemoryGraph.prototype = extend(PerformanceGraph.prototype, {
|
||||
mainColor: MEMORY_GRAPH_COLOR_NAME,
|
||||
setPerformanceData: function({ duration, memory }) {
|
||||
this.dataDuration = duration;
|
||||
return this.setData(memory);
|
||||
},
|
||||
});
|
||||
|
||||
function TimelineGraph(parent, filter) {
|
||||
MarkersOverview.call(this, parent, filter);
|
||||
}
|
||||
|
||||
TimelineGraph.prototype = extend(MarkersOverview.prototype, {
|
||||
headerHeight: MARKERS_GRAPH_HEADER_HEIGHT,
|
||||
rowHeight: MARKERS_GRAPH_ROW_HEIGHT,
|
||||
groupPadding: MARKERS_GROUP_VERTICAL_PADDING,
|
||||
setPerformanceData: MarkersOverview.prototype.setData,
|
||||
});
|
||||
|
||||
/**
|
||||
* Definitions file for GraphsController, indicating the constructor,
|
||||
* selector and other meta for each of the graphs controller by
|
||||
* GraphsController.
|
||||
*/
|
||||
const GRAPH_DEFINITIONS = {
|
||||
memory: {
|
||||
constructor: MemoryGraph,
|
||||
selector: "#memory-overview",
|
||||
},
|
||||
framerate: {
|
||||
constructor: FramerateGraph,
|
||||
selector: "#time-framerate",
|
||||
},
|
||||
timeline: {
|
||||
constructor: TimelineGraph,
|
||||
selector: "#markers-overview",
|
||||
primaryLink: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A controller for orchestrating the performance's tool overview graphs. Constructs,
|
||||
* syncs, toggles displays and defines the memory, framerate and timeline view.
|
||||
*
|
||||
* @param {object} definition
|
||||
* @param {DOMElement} root
|
||||
* @param {function} getFilter
|
||||
* @param {function} getTheme
|
||||
*/
|
||||
function GraphsController({ definition, root, getFilter, getTheme }) {
|
||||
this._graphs = {};
|
||||
this._enabled = new Set();
|
||||
this._definition = definition || GRAPH_DEFINITIONS;
|
||||
this._root = root;
|
||||
this._getFilter = getFilter;
|
||||
this._getTheme = getTheme;
|
||||
this._primaryLink = Object.keys(this._definition).filter(
|
||||
name => this._definition[name].primaryLink
|
||||
)[0];
|
||||
this.$ = root.ownerDocument.querySelector.bind(root.ownerDocument);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
this._onSelecting = this._onSelecting.bind(this);
|
||||
}
|
||||
|
||||
GraphsController.prototype = {
|
||||
/**
|
||||
* Returns the corresponding graph by `graphName`.
|
||||
*/
|
||||
get: function(graphName) {
|
||||
return this._graphs[graphName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterates through all graphs and renders the data
|
||||
* from a RecordingModel. Takes a resolution value used in
|
||||
* some graphs.
|
||||
* Saves rendering progress as a promise to be consumed by `destroy`,
|
||||
* to wait for cleaning up rendering during destruction.
|
||||
*/
|
||||
async render(recordingData, resolution) {
|
||||
// Get the previous render promise so we don't start rendering
|
||||
// until the previous render cycle completes, which can occur
|
||||
// especially when a recording is finished, and triggers a
|
||||
// fresh rendering at a higher rate
|
||||
await this._rendering;
|
||||
|
||||
// Check after yielding to ensure we're not tearing down,
|
||||
// as this can create a race condition in tests
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._rendering = (async () => {
|
||||
for (const graph of await this._getEnabled()) {
|
||||
await graph.setPerformanceData(recordingData, resolution);
|
||||
this.emit("rendered", graph.graphName);
|
||||
}
|
||||
})();
|
||||
await this._rendering;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys the underlying graphs.
|
||||
*/
|
||||
async destroy() {
|
||||
const primary = this._getPrimaryLink();
|
||||
|
||||
this._destroyed = true;
|
||||
|
||||
if (primary) {
|
||||
primary.off("selecting", this._onSelecting);
|
||||
}
|
||||
|
||||
// If there was rendering, wait until the most recent render cycle
|
||||
// has finished
|
||||
if (this._rendering) {
|
||||
await this._rendering;
|
||||
}
|
||||
|
||||
for (const graph of this.getWidgets()) {
|
||||
await graph.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies the theme to the underlying graphs. Optionally takes
|
||||
* a `redraw` boolean in the options to force redraw.
|
||||
*/
|
||||
setTheme: function(options = {}) {
|
||||
const theme = options.theme || this._getTheme();
|
||||
for (const graph of this.getWidgets()) {
|
||||
graph.setTheme(theme);
|
||||
graph.refresh({ force: options.redraw });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up the graph, if needed. Returns a promise resolving
|
||||
* to the graph if it is enabled once it's ready, or otherwise returns
|
||||
* null if disabled.
|
||||
*/
|
||||
async isAvailable(graphName) {
|
||||
if (!this._enabled.has(graphName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let graph = this.get(graphName);
|
||||
|
||||
if (!graph) {
|
||||
graph = await this._construct(graphName);
|
||||
}
|
||||
|
||||
await graph.ready();
|
||||
return graph;
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable or disable a subgraph controlled by GraphsController.
|
||||
* This determines what graphs are visible and get rendered.
|
||||
*/
|
||||
enable: function(graphName, isEnabled) {
|
||||
const el = this.$(this._definition[graphName].selector);
|
||||
el.classList[isEnabled ? "remove" : "add"]("hidden");
|
||||
|
||||
// If no status change, just return
|
||||
if (this._enabled.has(graphName) === isEnabled) {
|
||||
return;
|
||||
}
|
||||
if (isEnabled) {
|
||||
this._enabled.add(graphName);
|
||||
} else {
|
||||
this._enabled.delete(graphName);
|
||||
}
|
||||
|
||||
// Invalidate our cache of ready-to-go graphs
|
||||
this._enabledGraphs = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables all graphs controller by the GraphsController, and
|
||||
* also hides the root element. This is a one way switch, and used
|
||||
* when older platforms do not have any timeline data.
|
||||
*/
|
||||
disableAll: function() {
|
||||
this._root.classList.add("hidden");
|
||||
// Hide all the subelements
|
||||
Object.keys(this._definition).forEach(graphName =>
|
||||
this.enable(graphName, false)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a mapped selection on the graph that is the main controller
|
||||
* for keeping the graphs' selections in sync.
|
||||
*/
|
||||
setMappedSelection: function(selection, { mapStart, mapEnd }) {
|
||||
return this._getPrimaryLink().setMappedSelection(selection, {
|
||||
mapStart,
|
||||
mapEnd,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches the currently mapped selection. If graphs are not yet rendered,
|
||||
* (which throws in Graphs.js), return null.
|
||||
*/
|
||||
getMappedSelection: function({ mapStart, mapEnd }) {
|
||||
const primary = this._getPrimaryLink();
|
||||
if (primary && primary.hasData()) {
|
||||
return primary.getMappedSelection({ mapStart, mapEnd });
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an array of graphs that have been created, not necessarily
|
||||
* enabled currently.
|
||||
*/
|
||||
getWidgets: function() {
|
||||
return Object.keys(this._graphs).map(name => this._graphs[name]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Drops the selection.
|
||||
*/
|
||||
dropSelection: function() {
|
||||
if (this._getPrimaryLink()) {
|
||||
return this._getPrimaryLink().dropSelection();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Makes sure the selection is enabled or disabled in all the graphs.
|
||||
*/
|
||||
async selectionEnabled(enabled) {
|
||||
for (const graph of await this._getEnabled()) {
|
||||
graph.selectionEnabled = enabled;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the graph `graphName` and initializes it.
|
||||
*/
|
||||
async _construct(graphName) {
|
||||
const def = this._definition[graphName];
|
||||
const el = this.$(def.selector);
|
||||
const filter = this._getFilter();
|
||||
const graph = (this._graphs[graphName] = new def.constructor(el, filter));
|
||||
graph.graphName = graphName;
|
||||
|
||||
await graph.ready();
|
||||
|
||||
// Sync the graphs' animations and selections together
|
||||
if (def.primaryLink) {
|
||||
graph.on("selecting", this._onSelecting);
|
||||
} else {
|
||||
CanvasGraphUtils.linkAnimation(this._getPrimaryLink(), graph);
|
||||
CanvasGraphUtils.linkSelection(this._getPrimaryLink(), graph);
|
||||
}
|
||||
|
||||
// Sets the container element's visibility based off of enabled status
|
||||
el.classList[this._enabled.has(graphName) ? "remove" : "add"]("hidden");
|
||||
|
||||
this.setTheme();
|
||||
return graph;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the main graph for this collection, that all graphs
|
||||
* are bound to for syncing and selection.
|
||||
*/
|
||||
_getPrimaryLink: function() {
|
||||
return this.get(this._primaryLink);
|
||||
},
|
||||
|
||||
/**
|
||||
* Emitted when a selection occurs.
|
||||
*/
|
||||
_onSelecting: function() {
|
||||
this.emit("selecting");
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves to an array with all graphs that are enabled, and
|
||||
* creates them if needed. Different than just iterating over `this._graphs`,
|
||||
* as those could be enabled. Uses caching, as rendering happens many times per second,
|
||||
* compared to how often which graphs/features are changed (rarely).
|
||||
*/
|
||||
async _getEnabled() {
|
||||
if (this._enabledGraphs) {
|
||||
return this._enabledGraphs;
|
||||
}
|
||||
const enabled = [];
|
||||
for (const graphName of this._enabled) {
|
||||
const graph = await this.isAvailable(graphName);
|
||||
if (graph) {
|
||||
enabled.push(graph);
|
||||
}
|
||||
}
|
||||
this._enabledGraphs = enabled;
|
||||
return this._enabledGraphs;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A base class for performance graphs to inherit from.
|
||||
*
|
||||
* @param Node parent
|
||||
* The parent node holding the overview.
|
||||
* @param string metric
|
||||
* The unit of measurement for this graph.
|
||||
*/
|
||||
function OptimizationsGraph(parent) {
|
||||
MountainGraphWidget.call(this, parent);
|
||||
this.setTheme();
|
||||
}
|
||||
|
||||
OptimizationsGraph.prototype = extend(MountainGraphWidget.prototype, {
|
||||
async render(threadNode, frameNode) {
|
||||
// Regardless if we draw or clear the graph, wait
|
||||
// until it's ready.
|
||||
await this.ready();
|
||||
|
||||
if (!threadNode || !frameNode) {
|
||||
this.setData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const { sampleTimes } = threadNode;
|
||||
|
||||
if (!sampleTimes.length) {
|
||||
this.setData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Take startTime/endTime from samples recorded, rather than
|
||||
// using duration directly from threadNode, as the first sample that
|
||||
// equals the startTime does not get recorded.
|
||||
const startTime = sampleTimes[0];
|
||||
const endTime = sampleTimes[sampleTimes.length - 1];
|
||||
|
||||
const bucketSize = (endTime - startTime) / OPTIMIZATIONS_GRAPH_RESOLUTION;
|
||||
const data = createTierGraphDataFromFrameNode(
|
||||
frameNode,
|
||||
sampleTimes,
|
||||
bucketSize
|
||||
);
|
||||
|
||||
// If for some reason we don't have data (like the frameNode doesn't
|
||||
// have optimizations, but it shouldn't be at this point if it doesn't),
|
||||
// log an error.
|
||||
if (!data) {
|
||||
console.error(
|
||||
`FrameNode#${frameNode.location} does not have optimizations data to render.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.dataOffsetX = startTime;
|
||||
await this.setData(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the theme via `theme` to either "light" or "dark",
|
||||
* and updates the internal styling to match. Requires a redraw
|
||||
* to see the effects.
|
||||
*/
|
||||
setTheme: function(theme) {
|
||||
theme = theme || "light";
|
||||
|
||||
const interpreterColor = getColor("graphs-red", theme);
|
||||
const baselineColor = getColor("graphs-blue", theme);
|
||||
const ionColor = getColor("graphs-green", theme);
|
||||
|
||||
this.format = [
|
||||
{ color: interpreterColor },
|
||||
{ color: baselineColor },
|
||||
{ color: ionColor },
|
||||
];
|
||||
|
||||
this.backgroundColor = getColor("sidebar-background", theme);
|
||||
},
|
||||
});
|
||||
|
||||
exports.OptimizationsGraph = OptimizationsGraph;
|
||||
exports.FramerateGraph = FramerateGraph;
|
||||
exports.MemoryGraph = MemoryGraph;
|
||||
exports.TimelineGraph = TimelineGraph;
|
||||
exports.GraphsController = GraphsController;
|
@ -1,177 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the rendering code for the marker sidebar.
|
||||
*/
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {
|
||||
MarkerDOMUtils,
|
||||
} = require("devtools/client/performance/modules/marker-dom-utils");
|
||||
|
||||
/**
|
||||
* A detailed view for one single marker.
|
||||
*
|
||||
* @param Node parent
|
||||
* The parent node holding the view.
|
||||
* @param Node splitter
|
||||
* The splitter node that the resize event is bound to.
|
||||
*/
|
||||
function MarkerDetails(parent, splitter) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._document = parent.ownerDocument;
|
||||
this._parent = parent;
|
||||
this._splitter = splitter;
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onSplitterMouseUp = this._onSplitterMouseUp.bind(this);
|
||||
|
||||
this._parent.addEventListener("click", this._onClick);
|
||||
this._splitter.addEventListener("mouseup", this._onSplitterMouseUp);
|
||||
|
||||
this.hidden = true;
|
||||
}
|
||||
|
||||
MarkerDetails.prototype = {
|
||||
/**
|
||||
* Sets this view's width.
|
||||
* @param number
|
||||
*/
|
||||
set width(value) {
|
||||
this._parent.setAttribute("width", value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets this view's width.
|
||||
* @return number
|
||||
*/
|
||||
get width() {
|
||||
return +this._parent.getAttribute("width");
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets this view's visibility.
|
||||
* @param boolean
|
||||
*/
|
||||
set hidden(value) {
|
||||
if (this._parent.hidden != value) {
|
||||
this._parent.hidden = value;
|
||||
this.emit("resize");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets this view's visibility.
|
||||
* @param boolean
|
||||
*/
|
||||
get hidden() {
|
||||
return this._parent.hidden;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the marker details from this view.
|
||||
*/
|
||||
empty: function() {
|
||||
this._parent.innerHTML = "";
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates view with marker's details.
|
||||
*
|
||||
* @param object params
|
||||
* An options object holding:
|
||||
* - marker: The marker to display.
|
||||
* - frames: Array of stack frame information; see stack.js.
|
||||
* - allocations: Whether or not allocations were enabled for this
|
||||
* recording. [optional]
|
||||
*/
|
||||
render: function(options) {
|
||||
const { marker, frames } = options;
|
||||
this.empty();
|
||||
|
||||
const elements = [];
|
||||
elements.push(MarkerDOMUtils.buildTitle(this._document, marker));
|
||||
elements.push(MarkerDOMUtils.buildDuration(this._document, marker));
|
||||
MarkerDOMUtils.buildFields(this._document, marker).forEach(f =>
|
||||
elements.push(f)
|
||||
);
|
||||
MarkerDOMUtils.buildCustom(this._document, marker, options).forEach(f =>
|
||||
elements.push(f)
|
||||
);
|
||||
|
||||
// Build a stack element -- and use the "startStack" label if
|
||||
// we have both a startStack and endStack.
|
||||
if (marker.stack) {
|
||||
const type = marker.endStack ? "startStack" : "stack";
|
||||
elements.push(
|
||||
MarkerDOMUtils.buildStackTrace(this._document, {
|
||||
frameIndex: marker.stack,
|
||||
frames,
|
||||
type,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (marker.endStack) {
|
||||
const type = "endStack";
|
||||
elements.push(
|
||||
MarkerDOMUtils.buildStackTrace(this._document, {
|
||||
frameIndex: marker.endStack,
|
||||
frames,
|
||||
type,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
elements.forEach(el => this._parent.appendChild(el));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles click in the marker details view. Based on the target,
|
||||
* can handle different actions -- only supporting view source links
|
||||
* for the moment.
|
||||
*/
|
||||
_onClick: function(e) {
|
||||
const data = findActionFromEvent(e.target, this._parent);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit(data.action, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the "mouseup" event on the marker details view splitter.
|
||||
*/
|
||||
_onSplitterMouseUp: function() {
|
||||
this.emit("resize");
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Take an element from an event `target`, and ascend through
|
||||
* the DOM, looking for an element with a `data-action` attribute. Return
|
||||
* the parsed `data-action` value found, or null if none found before
|
||||
* reaching the parent `container`.
|
||||
*
|
||||
* @param {Element} target
|
||||
* @param {Element} container
|
||||
* @return {?object}
|
||||
*/
|
||||
function findActionFromEvent(target, container) {
|
||||
let el = target;
|
||||
let action;
|
||||
while (el !== container) {
|
||||
action = el.getAttribute("data-action");
|
||||
if (action) {
|
||||
return JSON.parse(action);
|
||||
}
|
||||
el = el.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
exports.MarkerDetails = MarkerDetails;
|
@ -1,256 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the "markers overview" graph, which is a minimap of all
|
||||
* the timeline data. Regions inside it may be selected, determining which
|
||||
* markers are visible in the "waterfall".
|
||||
*/
|
||||
|
||||
const { extend } = require("devtools/shared/extend");
|
||||
const {
|
||||
AbstractCanvasGraph,
|
||||
} = require("devtools/client/shared/widgets/Graphs");
|
||||
|
||||
const { colorUtils } = require("devtools/shared/css/color");
|
||||
const { getColor } = require("devtools/client/shared/theme");
|
||||
const ProfilerGlobal = require("devtools/client/performance/modules/global");
|
||||
const {
|
||||
MarkerBlueprintUtils,
|
||||
} = require("devtools/client/performance/modules/marker-blueprint-utils");
|
||||
const {
|
||||
TickUtils,
|
||||
} = require("devtools/client/performance/modules/waterfall-ticks");
|
||||
const {
|
||||
TIMELINE_BLUEPRINT,
|
||||
} = require("devtools/client/performance/modules/markers");
|
||||
|
||||
const OVERVIEW_HEADER_HEIGHT = 14; // px
|
||||
const OVERVIEW_ROW_HEIGHT = 11; // px
|
||||
|
||||
const OVERVIEW_SELECTION_LINE_COLOR = "#666";
|
||||
const OVERVIEW_CLIPHEAD_LINE_COLOR = "#555";
|
||||
|
||||
const OVERVIEW_HEADER_TICKS_MULTIPLE = 100; // ms
|
||||
const OVERVIEW_HEADER_TICKS_SPACING_MIN = 75; // px
|
||||
const OVERVIEW_HEADER_TEXT_FONT_SIZE = 9; // px
|
||||
const OVERVIEW_HEADER_TEXT_FONT_FAMILY = "sans-serif";
|
||||
const OVERVIEW_HEADER_TEXT_PADDING_LEFT = 6; // px
|
||||
const OVERVIEW_HEADER_TEXT_PADDING_TOP = 1; // px
|
||||
const OVERVIEW_MARKER_WIDTH_MIN = 4; // px
|
||||
const OVERVIEW_GROUP_VERTICAL_PADDING = 5; // px
|
||||
|
||||
/**
|
||||
* An overview for the markers data.
|
||||
*
|
||||
* @param Node parent
|
||||
* The parent node holding the overview.
|
||||
* @param Array<String> filter
|
||||
* List of names of marker types that should not be shown.
|
||||
*/
|
||||
function MarkersOverview(parent, filter = [], ...args) {
|
||||
AbstractCanvasGraph.apply(this, [parent, "markers-overview", ...args]);
|
||||
this.setTheme();
|
||||
this.setFilter(filter);
|
||||
}
|
||||
|
||||
MarkersOverview.prototype = extend(AbstractCanvasGraph.prototype, {
|
||||
clipheadLineColor: OVERVIEW_CLIPHEAD_LINE_COLOR,
|
||||
selectionLineColor: OVERVIEW_SELECTION_LINE_COLOR,
|
||||
headerHeight: OVERVIEW_HEADER_HEIGHT,
|
||||
rowHeight: OVERVIEW_ROW_HEIGHT,
|
||||
groupPadding: OVERVIEW_GROUP_VERTICAL_PADDING,
|
||||
|
||||
/**
|
||||
* Compute the height of the overview.
|
||||
*/
|
||||
get fixedHeight() {
|
||||
return this.headerHeight + this.rowHeight * this._numberOfGroups;
|
||||
},
|
||||
|
||||
/**
|
||||
* List of marker types that should not be shown in the graph.
|
||||
*/
|
||||
setFilter: function(filter) {
|
||||
this._paintBatches = new Map();
|
||||
this._filter = filter;
|
||||
this._groupMap = Object.create(null);
|
||||
|
||||
const observedGroups = new Set();
|
||||
|
||||
for (const type in TIMELINE_BLUEPRINT) {
|
||||
if (filter.includes(type)) {
|
||||
continue;
|
||||
}
|
||||
this._paintBatches.set(type, {
|
||||
definition: TIMELINE_BLUEPRINT[type],
|
||||
batch: [],
|
||||
});
|
||||
observedGroups.add(TIMELINE_BLUEPRINT[type].group);
|
||||
}
|
||||
|
||||
// Take our set of observed groups and order them and map
|
||||
// the group numbers to fill in the holes via `_groupMap`.
|
||||
// This normalizes our rows by removing rows that aren't used
|
||||
// if filters are enabled.
|
||||
let actualPosition = 0;
|
||||
for (const groupNumber of Array.from(observedGroups).sort()) {
|
||||
this._groupMap[groupNumber] = actualPosition++;
|
||||
}
|
||||
this._numberOfGroups = Object.keys(this._groupMap).length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables selection and empties this graph.
|
||||
*/
|
||||
clearView: function() {
|
||||
this.selectionEnabled = false;
|
||||
this.dropSelection();
|
||||
this.setData({ duration: 0, markers: [] });
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the graph's data source.
|
||||
* @see AbstractCanvasGraph.prototype.buildGraphImage
|
||||
*/
|
||||
buildGraphImage: function() {
|
||||
const { markers, duration } = this._data;
|
||||
|
||||
const { canvas, ctx } = this._getNamedCanvas("markers-overview-data");
|
||||
const canvasWidth = this._width;
|
||||
const canvasHeight = this._height;
|
||||
|
||||
// Group markers into separate paint batches. This is necessary to
|
||||
// draw all markers sharing the same style at once.
|
||||
for (const marker of markers) {
|
||||
// Again skip over markers that we're filtering -- we don't want them
|
||||
// to be labeled as "Unknown"
|
||||
if (!MarkerBlueprintUtils.shouldDisplayMarker(marker, this._filter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const markerType =
|
||||
this._paintBatches.get(marker.name) ||
|
||||
this._paintBatches.get("UNKNOWN");
|
||||
markerType.batch.push(marker);
|
||||
}
|
||||
|
||||
// Calculate each row's height, and the time-based scaling.
|
||||
|
||||
const groupHeight = this.rowHeight * this._pixelRatio;
|
||||
const groupPadding = this.groupPadding * this._pixelRatio;
|
||||
const headerHeight = this.headerHeight * this._pixelRatio;
|
||||
const dataScale = (this.dataScaleX = canvasWidth / duration);
|
||||
|
||||
// Draw the header and overview background.
|
||||
|
||||
ctx.fillStyle = this.headerBackgroundColor;
|
||||
ctx.fillRect(0, 0, canvasWidth, headerHeight);
|
||||
|
||||
ctx.fillStyle = this.backgroundColor;
|
||||
ctx.fillRect(0, headerHeight, canvasWidth, canvasHeight);
|
||||
|
||||
// Draw the alternating odd/even group backgrounds.
|
||||
|
||||
ctx.fillStyle = this.alternatingBackgroundColor;
|
||||
ctx.beginPath();
|
||||
|
||||
for (let i = 0; i < this._numberOfGroups; i += 2) {
|
||||
const top = headerHeight + i * groupHeight;
|
||||
ctx.rect(0, top, canvasWidth, groupHeight);
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
|
||||
// Draw the timeline header ticks.
|
||||
|
||||
const fontSize = OVERVIEW_HEADER_TEXT_FONT_SIZE * this._pixelRatio;
|
||||
const fontFamily = OVERVIEW_HEADER_TEXT_FONT_FAMILY;
|
||||
const textPaddingLeft =
|
||||
OVERVIEW_HEADER_TEXT_PADDING_LEFT * this._pixelRatio;
|
||||
const textPaddingTop = OVERVIEW_HEADER_TEXT_PADDING_TOP * this._pixelRatio;
|
||||
|
||||
const tickInterval = TickUtils.findOptimalTickInterval({
|
||||
ticksMultiple: OVERVIEW_HEADER_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: OVERVIEW_HEADER_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale,
|
||||
});
|
||||
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.font = fontSize + "px " + fontFamily;
|
||||
ctx.fillStyle = this.headerTextColor;
|
||||
ctx.strokeStyle = this.headerTimelineStrokeColor;
|
||||
ctx.beginPath();
|
||||
|
||||
for (let x = 0; x < canvasWidth; x += tickInterval) {
|
||||
const lineLeft = x;
|
||||
const textLeft = lineLeft + textPaddingLeft;
|
||||
const time = Math.round(x / dataScale);
|
||||
const label = ProfilerGlobal.L10N.getFormatStr("timeline.tick", time);
|
||||
ctx.fillText(label, textLeft, headerHeight / 2 + textPaddingTop);
|
||||
ctx.moveTo(lineLeft, 0);
|
||||
ctx.lineTo(lineLeft, canvasHeight);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
// Draw the timeline markers.
|
||||
|
||||
for (const [, { definition, batch }] of this._paintBatches) {
|
||||
const group = this._groupMap[definition.group];
|
||||
const top = headerHeight + group * groupHeight + groupPadding / 2;
|
||||
const height = groupHeight - groupPadding;
|
||||
|
||||
const color = getColor(definition.colorName, this.theme);
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
|
||||
for (const { start, end } of batch) {
|
||||
const left = start * dataScale;
|
||||
const width = Math.max(
|
||||
(end - start) * dataScale,
|
||||
OVERVIEW_MARKER_WIDTH_MIN
|
||||
);
|
||||
ctx.rect(left, top, width, height);
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
|
||||
// Since all the markers in this batch (thus sharing the same style) have
|
||||
// been drawn, empty it. The next time new markers will be available,
|
||||
// they will be sorted and drawn again.
|
||||
batch.length = 0;
|
||||
}
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the theme via `theme` to either "light" or "dark",
|
||||
* and updates the internal styling to match. Requires a redraw
|
||||
* to see the effects.
|
||||
*/
|
||||
setTheme: function(theme) {
|
||||
this.theme = theme = theme || "light";
|
||||
this.backgroundColor = getColor("body-background", theme);
|
||||
this.selectionBackgroundColor = colorUtils.setAlpha(
|
||||
getColor("selection-background", theme),
|
||||
0.25
|
||||
);
|
||||
this.selectionStripesColor = colorUtils.setAlpha("#fff", 0.1);
|
||||
this.headerBackgroundColor = getColor("body-background", theme);
|
||||
this.headerTextColor = getColor("body-color", theme);
|
||||
this.headerTimelineStrokeColor = colorUtils.setAlpha(
|
||||
getColor("text-color-alt", theme),
|
||||
0.25
|
||||
);
|
||||
this.alternatingBackgroundColor = colorUtils.setAlpha(
|
||||
getColor("body-color", theme),
|
||||
0.05
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
exports.MarkersOverview = MarkersOverview;
|
@ -1,11 +0,0 @@
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
"graphs.js",
|
||||
"marker-details.js",
|
||||
"markers-overview.js",
|
||||
"tree-view.js",
|
||||
)
|
@ -1,461 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the tree view, displaying all the samples and frames
|
||||
* received from the proviler in a tree-like structure.
|
||||
*/
|
||||
|
||||
const { L10N } = require("devtools/client/performance/modules/global");
|
||||
const { extend } = require("devtools/shared/extend");
|
||||
const {
|
||||
AbstractTreeItem,
|
||||
} = require("resource://devtools/client/shared/widgets/AbstractTreeItem.jsm");
|
||||
|
||||
const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
|
||||
const VIEW_OPTIMIZATIONS_TOOLTIP = L10N.getStr(
|
||||
"table.view-optimizations.tooltiptext2"
|
||||
);
|
||||
|
||||
const CALL_TREE_INDENTATION = 16; // px
|
||||
|
||||
// Used for rendering values in cells
|
||||
const FORMATTERS = {
|
||||
TIME: value =>
|
||||
L10N.getFormatStr("table.ms2", L10N.numberWithDecimals(value, 2)),
|
||||
PERCENT: value =>
|
||||
L10N.getFormatStr("table.percentage3", L10N.numberWithDecimals(value, 2)),
|
||||
NUMBER: value => value || 0,
|
||||
BYTESIZE: value => L10N.getFormatStr("table.bytes", value || 0),
|
||||
};
|
||||
|
||||
/**
|
||||
* Definitions for rendering cells. Triads of class name, property name from
|
||||
* `frame.getInfo()`, and a formatter function.
|
||||
*/
|
||||
const CELLS = {
|
||||
duration: ["duration", "totalDuration", FORMATTERS.TIME],
|
||||
percentage: ["percentage", "totalPercentage", FORMATTERS.PERCENT],
|
||||
selfDuration: ["self-duration", "selfDuration", FORMATTERS.TIME],
|
||||
selfPercentage: ["self-percentage", "selfPercentage", FORMATTERS.PERCENT],
|
||||
samples: ["samples", "samples", FORMATTERS.NUMBER],
|
||||
|
||||
selfSize: ["self-size", "selfSize", FORMATTERS.BYTESIZE],
|
||||
selfSizePercentage: [
|
||||
"self-size-percentage",
|
||||
"selfSizePercentage",
|
||||
FORMATTERS.PERCENT,
|
||||
],
|
||||
selfCount: ["self-count", "selfCount", FORMATTERS.NUMBER],
|
||||
selfCountPercentage: [
|
||||
"self-count-percentage",
|
||||
"selfCountPercentage",
|
||||
FORMATTERS.PERCENT,
|
||||
],
|
||||
size: ["size", "totalSize", FORMATTERS.BYTESIZE],
|
||||
sizePercentage: [
|
||||
"size-percentage",
|
||||
"totalSizePercentage",
|
||||
FORMATTERS.PERCENT,
|
||||
],
|
||||
count: ["count", "totalCount", FORMATTERS.NUMBER],
|
||||
countPercentage: [
|
||||
"count-percentage",
|
||||
"totalCountPercentage",
|
||||
FORMATTERS.PERCENT,
|
||||
],
|
||||
};
|
||||
const CELL_TYPES = Object.keys(CELLS);
|
||||
|
||||
const DEFAULT_SORTING_PREDICATE = (frameA, frameB) => {
|
||||
const dataA = frameA.getDisplayedData();
|
||||
const dataB = frameB.getDisplayedData();
|
||||
const isAllocations = "totalSize" in dataA;
|
||||
|
||||
if (isAllocations) {
|
||||
if (this.inverted && dataA.selfSize !== dataB.selfSize) {
|
||||
return dataA.selfSize < dataB.selfSize ? 1 : -1;
|
||||
}
|
||||
return dataA.totalSize < dataB.totalSize ? 1 : -1;
|
||||
}
|
||||
|
||||
if (this.inverted && dataA.selfPercentage !== dataB.selfPercentage) {
|
||||
return dataA.selfPercentage < dataB.selfPercentage ? 1 : -1;
|
||||
}
|
||||
return dataA.totalPercentage < dataB.totalPercentage ? 1 : -1;
|
||||
};
|
||||
|
||||
// depth
|
||||
const DEFAULT_AUTO_EXPAND_DEPTH = 3;
|
||||
const DEFAULT_VISIBLE_CELLS = {
|
||||
duration: true,
|
||||
percentage: true,
|
||||
selfDuration: true,
|
||||
selfPercentage: true,
|
||||
samples: true,
|
||||
function: true,
|
||||
|
||||
// allocation columns
|
||||
count: false,
|
||||
selfCount: false,
|
||||
size: false,
|
||||
selfSize: false,
|
||||
countPercentage: false,
|
||||
selfCountPercentage: false,
|
||||
sizePercentage: false,
|
||||
selfSizePercentage: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* An item in a call tree view, which looks like this:
|
||||
*
|
||||
* Time (ms) | Cost | Calls | Function
|
||||
* ============================================================================
|
||||
* 1,000.00 | 100.00% | | ▼ (root)
|
||||
* 500.12 | 50.01% | 300 | ▼ foo Categ. 1
|
||||
* 300.34 | 30.03% | 1500 | ▼ bar Categ. 2
|
||||
* 10.56 | 0.01% | 42 | ▶ call_with_children Categ. 3
|
||||
* 90.78 | 0.09% | 25 | call_without_children Categ. 4
|
||||
*
|
||||
* Every instance of a `CallView` represents a row in the call tree. The same
|
||||
* parent node is used for all rows.
|
||||
*
|
||||
* @param CallView caller
|
||||
* The CallView considered the "caller" frame. This newly created
|
||||
* instance will be represent the "callee". Should be null for root nodes.
|
||||
* @param ThreadNode | FrameNode frame
|
||||
* Details about this function, like { samples, duration, calls } etc.
|
||||
* @param number level [optional]
|
||||
* The indentation level in the call tree. The root node is at level 0.
|
||||
* @param boolean hidden [optional]
|
||||
* Whether this node should be hidden and not contribute to depth/level
|
||||
* calculations. Defaults to false.
|
||||
* @param boolean inverted [optional]
|
||||
* Whether the call tree has been inverted (bottom up, rather than
|
||||
* top-down). Defaults to false.
|
||||
* @param function sortingPredicate [optional]
|
||||
* The predicate used to sort the tree items when created. Defaults to
|
||||
* the caller's `sortingPredicate` if a caller exists, otherwise defaults
|
||||
* to DEFAULT_SORTING_PREDICATE. The two passed arguments are FrameNodes.
|
||||
* @param number autoExpandDepth [optional]
|
||||
* The depth to which the tree should automatically expand. Defualts to
|
||||
* the caller's `autoExpandDepth` if a caller exists, otherwise defaults
|
||||
* to DEFAULT_AUTO_EXPAND_DEPTH.
|
||||
* @param object visibleCells
|
||||
* An object specifying which cells are visible in the tree. Defaults to
|
||||
* the caller's `visibleCells` if a caller exists, otherwise defaults
|
||||
* to DEFAULT_VISIBLE_CELLS.
|
||||
* @param boolean showOptimizationHint [optional]
|
||||
* Whether or not to show an icon indicating if the frame has optimization
|
||||
* data.
|
||||
*/
|
||||
function CallView({
|
||||
caller,
|
||||
frame,
|
||||
level,
|
||||
hidden,
|
||||
inverted,
|
||||
sortingPredicate,
|
||||
autoExpandDepth,
|
||||
visibleCells,
|
||||
showOptimizationHint,
|
||||
}) {
|
||||
AbstractTreeItem.call(this, {
|
||||
parent: caller,
|
||||
level: level | (0 - (hidden ? 1 : 0)),
|
||||
});
|
||||
|
||||
if (sortingPredicate != null) {
|
||||
this.sortingPredicate = sortingPredicate;
|
||||
} else if (caller) {
|
||||
this.sortingPredicate = caller.sortingPredicate;
|
||||
} else {
|
||||
this.sortingPredicate = DEFAULT_SORTING_PREDICATE;
|
||||
}
|
||||
|
||||
if (autoExpandDepth != null) {
|
||||
this.autoExpandDepth = autoExpandDepth;
|
||||
} else if (caller) {
|
||||
this.autoExpandDepth = caller.autoExpandDepth;
|
||||
} else {
|
||||
this.autoExpandDepth = DEFAULT_AUTO_EXPAND_DEPTH;
|
||||
}
|
||||
|
||||
if (visibleCells != null) {
|
||||
this.visibleCells = visibleCells;
|
||||
} else if (caller) {
|
||||
this.visibleCells = caller.visibleCells;
|
||||
} else {
|
||||
this.visibleCells = Object.create(DEFAULT_VISIBLE_CELLS);
|
||||
}
|
||||
|
||||
this.caller = caller;
|
||||
this.frame = frame;
|
||||
this.hidden = hidden;
|
||||
this.inverted = inverted;
|
||||
this.showOptimizationHint = showOptimizationHint;
|
||||
|
||||
this._onUrlClick = this._onUrlClick.bind(this);
|
||||
}
|
||||
|
||||
CallView.prototype = extend(AbstractTreeItem.prototype, {
|
||||
/**
|
||||
* Creates the view for this tree node.
|
||||
* @param Node document
|
||||
* @param Node arrowNode
|
||||
* @return Node
|
||||
*/
|
||||
_displaySelf: function(document, arrowNode) {
|
||||
const frameInfo = this.getDisplayedData();
|
||||
const cells = [];
|
||||
|
||||
for (const type of CELL_TYPES) {
|
||||
if (this.visibleCells[type]) {
|
||||
// Inline for speed, but pass in the formatted value via
|
||||
// cell definition, as well as the element type.
|
||||
cells.push(
|
||||
this._createCell(
|
||||
document,
|
||||
CELLS[type][2](frameInfo[CELLS[type][1]]),
|
||||
CELLS[type][0]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.visibleCells.function) {
|
||||
cells.push(
|
||||
this._createFunctionCell(
|
||||
document,
|
||||
arrowNode,
|
||||
frameInfo.name,
|
||||
frameInfo,
|
||||
this.level
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const targetNode = document.createXULElement("hbox");
|
||||
targetNode.className = "call-tree-item";
|
||||
targetNode.setAttribute(
|
||||
"origin",
|
||||
frameInfo.isContent ? "content" : "chrome"
|
||||
);
|
||||
targetNode.setAttribute("category", frameInfo.categoryData.abbrev || "");
|
||||
targetNode.setAttribute("tooltiptext", frameInfo.tooltiptext);
|
||||
|
||||
if (this.hidden) {
|
||||
targetNode.style.display = "none";
|
||||
}
|
||||
|
||||
for (let i = 0; i < cells.length; i++) {
|
||||
targetNode.appendChild(cells[i]);
|
||||
}
|
||||
|
||||
return targetNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this node in the call tree with the corresponding "callees".
|
||||
* These are defined in the `frame` data source for this call view.
|
||||
* @param array:AbstractTreeItem children
|
||||
*/
|
||||
_populateSelf: function(children) {
|
||||
const newLevel = this.level + 1;
|
||||
|
||||
for (const newFrame of this.frame.calls) {
|
||||
children.push(
|
||||
new CallView({
|
||||
caller: this,
|
||||
frame: newFrame,
|
||||
level: newLevel,
|
||||
inverted: this.inverted,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Sort the "callees" asc. by samples, before inserting them in the tree,
|
||||
// if no other sorting predicate was specified on this on the root item.
|
||||
children.sort(this.sortingPredicate.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Functions creating each cell in this call view.
|
||||
* Invoked by `_displaySelf`.
|
||||
*/
|
||||
_createCell: function(doc, value, type) {
|
||||
const cell = doc.createXULElement("description");
|
||||
cell.className = "plain call-tree-cell";
|
||||
cell.setAttribute("type", type);
|
||||
cell.setAttribute("crop", "end");
|
||||
// Add a tabulation to the cell text in case it's is selected and copied.
|
||||
cell.textContent = value + "\t";
|
||||
return cell;
|
||||
},
|
||||
|
||||
_createFunctionCell: function(
|
||||
doc,
|
||||
arrowNode,
|
||||
frameName,
|
||||
frameInfo,
|
||||
frameLevel
|
||||
) {
|
||||
const cell = doc.createXULElement("hbox");
|
||||
cell.className = "call-tree-cell";
|
||||
cell.style.marginInlineStart = frameLevel * CALL_TREE_INDENTATION + "px";
|
||||
cell.setAttribute("type", "function");
|
||||
cell.appendChild(arrowNode);
|
||||
|
||||
// Render optimization hint if this frame has opt data.
|
||||
if (
|
||||
this.root.showOptimizationHint &&
|
||||
frameInfo.hasOptimizations &&
|
||||
!frameInfo.isMetaCategory
|
||||
) {
|
||||
const icon = doc.createXULElement("description");
|
||||
icon.setAttribute("tooltiptext", VIEW_OPTIMIZATIONS_TOOLTIP);
|
||||
icon.className = "opt-icon";
|
||||
cell.appendChild(icon);
|
||||
}
|
||||
|
||||
// Don't render a name label node if there's no function name. A different
|
||||
// location label node will be rendered instead.
|
||||
if (frameName) {
|
||||
const nameNode = doc.createXULElement("description");
|
||||
nameNode.className = "plain call-tree-name";
|
||||
nameNode.textContent = frameName;
|
||||
cell.appendChild(nameNode);
|
||||
}
|
||||
|
||||
// Don't render detailed labels for meta category frames
|
||||
if (!frameInfo.isMetaCategory) {
|
||||
this._appendFunctionDetailsCells(doc, cell, frameInfo);
|
||||
}
|
||||
|
||||
// Don't render an expando-arrow for leaf nodes.
|
||||
const hasDescendants = Object.keys(this.frame.calls).length > 0;
|
||||
if (!hasDescendants) {
|
||||
arrowNode.setAttribute("invisible", "");
|
||||
}
|
||||
|
||||
// Add a line break to the last description of the row in case it's selected
|
||||
// and copied.
|
||||
const lastDescription = cell.querySelector("description:last-of-type");
|
||||
lastDescription.textContent = lastDescription.textContent + "\n";
|
||||
|
||||
// Add spaces as frameLevel indicators in case the row is selected and
|
||||
// copied. These spaces won't be displayed in the cell content.
|
||||
const firstDescription = cell.querySelector("description:first-of-type");
|
||||
const levelIndicator = frameLevel > 0 ? " ".repeat(frameLevel) : "";
|
||||
firstDescription.textContent =
|
||||
levelIndicator + firstDescription.textContent;
|
||||
|
||||
return cell;
|
||||
},
|
||||
|
||||
_appendFunctionDetailsCells: function(doc, cell, frameInfo) {
|
||||
if (frameInfo.fileName) {
|
||||
const urlNode = doc.createXULElement("description");
|
||||
urlNode.className = "plain call-tree-url";
|
||||
urlNode.textContent = frameInfo.fileName;
|
||||
urlNode.setAttribute(
|
||||
"tooltiptext",
|
||||
URL_LABEL_TOOLTIP + " → " + frameInfo.url
|
||||
);
|
||||
urlNode.addEventListener("mousedown", this._onUrlClick);
|
||||
cell.appendChild(urlNode);
|
||||
}
|
||||
|
||||
if (frameInfo.line) {
|
||||
const lineNode = doc.createXULElement("description");
|
||||
lineNode.className = "plain call-tree-line";
|
||||
lineNode.textContent = ":" + frameInfo.line;
|
||||
cell.appendChild(lineNode);
|
||||
}
|
||||
|
||||
if (frameInfo.column) {
|
||||
const columnNode = doc.createXULElement("description");
|
||||
columnNode.className = "plain call-tree-column";
|
||||
columnNode.textContent = ":" + frameInfo.column;
|
||||
cell.appendChild(columnNode);
|
||||
}
|
||||
|
||||
if (frameInfo.host) {
|
||||
const hostNode = doc.createXULElement("description");
|
||||
hostNode.className = "plain call-tree-host";
|
||||
hostNode.textContent = frameInfo.host;
|
||||
cell.appendChild(hostNode);
|
||||
}
|
||||
|
||||
if (frameInfo.categoryData.label) {
|
||||
const categoryNode = doc.createXULElement("description");
|
||||
categoryNode.className = "plain call-tree-category";
|
||||
categoryNode.style.color = frameInfo.categoryData.color;
|
||||
categoryNode.textContent = frameInfo.categoryData.label;
|
||||
cell.appendChild(categoryNode);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the data displayed about this tree item, based on the FrameNode
|
||||
* model associated with this view.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
getDisplayedData: function() {
|
||||
if (this._cachedDisplayedData) {
|
||||
return this._cachedDisplayedData;
|
||||
}
|
||||
|
||||
this._cachedDisplayedData = this.frame.getInfo({
|
||||
root: this.root.frame,
|
||||
allocations: this.visibleCells.count || this.visibleCells.selfCount,
|
||||
});
|
||||
|
||||
return this._cachedDisplayedData;
|
||||
|
||||
/**
|
||||
* When inverting call tree, the costs and times are dependent on position
|
||||
* in the tree. We must only count leaf nodes with self cost, and total costs
|
||||
* dependent on how many times the leaf node was found with a full stack path.
|
||||
*
|
||||
* Total | Self | Calls | Function
|
||||
* ============================================================================
|
||||
* 100% | 100% | 100 | ▼ C
|
||||
* 50% | 0% | 50 | ▼ B
|
||||
* 50% | 0% | 50 | ▼ A
|
||||
* 50% | 0% | 50 | ▼ B
|
||||
*
|
||||
* Every instance of a `CallView` represents a row in the call tree. The same
|
||||
* container node is used for all rows.
|
||||
*/
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the category information hidden or visible.
|
||||
* @param boolean visible
|
||||
*/
|
||||
toggleCategories: function(visible) {
|
||||
if (!visible) {
|
||||
this.container.setAttribute("categories-hidden", "");
|
||||
} else {
|
||||
this.container.removeAttribute("categories-hidden");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "click" event on the url node of this call view.
|
||||
*/
|
||||
_onUrlClick: function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Only emit for left click events
|
||||
if (e.button === 0) {
|
||||
this.root.emit("link", this);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
exports.CallView = CallView;
|
@ -1,25 +0,0 @@
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += [
|
||||
"components",
|
||||
"modules",
|
||||
"test",
|
||||
"views",
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
"events.js",
|
||||
"initializer.js",
|
||||
"panel.js",
|
||||
"performance-controller.js",
|
||||
"performance-view.js",
|
||||
)
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ["test/browser.ini"]
|
||||
XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell/xpcshell.ini"]
|
||||
|
||||
with Files("**"):
|
||||
BUG_COMPONENT = ("DevTools", "Performance Tools (Profiler/Timeline)")
|
@ -1,160 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
|
||||
|
||||
function PerformancePanel(iframeWindow, toolbox, commands) {
|
||||
this.panelWin = iframeWindow;
|
||||
this.toolbox = toolbox;
|
||||
this.commands = commands;
|
||||
this._targetAvailablePromise = Promise.resolve();
|
||||
|
||||
this._onTargetAvailable = this._onTargetAvailable.bind(this);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
exports.PerformancePanel = PerformancePanel;
|
||||
|
||||
PerformancePanel.prototype = {
|
||||
/**
|
||||
* Open is effectively an asynchronous constructor.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved when the Performance tool
|
||||
* completes opening.
|
||||
*/
|
||||
async open() {
|
||||
if (this._opening) {
|
||||
return this._opening;
|
||||
}
|
||||
|
||||
this._checkRecordingStatus = this._checkRecordingStatus.bind(this);
|
||||
|
||||
const { PerformanceController, EVENTS } = this.panelWin;
|
||||
PerformanceController.on(
|
||||
EVENTS.RECORDING_ADDED,
|
||||
this._checkRecordingStatus
|
||||
);
|
||||
PerformanceController.on(
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
this._checkRecordingStatus
|
||||
);
|
||||
|
||||
// In case that the target is switched across process, the corresponding front also
|
||||
// will be changed. In order to detect that, watch the change.
|
||||
// Also, we wait for `watchTargets` to end. Indeed the function `_onTargetAvailable
|
||||
// will be called synchronously with current target as a parameter by
|
||||
// the `watchTargets` function.
|
||||
// So this `await` waits for initialization with current target, happening
|
||||
// in `_onTargetAvailable`.
|
||||
await this.commands.targetCommand.watchTargets({
|
||||
types: [this.commands.targetCommand.TYPES.FRAME],
|
||||
onAvailable: this._onTargetAvailable,
|
||||
});
|
||||
|
||||
// Fire this once incase we have an in-progress recording (console profile)
|
||||
// that caused this start up, and no state change yet, so we can highlight the
|
||||
// tab if we need.
|
||||
this._checkRecordingStatus();
|
||||
|
||||
this._opening = new Promise(resolve => {
|
||||
resolve(this);
|
||||
});
|
||||
return this._opening;
|
||||
},
|
||||
|
||||
// DevToolPanel API
|
||||
|
||||
get target() {
|
||||
return this.toolbox.target;
|
||||
},
|
||||
|
||||
async destroy() {
|
||||
// Make sure this panel is not already destroyed.
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { PerformanceController, PerformanceView, EVENTS } = this.panelWin;
|
||||
PerformanceController.off(
|
||||
EVENTS.RECORDING_ADDED,
|
||||
this._checkRecordingStatus
|
||||
);
|
||||
PerformanceController.off(
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
this._checkRecordingStatus
|
||||
);
|
||||
|
||||
this.commands.targetCommand.unwatchTargets({
|
||||
types: [this.commands.targetCommand.TYPES.FRAME],
|
||||
onAvailable: this._onTargetAvailable,
|
||||
});
|
||||
await PerformanceController.destroy();
|
||||
await PerformanceView.destroy();
|
||||
PerformanceController.disableFrontEventListeners();
|
||||
|
||||
this.emit("destroyed");
|
||||
this._destroyed = true;
|
||||
},
|
||||
|
||||
_checkRecordingStatus: function() {
|
||||
if (this.panelWin.PerformanceController.isRecording()) {
|
||||
this.toolbox.highlightTool("performance");
|
||||
} else {
|
||||
this.toolbox.unhighlightTool("performance");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This function executes actual logic for the target-switching.
|
||||
*
|
||||
* @param {TargetFront} - targetFront
|
||||
* As we are watching only FRAME type for this panel,
|
||||
* the target should be a instance of WindowGlobalTarget.
|
||||
*/
|
||||
async _handleTargetAvailable({ targetFront }) {
|
||||
if (targetFront.isTopLevel) {
|
||||
const { PerformanceController, PerformanceView } = this.panelWin;
|
||||
const performanceFront = await targetFront.getFront("performance");
|
||||
|
||||
if (!this._isPanelInitialized) {
|
||||
await PerformanceController.initialize(
|
||||
this.toolbox,
|
||||
targetFront,
|
||||
performanceFront
|
||||
);
|
||||
await PerformanceView.initialize();
|
||||
PerformanceController.enableFrontEventListeners();
|
||||
this._isPanelInitialized = true;
|
||||
} else {
|
||||
const isRecording = PerformanceController.isRecording();
|
||||
if (isRecording) {
|
||||
await PerformanceController.stopRecording();
|
||||
}
|
||||
|
||||
PerformanceView.resetBufferStatus();
|
||||
PerformanceController.updateFronts(targetFront, performanceFront);
|
||||
|
||||
if (isRecording) {
|
||||
await PerformanceController.startRecording();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This function is called for every target is available.
|
||||
*/
|
||||
_onTargetAvailable(parameters) {
|
||||
// As this function is called asynchronous, while previous processing, this might be
|
||||
// called. Thus, we wait until finishing previous one before starting next.
|
||||
this._targetAvailablePromise = this._targetAvailablePromise.then(() =>
|
||||
this._handleTargetAvailable(parameters)
|
||||
);
|
||||
|
||||
return this._targetAvailablePromise;
|
||||
},
|
||||
};
|
@ -1,542 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* globals $ */
|
||||
"use strict";
|
||||
|
||||
// Events emitted by various objects in the panel.
|
||||
const EVENTS = require("devtools/client/performance/events");
|
||||
|
||||
const Services = require("Services");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const flags = require("devtools/shared/flags");
|
||||
const {
|
||||
getTheme,
|
||||
addThemeObserver,
|
||||
removeThemeObserver,
|
||||
} = require("devtools/client/shared/theme");
|
||||
|
||||
// Logic modules
|
||||
const {
|
||||
PerformanceTelemetry,
|
||||
} = require("devtools/client/performance/modules/logic/telemetry");
|
||||
const {
|
||||
PerformanceView,
|
||||
} = require("devtools/client/performance/performance-view");
|
||||
const { DetailsView } = require("devtools/client/performance/views/details");
|
||||
const {
|
||||
RecordingsView,
|
||||
} = require("devtools/client/performance/views/recordings");
|
||||
const { ToolbarView } = require("devtools/client/performance/views/toolbar");
|
||||
|
||||
/**
|
||||
* Functions handling target-related lifetime events and
|
||||
* UI interaction.
|
||||
*/
|
||||
const PerformanceController = {
|
||||
_recordings: [],
|
||||
_currentRecording: null,
|
||||
|
||||
/**
|
||||
* Listen for events emitted by the current tab target and
|
||||
* main UI events.
|
||||
*/
|
||||
async initialize(toolbox, targetFront, performanceFront) {
|
||||
this.toolbox = toolbox;
|
||||
this.target = targetFront;
|
||||
this.front = performanceFront;
|
||||
|
||||
this._telemetry = new PerformanceTelemetry(this);
|
||||
this.startRecording = this.startRecording.bind(this);
|
||||
this.stopRecording = this.stopRecording.bind(this);
|
||||
this.importRecording = this.importRecording.bind(this);
|
||||
this.exportRecording = this.exportRecording.bind(this);
|
||||
this.clearRecordings = this.clearRecordings.bind(this);
|
||||
this._onRecordingSelectFromView = this._onRecordingSelectFromView.bind(
|
||||
this
|
||||
);
|
||||
this._onPrefChanged = this._onPrefChanged.bind(this);
|
||||
this._onThemeChanged = this._onThemeChanged.bind(this);
|
||||
this._onDetailsViewSelected = this._onDetailsViewSelected.bind(this);
|
||||
this._onProfilerStatus = this._onProfilerStatus.bind(this);
|
||||
this._onRecordingStarted = this._emitRecordingStateChange.bind(
|
||||
this,
|
||||
"recording-started"
|
||||
);
|
||||
this._onRecordingStopping = this._emitRecordingStateChange.bind(
|
||||
this,
|
||||
"recording-stopping"
|
||||
);
|
||||
this._onRecordingStopped = this._emitRecordingStateChange.bind(
|
||||
this,
|
||||
"recording-stopped"
|
||||
);
|
||||
|
||||
// Store data regarding if e10s is enabled.
|
||||
this._e10s = Services.appinfo.browserTabsRemoteAutostart;
|
||||
this._setMultiprocessAttributes();
|
||||
|
||||
this._prefs = require("devtools/client/performance/modules/global").PREFS;
|
||||
this._prefs.registerObserver();
|
||||
this._prefs.on("pref-changed", this._onPrefChanged);
|
||||
|
||||
ToolbarView.on(EVENTS.UI_PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
PerformanceView.on(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
||||
PerformanceView.on(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings);
|
||||
RecordingsView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
RecordingsView.on(
|
||||
EVENTS.UI_RECORDING_SELECTED,
|
||||
this._onRecordingSelectFromView
|
||||
);
|
||||
DetailsView.on(
|
||||
EVENTS.UI_DETAILS_VIEW_SELECTED,
|
||||
this._onDetailsViewSelected
|
||||
);
|
||||
|
||||
addThemeObserver(this._onThemeChanged);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove events handled by the PerformanceController
|
||||
*/
|
||||
destroy: function() {
|
||||
this._prefs.off("pref-changed", this._onPrefChanged);
|
||||
this._prefs.unregisterObserver();
|
||||
|
||||
ToolbarView.off(EVENTS.UI_PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
PerformanceView.off(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
||||
PerformanceView.off(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings);
|
||||
RecordingsView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
RecordingsView.off(
|
||||
EVENTS.UI_RECORDING_SELECTED,
|
||||
this._onRecordingSelectFromView
|
||||
);
|
||||
DetailsView.off(
|
||||
EVENTS.UI_DETAILS_VIEW_SELECTED,
|
||||
this._onDetailsViewSelected
|
||||
);
|
||||
|
||||
removeThemeObserver(this._onThemeChanged);
|
||||
|
||||
this._telemetry.destroy();
|
||||
},
|
||||
|
||||
updateFronts(targetFront, performanceFront) {
|
||||
this.target = targetFront;
|
||||
this.front = performanceFront;
|
||||
this.enableFrontEventListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables front event listeners.
|
||||
*
|
||||
* The rationale behind this is given by the async intialization of all the
|
||||
* frontend components. Even though the panel is considered "open" only after
|
||||
* both the controller and the view are created, and even though their
|
||||
* initialization is sequential (controller, then view), the controller might
|
||||
* start handling backend events before the view finishes if the event
|
||||
* listeners are added too soon.
|
||||
*/
|
||||
enableFrontEventListeners: function() {
|
||||
this.front.on("profiler-status", this._onProfilerStatus);
|
||||
this.front.on("recording-started", this._onRecordingStarted);
|
||||
this.front.on("recording-stopping", this._onRecordingStopping);
|
||||
this.front.on("recording-stopped", this._onRecordingStopped);
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables front event listeners.
|
||||
*/
|
||||
disableFrontEventListeners: function() {
|
||||
this.front.off("profiler-status", this._onProfilerStatus);
|
||||
this.front.off("recording-started", this._onRecordingStarted);
|
||||
this.front.off("recording-stopping", this._onRecordingStopping);
|
||||
this.front.off("recording-stopped", this._onRecordingStopped);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current devtools theme.
|
||||
*/
|
||||
getTheme: function() {
|
||||
return getTheme();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a boolean preference setting from `prefName` via the underlying
|
||||
* OptionsView in the ToolbarView. This preference is guaranteed to be
|
||||
* displayed in the UI.
|
||||
*
|
||||
* @param string prefName
|
||||
* @return boolean
|
||||
*/
|
||||
getOption: function(prefName) {
|
||||
return ToolbarView.optionsView.getPref(prefName);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a preference setting from `prefName`. This preference can be of
|
||||
* any type and might not be displayed in the UI.
|
||||
*
|
||||
* @param string prefName
|
||||
* @return any
|
||||
*/
|
||||
getPref: function(prefName) {
|
||||
return this._prefs[prefName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a preference setting from `prefName`. This preference can be of
|
||||
* any type and might not be displayed in the UI.
|
||||
*
|
||||
* @param string prefName
|
||||
* @param any prefValue
|
||||
*/
|
||||
setPref: function(prefName, prefValue) {
|
||||
this._prefs[prefName] = prefValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether or not a new recording is supported by the PerformanceFront.
|
||||
* @return Promise:boolean
|
||||
*/
|
||||
async canCurrentlyRecord() {
|
||||
const hasActor = await this.target.hasActor("performance");
|
||||
if (!hasActor) {
|
||||
return true;
|
||||
}
|
||||
return (await this.front.canCurrentlyRecord()).success;
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts recording with the PerformanceFront.
|
||||
*/
|
||||
async startRecording() {
|
||||
const options = {
|
||||
withMarkers: true,
|
||||
withTicks: this.getOption("enable-framerate"),
|
||||
withMemory: this.getOption("enable-memory"),
|
||||
withFrames: true,
|
||||
withGCEvents: true,
|
||||
withAllocations: this.getOption("enable-allocations"),
|
||||
allocationsSampleProbability: this.getPref("memory-sample-probability"),
|
||||
allocationsMaxLogLength: this.getPref("memory-max-log-length"),
|
||||
bufferSize: this.getPref("profiler-buffer-size"),
|
||||
sampleFrequency: this.getPref("profiler-sample-frequency"),
|
||||
};
|
||||
|
||||
const recordingStarted = await this.front.startRecording(options);
|
||||
|
||||
// In some cases, like when the target has a private browsing tab,
|
||||
// recording is not currently supported because of the profiler module.
|
||||
// Present a notification in this case alerting the user of this issue.
|
||||
if (!recordingStarted) {
|
||||
this.emit(EVENTS.BACKEND_FAILED_AFTER_RECORDING_START);
|
||||
PerformanceView.setState("unavailable");
|
||||
} else {
|
||||
this.emit(EVENTS.BACKEND_READY_AFTER_RECORDING_START);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops recording with the PerformanceFront.
|
||||
*/
|
||||
async stopRecording() {
|
||||
const recording = this.getLatestManualRecording();
|
||||
|
||||
if (!this.front.isDestroyed()) {
|
||||
await this.front.stopRecording(recording);
|
||||
} else {
|
||||
// As the front was destroyed, we do stop sequence manually without the actor.
|
||||
recording._recording = false;
|
||||
recording._completed = true;
|
||||
await this._onRecordingStopped(recording);
|
||||
}
|
||||
|
||||
this.emit(EVENTS.BACKEND_READY_AFTER_RECORDING_STOP);
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves the given recording to a file. Emits `EVENTS.RECORDING_EXPORTED`
|
||||
* when the file was saved.
|
||||
*
|
||||
* @param PerformanceRecording recording
|
||||
* The model that holds the recording data.
|
||||
* @param nsIFile file
|
||||
* The file to stream the data into.
|
||||
*/
|
||||
async exportRecording(recording, file) {
|
||||
await recording.exportRecording(file);
|
||||
this.emit(EVENTS.RECORDING_EXPORTED, recording, file);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears all completed recordings from the list as well as the current non-console
|
||||
* recording. Emits `EVENTS.RECORDING_DELETED` when complete so other components can
|
||||
* clean up.
|
||||
*/
|
||||
async clearRecordings() {
|
||||
for (let i = this._recordings.length - 1; i >= 0; i--) {
|
||||
const model = this._recordings[i];
|
||||
if (!model.isConsole() && model.isRecording()) {
|
||||
await this.stopRecording();
|
||||
}
|
||||
// If last recording is not recording, but finalizing itself,
|
||||
// wait for that to finish
|
||||
if (!model.isRecording() && !model.isCompleted()) {
|
||||
await this.waitForStateChangeOnRecording(model, "recording-stopped");
|
||||
}
|
||||
// If recording is completed,
|
||||
// clean it up from UI and remove it from the _recordings array.
|
||||
if (model.isCompleted()) {
|
||||
this.emit(EVENTS.RECORDING_DELETED, model);
|
||||
this._recordings.splice(i, 1);
|
||||
}
|
||||
}
|
||||
if (this._recordings.length > 0) {
|
||||
if (!this._recordings.includes(this.getCurrentRecording())) {
|
||||
this.setCurrentRecording(this._recordings[0]);
|
||||
}
|
||||
} else {
|
||||
this.setCurrentRecording(null);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads a recording from a file, adding it to the recordings list. Emits
|
||||
* `EVENTS.RECORDING_IMPORTED` when the file was loaded.
|
||||
*
|
||||
* @param nsIFile file
|
||||
* The file to import the data from.
|
||||
*/
|
||||
async importRecording(file) {
|
||||
const recording = await this.front.importRecording(file);
|
||||
this._addRecordingIfUnknown(recording);
|
||||
|
||||
this.emit(EVENTS.RECORDING_IMPORTED, recording);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the currently active PerformanceRecording. Should rarely be called directly,
|
||||
* as RecordingsView handles this when manually selected a recording item. Exceptions
|
||||
* are when clearing the view.
|
||||
* @param PerformanceRecording recording
|
||||
*/
|
||||
setCurrentRecording: function(recording) {
|
||||
if (this._currentRecording !== recording) {
|
||||
this._currentRecording = recording;
|
||||
this.emit(EVENTS.RECORDING_SELECTED, recording);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the currently active PerformanceRecording.
|
||||
* @return PerformanceRecording
|
||||
*/
|
||||
getCurrentRecording: function() {
|
||||
return this._currentRecording;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get most recently added recording that was triggered manually (via UI).
|
||||
* @return PerformanceRecording
|
||||
*/
|
||||
getLatestManualRecording: function() {
|
||||
for (let i = this._recordings.length - 1; i >= 0; i--) {
|
||||
const model = this._recordings[i];
|
||||
if (!model.isConsole() && !model.isImported()) {
|
||||
return this._recordings[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired from RecordingsView, we listen on the PerformanceController so we can
|
||||
* set it here and re-emit on the controller, where all views can listen.
|
||||
*/
|
||||
_onRecordingSelectFromView: function(recording) {
|
||||
this.setCurrentRecording(recording);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when the ToolbarView fires a PREF_CHANGED event.
|
||||
* with the value.
|
||||
*/
|
||||
_onPrefChanged: function(prefName, prefValue) {
|
||||
this.emit(EVENTS.PREF_CHANGED, prefName, prefValue);
|
||||
},
|
||||
|
||||
/*
|
||||
* Called when the developer tools theme changes.
|
||||
*/
|
||||
_onThemeChanged: function(newValue) {
|
||||
this.emit(EVENTS.THEME_CHANGED, newValue);
|
||||
},
|
||||
|
||||
_onProfilerStatus: function(status) {
|
||||
this.emit(EVENTS.RECORDING_PROFILER_STATUS_UPDATE, status);
|
||||
},
|
||||
|
||||
_emitRecordingStateChange(eventName, recordingModel) {
|
||||
this._addRecordingIfUnknown(recordingModel);
|
||||
this.emit(EVENTS.RECORDING_STATE_CHANGE, eventName, recordingModel);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores a recording internally.
|
||||
*
|
||||
* @param {PerformanceRecordingFront} recording
|
||||
*/
|
||||
_addRecordingIfUnknown: function(recording) {
|
||||
if (!this._recordings.includes(recording)) {
|
||||
this._recordings.push(recording);
|
||||
this.emit(EVENTS.RECORDING_ADDED, recording);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a recording and returns a value between 0 and 1 indicating how much
|
||||
* of the buffer is used.
|
||||
*/
|
||||
getBufferUsageForRecording: function(recording) {
|
||||
return this.front.getBufferUsageForRecording(recording);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating if any recordings are currently in progress or not.
|
||||
*/
|
||||
isRecording: function() {
|
||||
return this._recordings.some(r => r.isRecording());
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the internal store of recording models.
|
||||
*/
|
||||
getRecordings: function() {
|
||||
return this._recordings;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns traits from the front.
|
||||
*/
|
||||
getTraits: function() {
|
||||
return this.front.traits;
|
||||
},
|
||||
|
||||
viewSourceInDebugger(url, line, column) {
|
||||
// Currently, the line and column values are strings, so we have to convert
|
||||
// them to numbers before passing them on to the toolbox.
|
||||
return this.toolbox.viewSourceInDebugger(url, +line, +column);
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility method taking a string or an array of strings of feature names (like
|
||||
* "withAllocations" or "withMarkers"), and returns whether or not the current
|
||||
* recording supports that feature, based off of UI preferences and server support.
|
||||
*
|
||||
* @option {Array<string>|string} features
|
||||
* A string or array of strings indicating what configuration is needed on the
|
||||
* recording model, like `withTicks`, or `withMemory`.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
isFeatureSupported: function(features) {
|
||||
if (!features) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const recording = this.getCurrentRecording();
|
||||
if (!recording) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const config = recording.getConfiguration();
|
||||
return [].concat(features).every(f => config[f]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes an array of PerformanceRecordingFronts and adds them to the internal
|
||||
* store of the UI. Used by the toolbox to lazily seed recordings that
|
||||
* were observed before the panel was loaded in the scenario where `console.profile()`
|
||||
* is used before the tool is loaded.
|
||||
*
|
||||
* @param {Array<PerformanceRecordingFront>} recordings
|
||||
*/
|
||||
populateWithRecordings: function(recordings = []) {
|
||||
for (const recording of recordings) {
|
||||
PerformanceController._addRecordingIfUnknown(recording);
|
||||
}
|
||||
this.emit(EVENTS.RECORDINGS_SEEDED);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object with `supported` and `enabled` properties indicating
|
||||
* whether or not the platform is capable of turning on e10s and whether or not
|
||||
* it's already enabled, respectively.
|
||||
*
|
||||
* @return {object}
|
||||
*/
|
||||
getMultiprocessStatus: function() {
|
||||
// If testing, set enabled to true so we have realtime rendering tests
|
||||
// in non-e10s. This function is overridden wholesale in tests
|
||||
// when we want to test multiprocess support
|
||||
// specifically.
|
||||
if (flags.testing) {
|
||||
return { enabled: true };
|
||||
}
|
||||
// This is only checked on tool startup -- requires a restart if
|
||||
// e10s subsequently enabled.
|
||||
const enabled = this._e10s;
|
||||
return { enabled };
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a PerformanceRecording and a state, and waits for
|
||||
* the event to be emitted from the front for that recording.
|
||||
*
|
||||
* @param {PerformanceRecordingFront} recording
|
||||
* @param {string} expectedState
|
||||
* @return {Promise}
|
||||
*/
|
||||
async waitForStateChangeOnRecording(recording, expectedState) {
|
||||
await new Promise(resolve => {
|
||||
this.on(EVENTS.RECORDING_STATE_CHANGE, function handler(state, model) {
|
||||
if (state === expectedState && model === recording) {
|
||||
this.off(EVENTS.RECORDING_STATE_CHANGE, handler);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called on init, sets an `e10s` attribute on the main view container with
|
||||
* "disabled" if e10s is possible on the platform and just not on, or "unsupported"
|
||||
* if e10s is not possible on the platform. If e10s is on, no attribute is set.
|
||||
*/
|
||||
_setMultiprocessAttributes: function() {
|
||||
const { enabled } = this.getMultiprocessStatus();
|
||||
if (!enabled) {
|
||||
$("#performance-view").setAttribute("e10s", "disabled");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Pipes EVENTS.UI_DETAILS_VIEW_SELECTED to the PerformanceController.
|
||||
*/
|
||||
_onDetailsViewSelected: function(...data) {
|
||||
this.emit(EVENTS.UI_DETAILS_VIEW_SELECTED, ...data);
|
||||
},
|
||||
|
||||
toString: () => "[object PerformanceController]",
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenient way of emitting events from the controller.
|
||||
*/
|
||||
EventEmitter.decorate(PerformanceController);
|
||||
exports.PerformanceController = PerformanceController;
|
@ -1,490 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* globals $, $$, PerformanceController */
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
|
||||
const RecordingControls = React.createFactory(
|
||||
require("devtools/client/performance/components/RecordingControls")
|
||||
);
|
||||
const RecordingButton = React.createFactory(
|
||||
require("devtools/client/performance/components/RecordingButton")
|
||||
);
|
||||
|
||||
const EVENTS = require("devtools/client/performance/events");
|
||||
const PerformanceUtils = require("devtools/client/performance/modules/utils");
|
||||
const { DetailsView } = require("devtools/client/performance/views/details");
|
||||
const { OverviewView } = require("devtools/client/performance/views/overview");
|
||||
const {
|
||||
RecordingsView,
|
||||
} = require("devtools/client/performance/views/recordings");
|
||||
const { ToolbarView } = require("devtools/client/performance/views/toolbar");
|
||||
|
||||
const { L10N } = require("devtools/client/performance/modules/global");
|
||||
/**
|
||||
* Master view handler for the performance tool.
|
||||
*/
|
||||
var PerformanceView = {
|
||||
_state: null,
|
||||
|
||||
// Set to true if the front emits a "buffer-status" event, indicating
|
||||
// that the server has support for determining buffer status.
|
||||
_bufferStatusSupported: false,
|
||||
|
||||
// Mapping of state to selectors for different properties and their values,
|
||||
// from the main profiler view. Used in `PerformanceView.setState()`
|
||||
states: {
|
||||
unavailable: [
|
||||
{
|
||||
sel: "#performance-view",
|
||||
opt: "selectedPanel",
|
||||
val: () => $("#unavailable-notice"),
|
||||
},
|
||||
{
|
||||
sel: "#performance-view-content",
|
||||
opt: "hidden",
|
||||
val: () => true,
|
||||
},
|
||||
],
|
||||
empty: [
|
||||
{
|
||||
sel: "#performance-view",
|
||||
opt: "selectedPanel",
|
||||
val: () => $("#empty-notice"),
|
||||
},
|
||||
{
|
||||
sel: "#performance-view-content",
|
||||
opt: "hidden",
|
||||
val: () => true,
|
||||
},
|
||||
],
|
||||
recording: [
|
||||
{
|
||||
sel: "#performance-view",
|
||||
opt: "selectedPanel",
|
||||
val: () => $("#performance-view-content"),
|
||||
},
|
||||
{
|
||||
sel: "#performance-view-content",
|
||||
opt: "hidden",
|
||||
val: () => false,
|
||||
},
|
||||
{
|
||||
sel: "#details-pane-container",
|
||||
opt: "selectedPanel",
|
||||
val: () => $("#recording-notice"),
|
||||
},
|
||||
],
|
||||
"console-recording": [
|
||||
{
|
||||
sel: "#performance-view",
|
||||
opt: "selectedPanel",
|
||||
val: () => $("#performance-view-content"),
|
||||
},
|
||||
{
|
||||
sel: "#performance-view-content",
|
||||
opt: "hidden",
|
||||
val: () => false,
|
||||
},
|
||||
{
|
||||
sel: "#details-pane-container",
|
||||
opt: "selectedPanel",
|
||||
val: () => $("#console-recording-notice"),
|
||||
},
|
||||
],
|
||||
recorded: [
|
||||
{
|
||||
sel: "#performance-view",
|
||||
opt: "selectedPanel",
|
||||
val: () => $("#performance-view-content"),
|
||||
},
|
||||
{
|
||||
sel: "#performance-view-content",
|
||||
opt: "hidden",
|
||||
val: () => false,
|
||||
},
|
||||
{
|
||||
sel: "#details-pane-container",
|
||||
opt: "selectedPanel",
|
||||
val: () => $("#details-pane"),
|
||||
},
|
||||
],
|
||||
loading: [
|
||||
{
|
||||
sel: "#performance-view",
|
||||
opt: "selectedPanel",
|
||||
val: () => $("#performance-view-content"),
|
||||
},
|
||||
{
|
||||
sel: "#performance-view-content",
|
||||
opt: "hidden",
|
||||
val: () => false,
|
||||
},
|
||||
{
|
||||
sel: "#details-pane-container",
|
||||
opt: "selectedPanel",
|
||||
val: () => $("#loading-notice"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up the view with event binding and main subviews.
|
||||
*/
|
||||
async initialize() {
|
||||
this._onRecordButtonClick = this._onRecordButtonClick.bind(this);
|
||||
this._onImportButtonClick = this._onImportButtonClick.bind(this);
|
||||
this._onClearButtonClick = this._onClearButtonClick.bind(this);
|
||||
this._onRecordingSelected = this._onRecordingSelected.bind(this);
|
||||
this._onProfilerStatusUpdated = this._onProfilerStatusUpdated.bind(this);
|
||||
this._onRecordingStateChange = this._onRecordingStateChange.bind(this);
|
||||
this._onNewRecordingFailed = this._onNewRecordingFailed.bind(this);
|
||||
|
||||
// Bind to controller events to unlock the record button
|
||||
PerformanceController.on(
|
||||
EVENTS.RECORDING_SELECTED,
|
||||
this._onRecordingSelected
|
||||
);
|
||||
PerformanceController.on(
|
||||
EVENTS.RECORDING_PROFILER_STATUS_UPDATE,
|
||||
this._onProfilerStatusUpdated
|
||||
);
|
||||
PerformanceController.on(
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
this._onRecordingStateChange
|
||||
);
|
||||
PerformanceController.on(
|
||||
EVENTS.RECORDING_ADDED,
|
||||
this._onRecordingStateChange
|
||||
);
|
||||
PerformanceController.on(
|
||||
EVENTS.BACKEND_FAILED_AFTER_RECORDING_START,
|
||||
this._onNewRecordingFailed
|
||||
);
|
||||
|
||||
if (await PerformanceController.canCurrentlyRecord()) {
|
||||
this.setState("empty");
|
||||
} else {
|
||||
this.setState("unavailable");
|
||||
}
|
||||
|
||||
// Initialize the ToolbarView first, because other views may need access
|
||||
// to the OptionsView via the controller, to read prefs.
|
||||
await ToolbarView.initialize();
|
||||
await RecordingsView.initialize();
|
||||
await OverviewView.initialize();
|
||||
await DetailsView.initialize();
|
||||
|
||||
// DE-XUL: Begin migrating the toolbar to React. Temporarily hold state here.
|
||||
this._recordingControlsState = {
|
||||
onRecordButtonClick: this._onRecordButtonClick,
|
||||
onImportButtonClick: this._onImportButtonClick,
|
||||
onClearButtonClick: this._onClearButtonClick,
|
||||
isRecording: false,
|
||||
isDisabled: false,
|
||||
};
|
||||
// Mount to an HTML element.
|
||||
const { createHtmlMount } = PerformanceUtils;
|
||||
this._recordingControlsMount = createHtmlMount(
|
||||
$("#recording-controls-mount")
|
||||
);
|
||||
this._recordingButtonsMounts = Array.from(
|
||||
$$(".recording-button-mount")
|
||||
).map(createHtmlMount);
|
||||
|
||||
this._renderRecordingControls();
|
||||
},
|
||||
|
||||
/**
|
||||
* DE-XUL: Render the recording controls and buttons using React.
|
||||
*/
|
||||
_renderRecordingControls: function() {
|
||||
ReactDOM.render(
|
||||
RecordingControls(this._recordingControlsState),
|
||||
this._recordingControlsMount
|
||||
);
|
||||
for (const button of this._recordingButtonsMounts) {
|
||||
ReactDOM.render(RecordingButton(this._recordingControlsState), button);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Unbinds events and destroys subviews.
|
||||
*/
|
||||
async destroy() {
|
||||
PerformanceController.off(
|
||||
EVENTS.RECORDING_SELECTED,
|
||||
this._onRecordingSelected
|
||||
);
|
||||
PerformanceController.off(
|
||||
EVENTS.RECORDING_PROFILER_STATUS_UPDATE,
|
||||
this._onProfilerStatusUpdated
|
||||
);
|
||||
PerformanceController.off(
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
this._onRecordingStateChange
|
||||
);
|
||||
PerformanceController.off(
|
||||
EVENTS.RECORDING_ADDED,
|
||||
this._onRecordingStateChange
|
||||
);
|
||||
PerformanceController.off(
|
||||
EVENTS.BACKEND_FAILED_AFTER_RECORDING_START,
|
||||
this._onNewRecordingFailed
|
||||
);
|
||||
|
||||
await ToolbarView.destroy();
|
||||
await RecordingsView.destroy();
|
||||
await OverviewView.destroy();
|
||||
await DetailsView.destroy();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the state of the profiler view. Possible options are "unavailable",
|
||||
* "empty", "recording", "console-recording", "recorded".
|
||||
*/
|
||||
setState: function(state) {
|
||||
// Make sure that the focus isn't captured on a hidden iframe. This fixes a
|
||||
// XUL bug where shortcuts stop working.
|
||||
const iframes = window.document.querySelectorAll("iframe");
|
||||
for (const iframe of iframes) {
|
||||
iframe.blur();
|
||||
}
|
||||
window.focus();
|
||||
|
||||
const viewConfig = this.states[state];
|
||||
if (!viewConfig) {
|
||||
throw new Error(`Invalid state for PerformanceView: ${state}`);
|
||||
}
|
||||
for (const { sel, opt, val } of viewConfig) {
|
||||
for (const el of $$(sel)) {
|
||||
el[opt] = val();
|
||||
}
|
||||
}
|
||||
|
||||
this._state = state;
|
||||
|
||||
if (state === "console-recording") {
|
||||
const recording = PerformanceController.getCurrentRecording();
|
||||
let label = recording.getLabel() || "";
|
||||
|
||||
// Wrap the label in quotes if it exists for the commands.
|
||||
label = label ? `"${label}"` : "";
|
||||
|
||||
const startCommand = $(
|
||||
".console-profile-recording-notice .console-profile-command"
|
||||
);
|
||||
const stopCommand = $(
|
||||
".console-profile-stop-notice .console-profile-command"
|
||||
);
|
||||
|
||||
startCommand.value = `console.profile(${label})`;
|
||||
stopCommand.value = `console.profileEnd(${label})`;
|
||||
}
|
||||
|
||||
this.updateBufferStatus();
|
||||
this.emit(EVENTS.UI_STATE_CHANGED, state);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the state of the PerformanceView.
|
||||
*/
|
||||
getState: function() {
|
||||
return this._state;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset the displayed buffer status.
|
||||
* Called for every target-switching.
|
||||
*/
|
||||
resetBufferStatus() {
|
||||
this._bufferStatusSupported = false;
|
||||
$("#details-pane-container").removeAttribute("buffer-status");
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the displayed buffer status.
|
||||
*/
|
||||
updateBufferStatus: function() {
|
||||
// If we've never seen a "buffer-status" event from the front, ignore
|
||||
// and keep the buffer elements hidden.
|
||||
if (!this._bufferStatusSupported) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recording = PerformanceController.getCurrentRecording();
|
||||
if (!recording || !recording.isRecording()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bufferUsage =
|
||||
PerformanceController.getBufferUsageForRecording(recording) || 0;
|
||||
|
||||
// Normalize to a percentage value
|
||||
const percent = Math.floor(bufferUsage * 100);
|
||||
|
||||
const $container = $("#details-pane-container");
|
||||
const $bufferLabel = $(".buffer-status-message", $container.selectedPanel);
|
||||
|
||||
// Be a little flexible on the buffer status, although not sure how
|
||||
// this could happen, as RecordingModel clamps.
|
||||
if (percent >= 99) {
|
||||
$container.setAttribute("buffer-status", "full");
|
||||
} else {
|
||||
$container.setAttribute("buffer-status", "in-progress");
|
||||
}
|
||||
|
||||
$bufferLabel.value = L10N.getFormatStr("profiler.bufferFull", percent);
|
||||
this.emit(EVENTS.UI_RECORDING_PROFILER_STATUS_RENDERED, percent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the `locked` attribute on the record buttons based
|
||||
* on `lock`.
|
||||
*
|
||||
* @param {boolean} lock
|
||||
*/
|
||||
_lockRecordButtons: function(lock) {
|
||||
this._recordingControlsState.isLocked = lock;
|
||||
this._renderRecordingControls();
|
||||
},
|
||||
|
||||
/*
|
||||
* Toggles the `checked` attribute on the record buttons based
|
||||
* on `activate`.
|
||||
*
|
||||
* @param {boolean} activate
|
||||
*/
|
||||
_toggleRecordButtons: function(activate) {
|
||||
this._recordingControlsState.isRecording = activate;
|
||||
this._renderRecordingControls();
|
||||
},
|
||||
|
||||
/**
|
||||
* When a recording has started.
|
||||
*/
|
||||
_onRecordingStateChange: function() {
|
||||
const currentRecording = PerformanceController.getCurrentRecording();
|
||||
const recordings = PerformanceController.getRecordings();
|
||||
|
||||
this._toggleRecordButtons(
|
||||
!!recordings.find(r => !r.isConsole() && r.isRecording())
|
||||
);
|
||||
this._lockRecordButtons(
|
||||
!!recordings.find(r => !r.isConsole() && r.isFinalizing())
|
||||
);
|
||||
|
||||
if (currentRecording && currentRecording.isFinalizing()) {
|
||||
this.setState("loading");
|
||||
}
|
||||
if (currentRecording && currentRecording.isCompleted()) {
|
||||
this.setState("recorded");
|
||||
}
|
||||
if (currentRecording && currentRecording.isRecording()) {
|
||||
this.updateBufferStatus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When starting a recording has failed.
|
||||
*/
|
||||
_onNewRecordingFailed: function() {
|
||||
this._lockRecordButtons(false);
|
||||
this._toggleRecordButtons(false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for clicking the clear button.
|
||||
*/
|
||||
_onClearButtonClick: function(e) {
|
||||
this.emit(EVENTS.UI_CLEAR_RECORDINGS);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for clicking the record button.
|
||||
*/
|
||||
_onRecordButtonClick: function(e) {
|
||||
if (this._recordingControlsState.isRecording) {
|
||||
this.emit(EVENTS.UI_STOP_RECORDING);
|
||||
} else {
|
||||
this._lockRecordButtons(true);
|
||||
this._toggleRecordButtons(true);
|
||||
this.emit(EVENTS.UI_START_RECORDING);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for clicking the import button.
|
||||
*/
|
||||
_onImportButtonClick: function(e) {
|
||||
const fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(
|
||||
window,
|
||||
L10N.getStr("recordingsList.importDialogTitle"),
|
||||
Ci.nsIFilePicker.modeOpen
|
||||
);
|
||||
fp.appendFilter(
|
||||
L10N.getStr("recordingsList.saveDialogJSONFilter"),
|
||||
"*.json"
|
||||
);
|
||||
fp.appendFilter(L10N.getStr("recordingsList.saveDialogAllFilter"), "*.*");
|
||||
|
||||
fp.open(rv => {
|
||||
if (rv == Ci.nsIFilePicker.returnOK) {
|
||||
this.emit(EVENTS.UI_IMPORT_RECORDING, fp.file);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when a recording is selected. Used to toggle the profiler view state.
|
||||
*/
|
||||
_onRecordingSelected: function(recording) {
|
||||
if (!recording) {
|
||||
this.setState("empty");
|
||||
} else if (recording.isRecording() && recording.isConsole()) {
|
||||
this.setState("console-recording");
|
||||
} else if (recording.isRecording()) {
|
||||
this.setState("recording");
|
||||
} else {
|
||||
this.setState("recorded");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when the controller has updated information on the buffer's status.
|
||||
* Update the buffer status display if shown.
|
||||
*/
|
||||
_onProfilerStatusUpdated: function(profilerStatus) {
|
||||
// We only care about buffer status here, so check to see
|
||||
// if it has position.
|
||||
if (!profilerStatus || profilerStatus.position === void 0) {
|
||||
return;
|
||||
}
|
||||
// If this is our first buffer event, set the status and add a class
|
||||
if (!this._bufferStatusSupported) {
|
||||
this._bufferStatusSupported = true;
|
||||
$("#details-pane-container").setAttribute("buffer-status", "in-progress");
|
||||
}
|
||||
|
||||
if (!this.getState("recording") && !this.getState("console-recording")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateBufferStatus();
|
||||
},
|
||||
|
||||
toString: () => "[object PerformanceView]",
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenient way of emitting events from the view.
|
||||
*/
|
||||
EventEmitter.decorate(PerformanceView);
|
||||
|
||||
exports.PerformanceView = PerformanceView;
|
@ -1,7 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
// General rule from /.eslintrc.js only accept folders matching **/test*/browser*/
|
||||
// where is this folder doesn't match, so manually apply browser test config
|
||||
module.exports = {
|
||||
extends: ["plugin:mozilla/browser-test"],
|
||||
};
|
@ -1,146 +0,0 @@
|
||||
[DEFAULT]
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
skip-if = os == 'linux' && e10s && (asan || debug) # Bug 1254821
|
||||
support-files =
|
||||
doc_allocs.html
|
||||
doc_innerHTML.html
|
||||
doc_markers.html
|
||||
doc_simple-test.html
|
||||
doc_worker.html
|
||||
js_simpleWorker.js
|
||||
head.js
|
||||
!/devtools/client/shared/test/telemetry-test-helpers.js
|
||||
|
||||
[browser_aaa-run-first-leaktest.js]
|
||||
[browser_perf-button-states.js]
|
||||
[browser_perf-calltree-js-categories.js]
|
||||
skip-if = (os == 'win' && os_version == '10.0' && bits == 64 && !asan) # Bug 1466377
|
||||
[browser_perf-calltree-js-columns.js]
|
||||
[browser_perf-calltree-js-events.js]
|
||||
fail-if = a11y_checks # bug 1687790 call-tree-item is not accessible
|
||||
[browser_perf-calltree-memory-columns.js]
|
||||
[browser_perf-console-record-01.js]
|
||||
[browser_perf-console-record-02.js]
|
||||
[browser_perf-console-record-03.js]
|
||||
[browser_perf-console-record-04.js]
|
||||
[browser_perf-console-record-05.js]
|
||||
[browser_perf-console-record-06.js]
|
||||
[browser_perf-console-record-07.js]
|
||||
[browser_perf-console-record-08.js]
|
||||
[browser_perf-console-record-09.js]
|
||||
[browser_perf-details-01-toggle.js]
|
||||
[browser_perf-details-02-utility-fun.js]
|
||||
[browser_perf-details-03-without-allocations.js]
|
||||
[browser_perf-details-04-toolbar-buttons.js]
|
||||
[browser_perf-details-05-preserve-view.js]
|
||||
[browser_perf-details-06-rerender-on-selection.js]
|
||||
[browser_perf-details-07-bleed-events.js]
|
||||
[browser_perf-details-render-00-waterfall.js]
|
||||
[browser_perf-details-render-01-js-calltree.js]
|
||||
[browser_perf-details-render-02-js-flamegraph.js]
|
||||
[browser_perf-details-render-03-memory-calltree.js]
|
||||
[browser_perf-details-render-04-memory-flamegraph.js]
|
||||
[browser_perf-docload.js]
|
||||
[browser_perf-fission-switch-target.js]
|
||||
[browser_perf-highlighted.js]
|
||||
[browser_perf-loading-01.js]
|
||||
[browser_perf-loading-02.js]
|
||||
[browser_perf-marker-details.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_perf-options-01-toggle-throw.js]
|
||||
[browser_perf-options-02-toggle-throw-alt.js]
|
||||
[browser_perf-options-03-toggle-meta.js]
|
||||
[browser_perf-options-enable-framerate-01.js]
|
||||
[browser_perf-options-enable-framerate-02.js]
|
||||
[browser_perf-options-enable-memory-01.js]
|
||||
[browser_perf-options-enable-memory-02.js]
|
||||
[browser_perf-options-flatten-tree-recursion-01.js]
|
||||
[browser_perf-options-flatten-tree-recursion-02.js]
|
||||
[browser_perf-options-invert-call-tree-01.js]
|
||||
[browser_perf-options-invert-call-tree-02.js]
|
||||
[browser_perf-options-invert-flame-graph-01.js]
|
||||
[browser_perf-options-invert-flame-graph-02.js]
|
||||
[browser_perf-options-propagate-allocations.js]
|
||||
[browser_perf-options-propagate-profiler.js]
|
||||
[browser_perf-options-show-idle-blocks-01.js]
|
||||
[browser_perf-options-show-idle-blocks-02.js]
|
||||
[browser_perf-options-show-jit-optimizations.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_perf-options-show-platform-data-01.js]
|
||||
[browser_perf-options-show-platform-data-02.js]
|
||||
[browser_perf-overview-render-01.js]
|
||||
[browser_perf-overview-render-02.js]
|
||||
[browser_perf-overview-render-03.js]
|
||||
[browser_perf-overview-render-04.js]
|
||||
[browser_perf-overview-selection-01.js]
|
||||
[browser_perf-overview-selection-02.js]
|
||||
[browser_perf-overview-selection-03.js]
|
||||
[browser_perf-overview-time-interval.js]
|
||||
[browser_perf-range-changed-render.js]
|
||||
[browser_perf-recording-notices-01.js]
|
||||
[browser_perf-recording-notices-02.js]
|
||||
[browser_perf-recording-notices-03.js]
|
||||
skip-if = (debug && (bits == 32)) # debug 32 bit: bug 1273374
|
||||
[browser_perf-recording-notices-04.js]
|
||||
[browser_perf-recording-notices-05.js]
|
||||
[browser_perf-recording-selected-01.js]
|
||||
[browser_perf-recording-selected-02.js]
|
||||
[browser_perf-recording-selected-03.js]
|
||||
[browser_perf-recording-selected-04.js]
|
||||
[browser_perf-recordings-clear-01.js]
|
||||
[browser_perf-recordings-clear-02.js]
|
||||
[browser_perf-recordings-io-01.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_perf-recordings-io-02.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_perf-recordings-io-03.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_perf-recordings-io-04.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_perf-recordings-io-05.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_perf-recordings-io-06.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_perf-refresh.js]
|
||||
[browser_perf-states.js]
|
||||
[browser_perf-telemetry-01.js]
|
||||
[browser_perf-telemetry-02.js]
|
||||
[browser_perf-telemetry-03.js]
|
||||
[browser_perf-telemetry-04.js]
|
||||
[browser_perf-theme-toggle.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_perf-tree-abstract-01.js]
|
||||
skip-if = (verify && debug && (os == 'win' || os == 'mac'))
|
||||
[browser_perf-tree-abstract-02.js]
|
||||
skip-if = (verify && debug && (os == 'win' || os == 'mac'))
|
||||
[browser_perf-tree-abstract-03.js]
|
||||
skip-if = (verify && debug && (os == 'win' || os == 'mac'))
|
||||
[browser_perf-tree-abstract-04.js]
|
||||
skip-if = (verify && debug && (os == 'win' || os == 'mac'))
|
||||
[browser_perf-tree-abstract-05.js]
|
||||
[browser_perf-tree-view-01.js]
|
||||
[browser_perf-tree-view-02.js]
|
||||
[browser_perf-tree-view-03.js]
|
||||
[browser_perf-tree-view-04.js]
|
||||
[browser_perf-tree-view-05.js]
|
||||
[browser_perf-tree-view-06.js]
|
||||
[browser_perf-tree-view-07.js]
|
||||
[browser_perf-tree-view-08.js]
|
||||
[browser_perf-tree-view-09.js]
|
||||
[browser_perf-tree-view-10.js]
|
||||
[browser_perf-tree-view-11.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_perf-ui-recording.js]
|
||||
[browser_timeline-filters-01.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_timeline-filters-02.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_timeline-waterfall-background.js]
|
||||
[browser_timeline-waterfall-generic.js]
|
||||
[browser_timeline-waterfall-rerender.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_timeline-waterfall-sidebar.js]
|
||||
disabled=TODO bug 1256350
|
||||
[browser_timeline-waterfall-workers.js]
|
||||
disabled=TODO bug 1256350
|
@ -1,27 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the performance tool leaks on initialization and sudden destruction.
|
||||
* You can also use this initialization format as a template for other tests.
|
||||
*/
|
||||
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
|
||||
add_task(async function() {
|
||||
const { target, toolbox, panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
ok(target, "Should have a target available.");
|
||||
ok(toolbox, "Should have a toolbox available.");
|
||||
ok(panel, "Should have a panel available.");
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,105 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the recording button states are set as expected.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const {
|
||||
$,
|
||||
$$,
|
||||
EVENTS,
|
||||
PerformanceController,
|
||||
PerformanceView,
|
||||
} = panel.panelWin;
|
||||
|
||||
const recordButton = $("#main-record-button");
|
||||
|
||||
checkRecordButtonsStates(false, false);
|
||||
|
||||
const uiStartClick = once(PerformanceView, EVENTS.UI_START_RECORDING);
|
||||
const recordingStarted = once(
|
||||
PerformanceController,
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
{
|
||||
expectedArgs: ["recording-started"],
|
||||
}
|
||||
);
|
||||
const backendStartReady = once(
|
||||
PerformanceController,
|
||||
EVENTS.BACKEND_READY_AFTER_RECORDING_START
|
||||
);
|
||||
const uiStateRecording = once(PerformanceView, EVENTS.UI_STATE_CHANGED, {
|
||||
expectedArgs: ["recording"],
|
||||
});
|
||||
|
||||
click(recordButton);
|
||||
await uiStartClick;
|
||||
|
||||
checkRecordButtonsStates(true, true);
|
||||
|
||||
await recordingStarted;
|
||||
|
||||
checkRecordButtonsStates(true, false);
|
||||
|
||||
await backendStartReady;
|
||||
await uiStateRecording;
|
||||
|
||||
const uiStopClick = once(PerformanceView, EVENTS.UI_STOP_RECORDING);
|
||||
const recordingStopped = once(
|
||||
PerformanceController,
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
{
|
||||
expectedArgs: ["recording-stopped"],
|
||||
}
|
||||
);
|
||||
const backendStopReady = once(
|
||||
PerformanceController,
|
||||
EVENTS.BACKEND_READY_AFTER_RECORDING_STOP
|
||||
);
|
||||
const uiStateRecorded = once(PerformanceView, EVENTS.UI_STATE_CHANGED, {
|
||||
expectedArgs: ["recorded"],
|
||||
});
|
||||
|
||||
click(recordButton);
|
||||
await uiStopClick;
|
||||
await recordingStopped;
|
||||
|
||||
checkRecordButtonsStates(false, false);
|
||||
|
||||
await backendStopReady;
|
||||
await uiStateRecorded;
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
|
||||
function checkRecordButtonsStates(checked, locked) {
|
||||
for (const button of $$(".record-button")) {
|
||||
is(
|
||||
button.classList.contains("checked"),
|
||||
checked,
|
||||
"The record button checked state should be " + checked
|
||||
);
|
||||
is(
|
||||
button.disabled,
|
||||
locked,
|
||||
"The record button locked state should be " + locked
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
@ -1,82 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the categories are shown in the js call tree when
|
||||
* platform data is enabled.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_SHOW_PLATFORM_DATA_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
busyWait,
|
||||
} = require("devtools/client/performance/test/helpers/wait-utils");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, $, $$, DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
// Enable platform data to show the categories in the tree.
|
||||
Services.prefs.setBoolPref(UI_SHOW_PLATFORM_DATA_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
// To show the `Gecko` category in the tree.
|
||||
await busyWait(100);
|
||||
await stopRecording(panel);
|
||||
|
||||
const rendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await rendered;
|
||||
|
||||
is(
|
||||
$(".call-tree-cells-container").hasAttribute("categories-hidden"),
|
||||
false,
|
||||
"The call tree cells container should show the categories now."
|
||||
);
|
||||
ok(
|
||||
geckoCategoryPresent($$),
|
||||
"A category node with the text `Gecko` is displayed in the tree."
|
||||
);
|
||||
|
||||
// Disable platform data to hide the categories.
|
||||
Services.prefs.setBoolPref(UI_SHOW_PLATFORM_DATA_PREF, false);
|
||||
|
||||
is(
|
||||
$(".call-tree-cells-container").getAttribute("categories-hidden"),
|
||||
"",
|
||||
"The call tree cells container should hide the categories now."
|
||||
);
|
||||
ok(
|
||||
!geckoCategoryPresent($$),
|
||||
"A category node with the text `Gecko` doesn't exist in the tree anymore."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
||||
|
||||
function geckoCategoryPresent($$) {
|
||||
for (const elem of $$(".call-tree-category")) {
|
||||
if (elem.textContent.trim() == "Gecko") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the js call tree view renders the correct columns.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_SHOW_PLATFORM_DATA_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
busyWait,
|
||||
} = require("devtools/client/performance/test/helpers/wait-utils");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, $, $$, DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
// Enable platform data to show the platform functions in the tree.
|
||||
Services.prefs.setBoolPref(UI_SHOW_PLATFORM_DATA_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
// To show the `busyWait` function in the tree.
|
||||
await busyWait(100);
|
||||
await stopRecording(panel);
|
||||
|
||||
const rendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await rendered;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(JsCallTreeView),
|
||||
"The call tree is now selected."
|
||||
);
|
||||
|
||||
testCells($, $$, {
|
||||
duration: true,
|
||||
percentage: true,
|
||||
allocations: false,
|
||||
"self-duration": true,
|
||||
"self-percentage": true,
|
||||
"self-allocations": false,
|
||||
samples: true,
|
||||
function: true,
|
||||
});
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
||||
|
||||
function testCells($, $$, visibleCells) {
|
||||
for (const cell in visibleCells) {
|
||||
if (visibleCells[cell]) {
|
||||
ok(
|
||||
$(`.call-tree-cell[type=${cell}]`),
|
||||
`At least one ${cell} column was visible in the tree.`
|
||||
);
|
||||
} else {
|
||||
ok(
|
||||
!$(`.call-tree-cell[type=${cell}]`),
|
||||
`No ${cell} columns were visible in the tree.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
is(
|
||||
$$(".call-tree-cell", $(".call-tree-item")).length,
|
||||
Object.keys(visibleCells).filter(e => visibleCells[e]).length,
|
||||
"The correct number of cells were found in the tree."
|
||||
);
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the call tree up/down events work for js calltrees.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
synthesizeProfile,
|
||||
} = require("devtools/client/performance/test/helpers/synth-utils");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
ThreadNode,
|
||||
} = require("devtools/client/performance/modules/logic/tree-model");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const {
|
||||
EVENTS,
|
||||
$,
|
||||
DetailsView,
|
||||
OverviewView,
|
||||
JsCallTreeView,
|
||||
} = panel.panelWin;
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
const rendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await rendered;
|
||||
|
||||
// Mock the profile used so we can get a deterministic tree created.
|
||||
const profile = synthesizeProfile();
|
||||
const threadNode = new ThreadNode(
|
||||
profile.threads[0],
|
||||
OverviewView.getTimeInterval()
|
||||
);
|
||||
JsCallTreeView._populateCallTree(threadNode);
|
||||
JsCallTreeView.emit(EVENTS.UI_JS_CALL_TREE_RENDERED);
|
||||
|
||||
const firstTreeItem = $("#js-calltree-view .call-tree-item");
|
||||
|
||||
// DE-XUL: There are focus issues with XUL. Focus first, then synthesize the clicks
|
||||
// so that keyboard events work correctly.
|
||||
firstTreeItem.focus();
|
||||
|
||||
let count = 0;
|
||||
const onFocus = () => count++;
|
||||
JsCallTreeView.on("focus", onFocus);
|
||||
|
||||
click(firstTreeItem);
|
||||
|
||||
key("VK_DOWN");
|
||||
key("VK_DOWN");
|
||||
key("VK_DOWN");
|
||||
key("VK_DOWN");
|
||||
|
||||
JsCallTreeView.off("focus", onFocus);
|
||||
is(count, 4, "Several focus events are fired for the calltree.");
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,91 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the memory call tree view renders the correct columns.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_ENABLE_ALLOCATIONS_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, $, $$, DetailsView, MemoryCallTreeView } = panel.panelWin;
|
||||
|
||||
// Enable allocations to test.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_ALLOCATIONS_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
const rendered = once(
|
||||
MemoryCallTreeView,
|
||||
EVENTS.UI_MEMORY_CALL_TREE_RENDERED
|
||||
);
|
||||
await DetailsView.selectView("memory-calltree");
|
||||
await rendered;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(MemoryCallTreeView),
|
||||
"The call tree is now selected."
|
||||
);
|
||||
|
||||
testCells($, $$, {
|
||||
duration: false,
|
||||
percentage: false,
|
||||
count: true,
|
||||
"count-percentage": true,
|
||||
size: true,
|
||||
"size-percentage": true,
|
||||
"self-duration": false,
|
||||
"self-percentage": false,
|
||||
"self-count": true,
|
||||
"self-count-percentage": true,
|
||||
"self-size": true,
|
||||
"self-size-percentage": true,
|
||||
samples: false,
|
||||
function: true,
|
||||
});
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
||||
|
||||
function testCells($, $$, visibleCells) {
|
||||
for (const cell in visibleCells) {
|
||||
if (visibleCells[cell]) {
|
||||
ok(
|
||||
$(`.call-tree-cell[type=${cell}]`),
|
||||
`At least one ${cell} column was visible in the tree.`
|
||||
);
|
||||
} else {
|
||||
ok(
|
||||
!$(`.call-tree-cell[type=${cell}]`),
|
||||
`No ${cell} columns were visible in the tree.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
is(
|
||||
$$(".call-tree-cell", $(".call-tree-item")).length,
|
||||
Object.keys(visibleCells).filter(e => visibleCells[e]).length,
|
||||
"The correct number of cells were found in the tree."
|
||||
);
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the profiler is populated by console recordings that have finished
|
||||
* before it was opened.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInTab,
|
||||
initConsoleInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
waitUntil,
|
||||
} = require("devtools/client/performance/test/helpers/wait-utils");
|
||||
const {
|
||||
getSelectedRecording,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { target, console } = await initConsoleInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
await console.profile("rust");
|
||||
await console.profileEnd("rust");
|
||||
|
||||
const { panel } = await initPerformanceInTab({ tab: target.localTab });
|
||||
const { PerformanceController, WaterfallView } = panel.panelWin;
|
||||
|
||||
await waitUntil(() => PerformanceController.getRecordings().length == 1);
|
||||
await waitUntil(() => WaterfallView.wasRenderedAtLeastOnce);
|
||||
|
||||
const recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 1, "One recording found in the performance panel.");
|
||||
is(recordings[0].isConsole(), true, "Recording came from console.profile.");
|
||||
is(recordings[0].getLabel(), "rust", "Correct label in the recording model.");
|
||||
|
||||
const selected = getSelectedRecording(panel);
|
||||
|
||||
is(
|
||||
selected,
|
||||
recordings[0],
|
||||
"The profile from console should be selected as it's the only one."
|
||||
);
|
||||
is(
|
||||
selected.getLabel(),
|
||||
"rust",
|
||||
"The profile label for the first recording is correct."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,104 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the profiler is populated by in-progress console recordings
|
||||
* when it is opened.
|
||||
*/
|
||||
|
||||
const { Constants } = require("devtools/client/performance/modules/constants");
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInTab,
|
||||
initConsoleInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
waitForRecordingStoppedEvents,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
waitUntil,
|
||||
} = require("devtools/client/performance/test/helpers/wait-utils");
|
||||
const {
|
||||
times,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
getSelectedRecording,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { target, console } = await initConsoleInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
await console.profile("rust");
|
||||
await console.profile("rust2");
|
||||
|
||||
const { panel } = await initPerformanceInTab({ tab: target.localTab });
|
||||
const { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
|
||||
|
||||
await waitUntil(() => PerformanceController.getRecordings().length == 2);
|
||||
|
||||
const recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 2, "Two recordings found in the performance panel.");
|
||||
is(
|
||||
recordings[0].isConsole(),
|
||||
true,
|
||||
"Recording came from console.profile (1)."
|
||||
);
|
||||
is(
|
||||
recordings[0].getLabel(),
|
||||
"rust",
|
||||
"Correct label in the recording model (1)."
|
||||
);
|
||||
is(recordings[0].isRecording(), true, "Recording is still recording (1).");
|
||||
is(
|
||||
recordings[1].isConsole(),
|
||||
true,
|
||||
"Recording came from console.profile (2)."
|
||||
);
|
||||
is(
|
||||
recordings[1].getLabel(),
|
||||
"rust2",
|
||||
"Correct label in the recording model (2)."
|
||||
);
|
||||
is(recordings[1].isRecording(), true, "Recording is still recording (2).");
|
||||
|
||||
const selected = getSelectedRecording(panel);
|
||||
is(
|
||||
selected,
|
||||
recordings[0],
|
||||
"The first console recording should be selected."
|
||||
);
|
||||
is(
|
||||
selected.getLabel(),
|
||||
"rust",
|
||||
"The profile label for the first recording is correct."
|
||||
);
|
||||
|
||||
// Ensure overview is still rendering.
|
||||
await times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
|
||||
expectedArgs: [Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL],
|
||||
});
|
||||
|
||||
let stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
});
|
||||
await console.profileEnd("rust");
|
||||
await stopped;
|
||||
|
||||
stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when a finished recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
skipWaitingForSubview: true,
|
||||
});
|
||||
await console.profileEnd("rust2");
|
||||
await stopped;
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,90 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the profiler is populated by in-progress console recordings, and
|
||||
* also console recordings that have finished before it was opened.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInTab,
|
||||
initConsoleInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
waitForRecordingStoppedEvents,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
waitUntil,
|
||||
} = require("devtools/client/performance/test/helpers/wait-utils");
|
||||
const {
|
||||
getSelectedRecording,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { target, console } = await initConsoleInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
await console.profile("rust");
|
||||
await console.profileEnd("rust");
|
||||
await console.profile("rust2");
|
||||
|
||||
const { panel } = await initPerformanceInTab({ tab: target.localTab });
|
||||
const { PerformanceController, WaterfallView } = panel.panelWin;
|
||||
|
||||
await waitUntil(() => PerformanceController.getRecordings().length == 2);
|
||||
await waitUntil(() => WaterfallView.wasRenderedAtLeastOnce);
|
||||
|
||||
const recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 2, "Two recordings found in the performance panel.");
|
||||
is(
|
||||
recordings[0].isConsole(),
|
||||
true,
|
||||
"Recording came from console.profile (1)."
|
||||
);
|
||||
is(
|
||||
recordings[0].getLabel(),
|
||||
"rust",
|
||||
"Correct label in the recording model (1)."
|
||||
);
|
||||
is(recordings[0].isRecording(), false, "Recording is still recording (1).");
|
||||
is(
|
||||
recordings[1].isConsole(),
|
||||
true,
|
||||
"Recording came from console.profile (2)."
|
||||
);
|
||||
is(
|
||||
recordings[1].getLabel(),
|
||||
"rust2",
|
||||
"Correct label in the recording model (2)."
|
||||
);
|
||||
is(recordings[1].isRecording(), true, "Recording is still recording (2).");
|
||||
|
||||
const selected = getSelectedRecording(panel);
|
||||
is(
|
||||
selected,
|
||||
recordings[0],
|
||||
"The first console recording should be selected."
|
||||
);
|
||||
is(
|
||||
selected.getLabel(),
|
||||
"rust",
|
||||
"The profile label for the first recording is correct."
|
||||
);
|
||||
|
||||
const stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when a finished recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
skipWaitingForSubview: true,
|
||||
});
|
||||
await console.profileEnd("rust2");
|
||||
await stopped;
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,75 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the profiler can handle creation and stopping of console profiles
|
||||
* after being opened.
|
||||
*/
|
||||
|
||||
const { Constants } = require("devtools/client/performance/modules/constants");
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInTab,
|
||||
initConsoleInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
waitForRecordingStartedEvents,
|
||||
waitForRecordingStoppedEvents,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
times,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
getSelectedRecording,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { target, console } = await initConsoleInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { panel } = await initPerformanceInTab({ tab: target.localTab });
|
||||
const { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
|
||||
|
||||
const started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
});
|
||||
await console.profile("rust");
|
||||
await started;
|
||||
|
||||
const recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 1, "One recording found in the performance panel.");
|
||||
is(recordings[0].isConsole(), true, "Recording came from console.profile.");
|
||||
is(recordings[0].getLabel(), "rust", "Correct label in the recording model.");
|
||||
is(recordings[0].isRecording(), true, "Recording is still recording.");
|
||||
|
||||
const selected = getSelectedRecording(panel);
|
||||
is(
|
||||
selected,
|
||||
recordings[0],
|
||||
"The profile from console should be selected as it's the only one."
|
||||
);
|
||||
is(
|
||||
selected.getLabel(),
|
||||
"rust",
|
||||
"The profile label for the first recording is correct."
|
||||
);
|
||||
|
||||
// Ensure overview is still rendering.
|
||||
await times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
|
||||
expectedArgs: [Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL],
|
||||
});
|
||||
|
||||
const stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
});
|
||||
await console.profileEnd("rust");
|
||||
await stopped;
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,131 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that multiple recordings with the same label (non-overlapping) appear
|
||||
* in the recording list.
|
||||
*/
|
||||
|
||||
const { Constants } = require("devtools/client/performance/modules/constants");
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInTab,
|
||||
initConsoleInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
waitForRecordingStartedEvents,
|
||||
waitForRecordingStoppedEvents,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
times,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
getSelectedRecording,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { target, console } = await initConsoleInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { panel } = await initPerformanceInTab({ tab: target.localTab });
|
||||
const { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
|
||||
|
||||
let started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
});
|
||||
await console.profile("rust");
|
||||
await started;
|
||||
|
||||
let recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 1, "One recording found in the performance panel.");
|
||||
is(
|
||||
recordings[0].isConsole(),
|
||||
true,
|
||||
"Recording came from console.profile (1)."
|
||||
);
|
||||
is(
|
||||
recordings[0].getLabel(),
|
||||
"rust",
|
||||
"Correct label in the recording model (1)."
|
||||
);
|
||||
is(recordings[0].isRecording(), true, "Recording is still recording (1).");
|
||||
|
||||
let selected = getSelectedRecording(panel);
|
||||
is(
|
||||
selected,
|
||||
recordings[0],
|
||||
"The profile from console should be selected as it's the only one."
|
||||
);
|
||||
is(
|
||||
selected.getLabel(),
|
||||
"rust",
|
||||
"The profile label for the first recording is correct."
|
||||
);
|
||||
|
||||
// Ensure overview is still rendering.
|
||||
await times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
|
||||
expectedArgs: [Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL],
|
||||
});
|
||||
|
||||
let stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
});
|
||||
await console.profileEnd("rust");
|
||||
await stopped;
|
||||
|
||||
started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when an in-progress recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
// the view state won't switch to "console-recording" unless the new
|
||||
// in-progress recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profile("rust");
|
||||
await started;
|
||||
|
||||
recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 2, "Two recordings found in the performance panel.");
|
||||
is(
|
||||
recordings[1].isConsole(),
|
||||
true,
|
||||
"Recording came from console.profile (2)."
|
||||
);
|
||||
is(
|
||||
recordings[1].getLabel(),
|
||||
"rust",
|
||||
"Correct label in the recording model (2)."
|
||||
);
|
||||
is(recordings[1].isRecording(), true, "Recording is still recording (2).");
|
||||
|
||||
selected = getSelectedRecording(panel);
|
||||
is(
|
||||
selected,
|
||||
recordings[0],
|
||||
"The profile from console should still be selected"
|
||||
);
|
||||
is(
|
||||
selected.getLabel(),
|
||||
"rust",
|
||||
"The profile label for the first recording is correct."
|
||||
);
|
||||
|
||||
stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when a finished recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
skipWaitingForSubview: true,
|
||||
});
|
||||
await console.profileEnd("rust");
|
||||
await stopped;
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,125 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that console recordings can overlap (not completely nested).
|
||||
*/
|
||||
|
||||
const { Constants } = require("devtools/client/performance/modules/constants");
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInTab,
|
||||
initConsoleInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
waitForRecordingStartedEvents,
|
||||
waitForRecordingStoppedEvents,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
times,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
getSelectedRecording,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { target, console } = await initConsoleInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { panel } = await initPerformanceInTab({ tab: target.localTab });
|
||||
const { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
|
||||
|
||||
let started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
});
|
||||
await console.profile("rust");
|
||||
await started;
|
||||
|
||||
let recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 1, "A recording found in the performance panel.");
|
||||
is(
|
||||
getSelectedRecording(panel),
|
||||
recordings[0],
|
||||
"The first console recording should be selected."
|
||||
);
|
||||
|
||||
// Ensure overview is still rendering.
|
||||
await times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
|
||||
expectedArgs: [Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL],
|
||||
});
|
||||
|
||||
started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when an in-progress recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
// the view state won't switch to "console-recording" unless the new
|
||||
// in-progress recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profile("golang");
|
||||
await started;
|
||||
|
||||
recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 2, "Two recordings found in the performance panel.");
|
||||
is(
|
||||
getSelectedRecording(panel),
|
||||
recordings[0],
|
||||
"The first console recording should still be selected."
|
||||
);
|
||||
|
||||
// Ensure overview is still rendering.
|
||||
await times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
|
||||
expectedArgs: [Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL],
|
||||
});
|
||||
|
||||
let stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
});
|
||||
await console.profileEnd("rust");
|
||||
await stopped;
|
||||
|
||||
recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 2, "Two recordings found in the performance panel.");
|
||||
is(
|
||||
getSelectedRecording(panel),
|
||||
recordings[0],
|
||||
"The first console recording should still be selected."
|
||||
);
|
||||
is(
|
||||
recordings[0].isRecording(),
|
||||
false,
|
||||
"The first console recording should no longer be recording."
|
||||
);
|
||||
|
||||
stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when a finished recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
skipWaitingForSubview: true,
|
||||
});
|
||||
await console.profileEnd("golang");
|
||||
await stopped;
|
||||
|
||||
recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 2, "Two recordings found in the performance panel.");
|
||||
is(
|
||||
getSelectedRecording(panel),
|
||||
recordings[0],
|
||||
"The first console recording should still be selected."
|
||||
);
|
||||
is(
|
||||
recordings[1].isRecording(),
|
||||
false,
|
||||
"The second console recording should no longer be recording."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,247 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that a call to console.profileEnd() with no label ends the
|
||||
* most recent console recording, and console.profileEnd() with a label that
|
||||
* does not match any pending recordings does nothing.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInTab,
|
||||
initConsoleInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
waitForRecordingStartedEvents,
|
||||
waitForRecordingStoppedEvents,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
idleWait,
|
||||
} = require("devtools/client/performance/test/helpers/wait-utils");
|
||||
const {
|
||||
getSelectedRecording,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { target, console } = await initConsoleInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { panel } = await initPerformanceInTab({ tab: target.localTab });
|
||||
const { PerformanceController } = panel.panelWin;
|
||||
|
||||
let started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
});
|
||||
await console.profile();
|
||||
await started;
|
||||
|
||||
started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when an in-progress recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
// the view state won't switch to "console-recording" unless the new
|
||||
// in-progress recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profile("1");
|
||||
await started;
|
||||
|
||||
started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when an in-progress recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
// the view state won't switch to "console-recording" unless the new
|
||||
// in-progress recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profile("2");
|
||||
await started;
|
||||
|
||||
let recordings = PerformanceController.getRecordings();
|
||||
let selected = getSelectedRecording(panel);
|
||||
is(recordings.length, 3, "Three recordings found in the performance panel.");
|
||||
is(recordings[0].getLabel(), "", "Checking label of recording 1");
|
||||
is(recordings[1].getLabel(), "1", "Checking label of recording 2");
|
||||
is(recordings[2].getLabel(), "2", "Checking label of recording 3");
|
||||
is(
|
||||
selected,
|
||||
recordings[0],
|
||||
"The first console recording should be selected."
|
||||
);
|
||||
|
||||
is(
|
||||
recordings[0].isRecording(),
|
||||
true,
|
||||
"All recordings should now be started. (1)"
|
||||
);
|
||||
is(
|
||||
recordings[1].isRecording(),
|
||||
true,
|
||||
"All recordings should now be started. (2)"
|
||||
);
|
||||
is(
|
||||
recordings[2].isRecording(),
|
||||
true,
|
||||
"All recordings should now be started. (3)"
|
||||
);
|
||||
|
||||
let stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when a finished recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
skipWaitingForSubview: true,
|
||||
// the view state won't switch to "recorded" unless the new
|
||||
// finished recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profileEnd();
|
||||
await stopped;
|
||||
|
||||
selected = getSelectedRecording(panel);
|
||||
recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 3, "Three recordings found in the performance panel.");
|
||||
is(
|
||||
selected,
|
||||
recordings[0],
|
||||
"The first console recording should still be selected."
|
||||
);
|
||||
|
||||
is(
|
||||
recordings[0].isRecording(),
|
||||
true,
|
||||
"The not most recent recording should not stop " +
|
||||
"when calling console.profileEnd with no args."
|
||||
);
|
||||
is(
|
||||
recordings[1].isRecording(),
|
||||
true,
|
||||
"The not most recent recording should not stop " +
|
||||
"when calling console.profileEnd with no args."
|
||||
);
|
||||
is(
|
||||
recordings[2].isRecording(),
|
||||
false,
|
||||
"Only the most recent recording should stop " +
|
||||
"when calling console.profileEnd with no args."
|
||||
);
|
||||
|
||||
info("Trying to `profileEnd` a non-existent console recording.");
|
||||
console.profileEnd("fxos");
|
||||
await idleWait(1000);
|
||||
|
||||
selected = getSelectedRecording(panel);
|
||||
recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 3, "Three recordings found in the performance panel.");
|
||||
is(
|
||||
selected,
|
||||
recordings[0],
|
||||
"The first console recording should still be selected."
|
||||
);
|
||||
|
||||
is(
|
||||
recordings[0].isRecording(),
|
||||
true,
|
||||
"The first recording should not be ended yet."
|
||||
);
|
||||
is(
|
||||
recordings[1].isRecording(),
|
||||
true,
|
||||
"The second recording should not be ended yet."
|
||||
);
|
||||
is(
|
||||
recordings[2].isRecording(),
|
||||
false,
|
||||
"The third recording should still be ended."
|
||||
);
|
||||
|
||||
stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when a finished recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
skipWaitingForSubview: true,
|
||||
// the view state won't switch to "recorded" unless the new
|
||||
// finished recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profileEnd();
|
||||
await stopped;
|
||||
|
||||
selected = getSelectedRecording(panel);
|
||||
recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 3, "Three recordings found in the performance panel.");
|
||||
is(
|
||||
selected,
|
||||
recordings[0],
|
||||
"The first console recording should still be selected."
|
||||
);
|
||||
|
||||
is(
|
||||
recordings[0].isRecording(),
|
||||
true,
|
||||
"The first recording should not be ended yet."
|
||||
);
|
||||
is(
|
||||
recordings[1].isRecording(),
|
||||
false,
|
||||
"The second recording should not be ended yet."
|
||||
);
|
||||
is(
|
||||
recordings[2].isRecording(),
|
||||
false,
|
||||
"The third recording should still be ended."
|
||||
);
|
||||
|
||||
stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
});
|
||||
await console.profileEnd();
|
||||
await stopped;
|
||||
|
||||
selected = getSelectedRecording(panel);
|
||||
recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 3, "Three recordings found in the performance panel.");
|
||||
is(
|
||||
selected,
|
||||
recordings[0],
|
||||
"The first console recording should be selected."
|
||||
);
|
||||
|
||||
is(
|
||||
recordings[0].isRecording(),
|
||||
false,
|
||||
"All recordings should now be ended. (1)"
|
||||
);
|
||||
is(
|
||||
recordings[1].isRecording(),
|
||||
false,
|
||||
"All recordings should now be ended. (2)"
|
||||
);
|
||||
is(
|
||||
recordings[2].isRecording(),
|
||||
false,
|
||||
"All recordings should now be ended. (3)"
|
||||
);
|
||||
|
||||
info("Trying to `profileEnd` with no pending recordings.");
|
||||
console.profileEnd();
|
||||
await idleWait(1000);
|
||||
|
||||
ok(
|
||||
true,
|
||||
"Calling console.profileEnd() with no argument and no pending recordings " +
|
||||
"does not throw."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,311 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the profiler can correctly handle simultaneous console and manual
|
||||
* recordings (via `console.profile` and clicking the record button).
|
||||
*/
|
||||
|
||||
const { Constants } = require("devtools/client/performance/modules/constants");
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInTab,
|
||||
initConsoleInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
waitForRecordingStartedEvents,
|
||||
waitForRecordingStoppedEvents,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
times,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
setSelectedRecording,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
/**
|
||||
* The following are bit flag constants that are used to represent the state of a
|
||||
* recording.
|
||||
*/
|
||||
|
||||
// Represents a manually recorded profile, if a user hit the record button.
|
||||
const MANUAL = 0;
|
||||
// Represents a recorded profile from console.profile().
|
||||
const CONSOLE = 1;
|
||||
// Represents a profile that is currently recording.
|
||||
const RECORDING = 2;
|
||||
// Represents a profile that is currently selected.
|
||||
const SELECTED = 4;
|
||||
|
||||
/**
|
||||
* Utility function to provide a meaningful inteface for testing that the bits
|
||||
* match for the recording state.
|
||||
* @param {integer} expected - The expected bit values packed in an integer.
|
||||
* @param {integer} actual - The actual bit values packed in an integer.
|
||||
*/
|
||||
function hasBitFlag(expected, actual) {
|
||||
return !!(expected & actual);
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
// This test seems to take a very long time to finish on Linux VMs.
|
||||
requestLongerTimeout(4);
|
||||
|
||||
const { target, console } = await initConsoleInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { panel } = await initPerformanceInTab({ tab: target.localTab });
|
||||
const { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
|
||||
|
||||
info("Recording 1 - Starting console.profile()...");
|
||||
let started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
});
|
||||
await console.profile("rust");
|
||||
await started;
|
||||
testRecordings(PerformanceController, [CONSOLE + SELECTED + RECORDING]);
|
||||
|
||||
info("Recording 2 - Starting manual recording...");
|
||||
await startRecording(panel);
|
||||
testRecordings(PerformanceController, [
|
||||
CONSOLE + RECORDING,
|
||||
MANUAL + RECORDING + SELECTED,
|
||||
]);
|
||||
|
||||
info('Recording 3 - Starting console.profile("3")...');
|
||||
started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when an in-progress recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
// the view state won't switch to "console-recording" unless the new
|
||||
// in-progress recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profile("3");
|
||||
await started;
|
||||
testRecordings(PerformanceController, [
|
||||
CONSOLE + RECORDING,
|
||||
MANUAL + RECORDING + SELECTED,
|
||||
CONSOLE + RECORDING,
|
||||
]);
|
||||
|
||||
info('Recording 4 - Starting console.profile("4")...');
|
||||
started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when an in-progress recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
// the view state won't switch to "console-recording" unless the new
|
||||
// in-progress recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profile("4");
|
||||
await started;
|
||||
testRecordings(PerformanceController, [
|
||||
CONSOLE + RECORDING,
|
||||
MANUAL + RECORDING + SELECTED,
|
||||
CONSOLE + RECORDING,
|
||||
CONSOLE + RECORDING,
|
||||
]);
|
||||
|
||||
info("Recording 4 - Ending console.profileEnd()...");
|
||||
let stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when a finished recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
skipWaitingForSubview: true,
|
||||
// the view state won't switch to "recorded" unless the new
|
||||
// finished recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profileEnd();
|
||||
await stopped;
|
||||
testRecordings(PerformanceController, [
|
||||
CONSOLE + RECORDING,
|
||||
MANUAL + RECORDING + SELECTED,
|
||||
CONSOLE + RECORDING,
|
||||
CONSOLE,
|
||||
]);
|
||||
|
||||
info("Recording 4 - Select last recording...");
|
||||
let recordingSelected = once(
|
||||
PerformanceController,
|
||||
EVENTS.RECORDING_SELECTED
|
||||
);
|
||||
setSelectedRecording(panel, 3);
|
||||
await recordingSelected;
|
||||
testRecordings(PerformanceController, [
|
||||
CONSOLE + RECORDING,
|
||||
MANUAL + RECORDING,
|
||||
CONSOLE + RECORDING,
|
||||
CONSOLE + SELECTED,
|
||||
]);
|
||||
ok(
|
||||
!OverviewView.isRendering(),
|
||||
"Stop rendering overview when a completed recording is selected."
|
||||
);
|
||||
|
||||
info("Recording 2 - Stop manual recording.");
|
||||
|
||||
await stopRecording(panel);
|
||||
testRecordings(PerformanceController, [
|
||||
CONSOLE + RECORDING,
|
||||
MANUAL + SELECTED,
|
||||
CONSOLE + RECORDING,
|
||||
CONSOLE,
|
||||
]);
|
||||
ok(
|
||||
!OverviewView.isRendering(),
|
||||
"Stop rendering overview when a completed recording is selected."
|
||||
);
|
||||
|
||||
info("Recording 1 - Select first recording.");
|
||||
recordingSelected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
|
||||
setSelectedRecording(panel, 0);
|
||||
await recordingSelected;
|
||||
testRecordings(PerformanceController, [
|
||||
CONSOLE + RECORDING + SELECTED,
|
||||
MANUAL,
|
||||
CONSOLE + RECORDING,
|
||||
CONSOLE,
|
||||
]);
|
||||
ok(
|
||||
OverviewView.isRendering(),
|
||||
"Should be rendering overview a recording in progress is selected."
|
||||
);
|
||||
|
||||
// Ensure overview is still rendering.
|
||||
await times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
|
||||
expectedArgs: [Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL],
|
||||
});
|
||||
|
||||
info("Ending console.profileEnd()...");
|
||||
stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when a finished recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
skipWaitingForSubview: true,
|
||||
// the view state won't switch to "recorded" unless the new
|
||||
// finished recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profileEnd();
|
||||
await stopped;
|
||||
testRecordings(PerformanceController, [
|
||||
CONSOLE + RECORDING + SELECTED,
|
||||
MANUAL,
|
||||
CONSOLE,
|
||||
CONSOLE,
|
||||
]);
|
||||
ok(
|
||||
OverviewView.isRendering(),
|
||||
"Should be rendering overview a recording in progress is selected."
|
||||
);
|
||||
|
||||
// Ensure overview is still rendering.
|
||||
await times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
|
||||
expectedArgs: [Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL],
|
||||
});
|
||||
|
||||
info("Recording 5 - Start one more manual recording.");
|
||||
await startRecording(panel);
|
||||
testRecordings(PerformanceController, [
|
||||
CONSOLE + RECORDING,
|
||||
MANUAL,
|
||||
CONSOLE,
|
||||
CONSOLE,
|
||||
MANUAL + RECORDING + SELECTED,
|
||||
]);
|
||||
ok(
|
||||
OverviewView.isRendering(),
|
||||
"Should be rendering overview a recording in progress is selected."
|
||||
);
|
||||
|
||||
// Ensure overview is still rendering.
|
||||
await times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
|
||||
expectedArgs: [Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL],
|
||||
});
|
||||
|
||||
info("Recording 5 - Stop manual recording.");
|
||||
await stopRecording(panel);
|
||||
testRecordings(PerformanceController, [
|
||||
CONSOLE + RECORDING,
|
||||
MANUAL,
|
||||
CONSOLE,
|
||||
CONSOLE,
|
||||
MANUAL + SELECTED,
|
||||
]);
|
||||
ok(
|
||||
!OverviewView.isRendering(),
|
||||
"Stop rendering overview when a completed recording is selected."
|
||||
);
|
||||
|
||||
info("Recording 1 - Ending console.profileEnd()...");
|
||||
stopped = waitForRecordingStoppedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when a finished recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
skipWaitingForSubview: true,
|
||||
// the view state won't switch to "recorded" unless the new
|
||||
// in-progress recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profileEnd();
|
||||
await stopped;
|
||||
testRecordings(PerformanceController, [
|
||||
CONSOLE,
|
||||
MANUAL,
|
||||
CONSOLE,
|
||||
CONSOLE,
|
||||
MANUAL + SELECTED,
|
||||
]);
|
||||
ok(
|
||||
!OverviewView.isRendering(),
|
||||
"Stop rendering overview when a completed recording is selected."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
||||
|
||||
function testRecordings(controller, expectedBitFlags) {
|
||||
const recordings = controller.getRecordings();
|
||||
const current = controller.getCurrentRecording();
|
||||
is(
|
||||
recordings.length,
|
||||
expectedBitFlags.length,
|
||||
"Expected number of recordings."
|
||||
);
|
||||
|
||||
recordings.forEach((recording, i) => {
|
||||
const expected = expectedBitFlags[i];
|
||||
is(
|
||||
recording.isConsole(),
|
||||
hasBitFlag(expected, CONSOLE),
|
||||
`Recording ${i + 1} has expected console state.`
|
||||
);
|
||||
is(
|
||||
recording.isRecording(),
|
||||
hasBitFlag(expected, RECORDING),
|
||||
`Recording ${i + 1} has expected console state.`
|
||||
);
|
||||
is(
|
||||
recording == current,
|
||||
hasBitFlag(expected, SELECTED),
|
||||
`Recording ${i + 1} has expected selected state.`
|
||||
);
|
||||
});
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that an error is not thrown when clearing out the recordings if there's
|
||||
* an in-progress console profile and that console profiles are not cleared
|
||||
* if in progress.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInTab,
|
||||
initConsoleInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
waitForRecordingStartedEvents,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
idleWait,
|
||||
} = require("devtools/client/performance/test/helpers/wait-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { target, console } = await initConsoleInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { panel } = await initPerformanceInTab({ tab: target.localTab });
|
||||
const { PerformanceController } = panel.panelWin;
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
info("Starting console.profile()...");
|
||||
const started = waitForRecordingStartedEvents(panel, {
|
||||
// only emitted for manual recordings
|
||||
skipWaitingForBackendReady: true,
|
||||
// only emitted when an in-progress recording is selected
|
||||
skipWaitingForOverview: true,
|
||||
// the view state won't switch to "console-recording" unless the new
|
||||
// in-progress recording is selected, which won't happen
|
||||
skipWaitingForViewState: true,
|
||||
});
|
||||
await console.profile();
|
||||
await started;
|
||||
|
||||
await PerformanceController.clearRecordings();
|
||||
let recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 1, "One recording found in the performance panel.");
|
||||
is(recordings[0].isConsole(), true, "Recording came from console.profile.");
|
||||
is(recordings[0].getLabel(), "", "Correct label in the recording model.");
|
||||
is(
|
||||
PerformanceController.getCurrentRecording(),
|
||||
recordings[0],
|
||||
"There current recording should be the first one."
|
||||
);
|
||||
|
||||
info("Attempting to end console.profileEnd()...");
|
||||
await console.profileEnd();
|
||||
await idleWait(1000);
|
||||
|
||||
ok(
|
||||
true,
|
||||
"Stopping an in-progress console profile after clearing recordings does not throw."
|
||||
);
|
||||
|
||||
await PerformanceController.clearRecordings();
|
||||
recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 0, "No recordings found");
|
||||
is(
|
||||
PerformanceController.getCurrentRecording(),
|
||||
null,
|
||||
"There should be no current recording."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,89 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the details view toggles subviews.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
command,
|
||||
} = require("devtools/client/performance/test/helpers/input-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, $, DetailsView } = panel.panelWin;
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
info("Checking views on startup.");
|
||||
checkViews(DetailsView, $, "waterfall");
|
||||
|
||||
// Select calltree view.
|
||||
let viewChanged = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED, {
|
||||
spreadArgs: true,
|
||||
});
|
||||
command($("toolbarbutton[data-view='js-calltree']"));
|
||||
let [viewName] = await viewChanged;
|
||||
is(viewName, "js-calltree", "UI_DETAILS_VIEW_SELECTED fired with view name");
|
||||
checkViews(DetailsView, $, "js-calltree");
|
||||
|
||||
// Select js flamegraph view.
|
||||
viewChanged = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED, {
|
||||
spreadArgs: true,
|
||||
});
|
||||
command($("toolbarbutton[data-view='js-flamegraph']"));
|
||||
[viewName] = await viewChanged;
|
||||
is(
|
||||
viewName,
|
||||
"js-flamegraph",
|
||||
"UI_DETAILS_VIEW_SELECTED fired with view name"
|
||||
);
|
||||
checkViews(DetailsView, $, "js-flamegraph");
|
||||
|
||||
// Select waterfall view.
|
||||
viewChanged = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED, {
|
||||
spreadArgs: true,
|
||||
});
|
||||
command($("toolbarbutton[data-view='waterfall']"));
|
||||
[viewName] = await viewChanged;
|
||||
is(viewName, "waterfall", "UI_DETAILS_VIEW_SELECTED fired with view name");
|
||||
checkViews(DetailsView, $, "waterfall");
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
||||
|
||||
function checkViews(DetailsView, $, currentView) {
|
||||
for (const viewName in DetailsView.components) {
|
||||
const button = $(`toolbarbutton[data-view="${viewName}"]`);
|
||||
|
||||
is(
|
||||
DetailsView.el.selectedPanel.id,
|
||||
DetailsView.components[currentView].id,
|
||||
`DetailsView correctly has ${currentView} selected.`
|
||||
);
|
||||
|
||||
if (viewName == currentView) {
|
||||
ok(button.getAttribute("checked"), `${viewName} button checked.`);
|
||||
} else {
|
||||
ok(!button.getAttribute("checked"), `${viewName} button not checked.`);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the details view utility functions work as advertised.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const {
|
||||
EVENTS,
|
||||
DetailsView,
|
||||
WaterfallView,
|
||||
JsCallTreeView,
|
||||
JsFlameGraphView,
|
||||
} = panel.panelWin;
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is selected by default in the details view."
|
||||
);
|
||||
|
||||
// Select js calltree view.
|
||||
let selected = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED);
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await selected;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(JsCallTreeView),
|
||||
"The js calltree view is now selected in the details view."
|
||||
);
|
||||
|
||||
// Select js flamegraph view.
|
||||
selected = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED);
|
||||
await DetailsView.selectView("js-flamegraph");
|
||||
await selected;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(JsFlameGraphView),
|
||||
"The js flamegraph view is now selected in the details view."
|
||||
);
|
||||
|
||||
// Select waterfall view.
|
||||
selected = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED);
|
||||
await DetailsView.selectView("waterfall");
|
||||
await selected;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is now selected in the details view."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,176 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the details view hides the allocations buttons when a recording
|
||||
* does not have allocations data ("withAllocations": false), and that when an
|
||||
* allocations panel is selected to a panel that does not have allocations goes
|
||||
* to a default panel instead.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_ENABLE_ALLOCATIONS_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
setSelectedRecording,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const {
|
||||
EVENTS,
|
||||
$,
|
||||
DetailsView,
|
||||
WaterfallView,
|
||||
MemoryCallTreeView,
|
||||
MemoryFlameGraphView,
|
||||
} = panel.panelWin;
|
||||
|
||||
const flameBtn = $("toolbarbutton[data-view='memory-flamegraph']");
|
||||
const callBtn = $("toolbarbutton[data-view='memory-calltree']");
|
||||
|
||||
// Disable allocations to prevent recording them.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_ALLOCATIONS_PREF, false);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is selected by default in the details view."
|
||||
);
|
||||
|
||||
// Re-enable allocations to test.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_ALLOCATIONS_PREF, true);
|
||||
|
||||
// The toolbar buttons will always be hidden when a recording isn't available,
|
||||
// so make sure we have one that's finished.
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is still selected in the details view."
|
||||
);
|
||||
|
||||
is(
|
||||
callBtn.hidden,
|
||||
false,
|
||||
"The `memory-calltree` button is shown when recording has memory data."
|
||||
);
|
||||
is(
|
||||
flameBtn.hidden,
|
||||
false,
|
||||
"The `memory-flamegraph` button is shown when recording has memory data."
|
||||
);
|
||||
|
||||
let selected = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED);
|
||||
let rendered = once(MemoryCallTreeView, EVENTS.UI_MEMORY_CALL_TREE_RENDERED);
|
||||
DetailsView.selectView("memory-calltree");
|
||||
await selected;
|
||||
await rendered;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(MemoryCallTreeView),
|
||||
"The memory call tree view can now be selected."
|
||||
);
|
||||
|
||||
selected = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED);
|
||||
rendered = once(MemoryFlameGraphView, EVENTS.UI_MEMORY_FLAMEGRAPH_RENDERED);
|
||||
DetailsView.selectView("memory-flamegraph");
|
||||
await selected;
|
||||
await rendered;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(MemoryFlameGraphView),
|
||||
"The memory flamegraph view can now be selected."
|
||||
);
|
||||
|
||||
// Select the first recording with no memory data.
|
||||
selected = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED);
|
||||
rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
|
||||
setSelectedRecording(panel, 0);
|
||||
await selected;
|
||||
await rendered;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is now selected " +
|
||||
"when switching back to a recording that does not have memory data."
|
||||
);
|
||||
|
||||
is(
|
||||
callBtn.hidden,
|
||||
true,
|
||||
"The `memory-calltree` button is hidden when recording has no memory data."
|
||||
);
|
||||
is(
|
||||
flameBtn.hidden,
|
||||
true,
|
||||
"The `memory-flamegraph` button is hidden when recording has no memory data."
|
||||
);
|
||||
|
||||
// Go back to the recording with memory data.
|
||||
rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
|
||||
setSelectedRecording(panel, 1);
|
||||
await rendered;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is still selected in the details view."
|
||||
);
|
||||
|
||||
is(
|
||||
callBtn.hidden,
|
||||
false,
|
||||
"The `memory-calltree` button is shown when recording has memory data."
|
||||
);
|
||||
is(
|
||||
flameBtn.hidden,
|
||||
false,
|
||||
"The `memory-flamegraph` button is shown when recording has memory data."
|
||||
);
|
||||
|
||||
selected = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED);
|
||||
rendered = once(MemoryCallTreeView, EVENTS.UI_MEMORY_CALL_TREE_RENDERED);
|
||||
DetailsView.selectView("memory-calltree");
|
||||
await selected;
|
||||
await rendered;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(MemoryCallTreeView),
|
||||
"The memory call tree view can be " +
|
||||
"selected again after going back to the view with memory data."
|
||||
);
|
||||
|
||||
selected = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED);
|
||||
rendered = once(MemoryFlameGraphView, EVENTS.UI_MEMORY_FLAMEGRAPH_RENDERED);
|
||||
DetailsView.selectView("memory-flamegraph");
|
||||
await selected;
|
||||
await rendered;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(MemoryFlameGraphView),
|
||||
"The memory flamegraph view can " +
|
||||
"be selected again after going back to the view with memory data."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,253 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the details view hides the toolbar buttons when a recording
|
||||
* doesn't exist or is in progress.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
setSelectedRecording,
|
||||
getSelectedRecordingIndex,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, $, PerformanceController, WaterfallView } = panel.panelWin;
|
||||
|
||||
const waterfallBtn = $("toolbarbutton[data-view='waterfall']");
|
||||
const jsFlameBtn = $("toolbarbutton[data-view='js-flamegraph']");
|
||||
const jsCallBtn = $("toolbarbutton[data-view='js-calltree']");
|
||||
const memFlameBtn = $("toolbarbutton[data-view='memory-flamegraph']");
|
||||
const memCallBtn = $("toolbarbutton[data-view='memory-calltree']");
|
||||
|
||||
is(
|
||||
waterfallBtn.hidden,
|
||||
true,
|
||||
"The `waterfall` button is hidden when tool starts."
|
||||
);
|
||||
is(
|
||||
jsFlameBtn.hidden,
|
||||
true,
|
||||
"The `js-flamegraph` button is hidden when tool starts."
|
||||
);
|
||||
is(
|
||||
jsCallBtn.hidden,
|
||||
true,
|
||||
"The `js-calltree` button is hidden when tool starts."
|
||||
);
|
||||
is(
|
||||
memFlameBtn.hidden,
|
||||
true,
|
||||
"The `memory-flamegraph` button is hidden when tool starts."
|
||||
);
|
||||
is(
|
||||
memCallBtn.hidden,
|
||||
true,
|
||||
"The `memory-calltree` button is hidden when tool starts."
|
||||
);
|
||||
|
||||
await startRecording(panel);
|
||||
|
||||
is(
|
||||
waterfallBtn.hidden,
|
||||
true,
|
||||
"The `waterfall` button is hidden when recording starts."
|
||||
);
|
||||
is(
|
||||
jsFlameBtn.hidden,
|
||||
true,
|
||||
"The `js-flamegraph` button is hidden when recording starts."
|
||||
);
|
||||
is(
|
||||
jsCallBtn.hidden,
|
||||
true,
|
||||
"The `js-calltree` button is hidden when recording starts."
|
||||
);
|
||||
is(
|
||||
memFlameBtn.hidden,
|
||||
true,
|
||||
"The `memory-flamegraph` button is hidden when recording starts."
|
||||
);
|
||||
is(
|
||||
memCallBtn.hidden,
|
||||
true,
|
||||
"The `memory-calltree` button is hidden when recording starts."
|
||||
);
|
||||
|
||||
await stopRecording(panel);
|
||||
|
||||
is(
|
||||
waterfallBtn.hidden,
|
||||
false,
|
||||
"The `waterfall` button is visible when recording ends."
|
||||
);
|
||||
is(
|
||||
jsFlameBtn.hidden,
|
||||
false,
|
||||
"The `js-flamegraph` button is visible when recording ends."
|
||||
);
|
||||
is(
|
||||
jsCallBtn.hidden,
|
||||
false,
|
||||
"The `js-calltree` button is visible when recording ends."
|
||||
);
|
||||
is(
|
||||
memFlameBtn.hidden,
|
||||
true,
|
||||
"The `memory-flamegraph` button is hidden when recording ends."
|
||||
);
|
||||
is(
|
||||
memCallBtn.hidden,
|
||||
true,
|
||||
"The `memory-calltree` button is hidden when recording ends."
|
||||
);
|
||||
|
||||
await startRecording(panel);
|
||||
|
||||
is(
|
||||
waterfallBtn.hidden,
|
||||
true,
|
||||
"The `waterfall` button is hidden when another recording starts."
|
||||
);
|
||||
is(
|
||||
jsFlameBtn.hidden,
|
||||
true,
|
||||
"The `js-flamegraph` button is hidden when another recording starts."
|
||||
);
|
||||
is(
|
||||
jsCallBtn.hidden,
|
||||
true,
|
||||
"The `js-calltree` button is hidden when another recording starts."
|
||||
);
|
||||
is(
|
||||
memFlameBtn.hidden,
|
||||
true,
|
||||
"The `memory-flamegraph` button is hidden when another recording starts."
|
||||
);
|
||||
is(
|
||||
memCallBtn.hidden,
|
||||
true,
|
||||
"The `memory-calltree` button is hidden when another recording starts."
|
||||
);
|
||||
|
||||
let selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
|
||||
let rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
|
||||
setSelectedRecording(panel, 0);
|
||||
await selected;
|
||||
await rendered;
|
||||
|
||||
let selectedIndex = getSelectedRecordingIndex(panel);
|
||||
is(selectedIndex, 0, "The first recording was selected again.");
|
||||
|
||||
is(
|
||||
waterfallBtn.hidden,
|
||||
false,
|
||||
"The `waterfall` button is visible when first recording selected."
|
||||
);
|
||||
is(
|
||||
jsFlameBtn.hidden,
|
||||
false,
|
||||
"The `js-flamegraph` button is visible when first recording selected."
|
||||
);
|
||||
is(
|
||||
jsCallBtn.hidden,
|
||||
false,
|
||||
"The `js-calltree` button is visible when first recording selected."
|
||||
);
|
||||
is(
|
||||
memFlameBtn.hidden,
|
||||
true,
|
||||
"The `memory-flamegraph` button is hidden when first recording selected."
|
||||
);
|
||||
is(
|
||||
memCallBtn.hidden,
|
||||
true,
|
||||
"The `memory-calltree` button is hidden when first recording selected."
|
||||
);
|
||||
|
||||
selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
|
||||
setSelectedRecording(panel, 1);
|
||||
await selected;
|
||||
|
||||
selectedIndex = getSelectedRecordingIndex(panel);
|
||||
is(selectedIndex, 1, "The second recording was selected again.");
|
||||
|
||||
is(
|
||||
waterfallBtn.hidden,
|
||||
true,
|
||||
"The `waterfall button` still is hidden when second recording selected."
|
||||
);
|
||||
is(
|
||||
jsFlameBtn.hidden,
|
||||
true,
|
||||
"The `js-flamegraph button` still is hidden when second recording selected."
|
||||
);
|
||||
is(
|
||||
jsCallBtn.hidden,
|
||||
true,
|
||||
"The `js-calltree button` still is hidden when second recording selected."
|
||||
);
|
||||
is(
|
||||
memFlameBtn.hidden,
|
||||
true,
|
||||
"The `memory-flamegraph button` still is hidden when second recording selected."
|
||||
);
|
||||
is(
|
||||
memCallBtn.hidden,
|
||||
true,
|
||||
"The `memory-calltree button` still is hidden when second recording selected."
|
||||
);
|
||||
|
||||
rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
|
||||
await stopRecording(panel);
|
||||
await rendered;
|
||||
|
||||
selectedIndex = getSelectedRecordingIndex(panel);
|
||||
is(selectedIndex, 1, "The second recording is still selected.");
|
||||
|
||||
is(
|
||||
waterfallBtn.hidden,
|
||||
false,
|
||||
"The `waterfall` button is visible when second recording finished."
|
||||
);
|
||||
is(
|
||||
jsFlameBtn.hidden,
|
||||
false,
|
||||
"The `js-flamegraph` button is visible when second recording finished."
|
||||
);
|
||||
is(
|
||||
jsCallBtn.hidden,
|
||||
false,
|
||||
"The `js-calltree` button is visible when second recording finished."
|
||||
);
|
||||
is(
|
||||
memFlameBtn.hidden,
|
||||
true,
|
||||
"The `memory-flamegraph` button is hidden when second recording finished."
|
||||
);
|
||||
is(
|
||||
memCallBtn.hidden,
|
||||
true,
|
||||
"The `memory-calltree` button is hidden when second recording finished."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,68 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the same details view is selected after recordings are cleared
|
||||
* and a new recording starts.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const {
|
||||
EVENTS,
|
||||
PerformanceController,
|
||||
DetailsView,
|
||||
JsCallTreeView,
|
||||
} = panel.panelWin;
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
const selected = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED);
|
||||
const rendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await selected;
|
||||
await rendered;
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(JsCallTreeView),
|
||||
"The js calltree view is now selected in the details view."
|
||||
);
|
||||
|
||||
const cleared = once(PerformanceController, EVENTS.RECORDING_SELECTED, {
|
||||
expectedArgs: [null],
|
||||
});
|
||||
await PerformanceController.clearRecordings();
|
||||
await cleared;
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel, {
|
||||
expectedViewClass: "JsCallTreeView",
|
||||
expectedViewEvent: "UI_JS_CALL_TREE_RENDERED",
|
||||
});
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(JsCallTreeView),
|
||||
"The js calltree view is still selected in the details view."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,105 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that when flame chart views scroll to change selection,
|
||||
* other detail views are rerendered.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
scrollCanvasGraph,
|
||||
HORIZONTAL_AXIS,
|
||||
} = require("devtools/client/performance/test/helpers/input-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const {
|
||||
EVENTS,
|
||||
OverviewView,
|
||||
DetailsView,
|
||||
WaterfallView,
|
||||
JsCallTreeView,
|
||||
JsFlameGraphView,
|
||||
} = panel.panelWin;
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
const waterfallRendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
|
||||
OverviewView.setTimeInterval({ startTime: 10, endTime: 20 });
|
||||
await waterfallRendered;
|
||||
|
||||
// Select the call tree to make sure it's initialized and ready to receive
|
||||
// redrawing requests once reselected.
|
||||
const callTreeRendered = once(
|
||||
JsCallTreeView,
|
||||
EVENTS.UI_JS_CALL_TREE_RENDERED
|
||||
);
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await callTreeRendered;
|
||||
|
||||
// Switch to the flamegraph and perform a scroll over the visualization.
|
||||
// The waterfall and call tree should get rerendered when reselected.
|
||||
const flamegraphRendered = once(
|
||||
JsFlameGraphView,
|
||||
EVENTS.UI_JS_FLAMEGRAPH_RENDERED
|
||||
);
|
||||
await DetailsView.selectView("js-flamegraph");
|
||||
await flamegraphRendered;
|
||||
|
||||
const overviewRangeSelected = once(
|
||||
OverviewView,
|
||||
EVENTS.UI_OVERVIEW_RANGE_SELECTED
|
||||
);
|
||||
const waterfallRerendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
|
||||
const callTreeRerendered = once(
|
||||
JsCallTreeView,
|
||||
EVENTS.UI_JS_CALL_TREE_RENDERED
|
||||
);
|
||||
|
||||
once(JsFlameGraphView, EVENTS.UI_JS_FLAMEGRAPH_RENDERED).then(() => {
|
||||
ok(
|
||||
false,
|
||||
"FlameGraphView should not publicly rerender, the internal state " +
|
||||
"and drawing should be handled by the underlying widget."
|
||||
);
|
||||
});
|
||||
|
||||
// Reset the range to full view, trigger a "selection" event as if
|
||||
// our mouse has done this
|
||||
scrollCanvasGraph(JsFlameGraphView.graph, {
|
||||
axis: HORIZONTAL_AXIS,
|
||||
wheel: 200,
|
||||
x: 10,
|
||||
});
|
||||
|
||||
await overviewRangeSelected;
|
||||
ok(true, "Overview range was changed.");
|
||||
|
||||
await DetailsView.selectView("waterfall");
|
||||
await waterfallRerendered;
|
||||
ok(true, "Waterfall rerendered by flame graph changing interval.");
|
||||
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await callTreeRerendered;
|
||||
ok(true, "CallTree rerendered by flame graph changing interval.");
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,63 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that events don't bleed between detail views.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
// The waterfall should render by default, and we want to make
|
||||
// sure that the render events don't bleed between detail views
|
||||
// so test that's the case after both views have been created.
|
||||
const callTreeRendered = once(
|
||||
JsCallTreeView,
|
||||
EVENTS.UI_JS_CALL_TREE_RENDERED
|
||||
);
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await callTreeRendered;
|
||||
|
||||
const waterfallSelected = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED);
|
||||
await DetailsView.selectView("waterfall");
|
||||
await waterfallSelected;
|
||||
|
||||
once(JsCallTreeView, EVENTS.UI_WATERFALL_RENDERED).then(() =>
|
||||
ok(false, "JsCallTreeView should not receive UI_WATERFALL_RENDERED event.")
|
||||
);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
const callTreeRerendered = once(
|
||||
JsCallTreeView,
|
||||
EVENTS.UI_JS_CALL_TREE_RENDERED
|
||||
);
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await callTreeRerendered;
|
||||
|
||||
ok(true, "Test passed.");
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,53 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the waterfall view renders content after recording.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { DetailsView, WaterfallView } = panel.panelWin;
|
||||
|
||||
await startRecording(panel);
|
||||
// Already waits for EVENTS.UI_WATERFALL_RENDERED.
|
||||
await stopRecording(panel);
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is selected by default in the details view."
|
||||
);
|
||||
|
||||
ok(true, "WaterfallView rendered after recording is stopped.");
|
||||
|
||||
await startRecording(panel);
|
||||
// Already waits for EVENTS.UI_WATERFALL_RENDERED.
|
||||
await stopRecording(panel);
|
||||
|
||||
ok(
|
||||
DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is still selected in the details view."
|
||||
);
|
||||
|
||||
ok(
|
||||
true,
|
||||
"WaterfallView rendered again after recording completed a second time."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,51 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the js call tree view renders content after recording.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
const rendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await rendered;
|
||||
|
||||
ok(true, "JsCallTreeView rendered after recording is stopped.");
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel, {
|
||||
expectedViewClass: "JsCallTreeView",
|
||||
expectedViewEvent: "UI_JS_CALL_TREE_RENDERED",
|
||||
});
|
||||
|
||||
ok(
|
||||
true,
|
||||
"JsCallTreeView rendered again after recording completed a second time."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,51 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the js flamegraph view renders content after recording.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, DetailsView, JsFlameGraphView } = panel.panelWin;
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
const rendered = once(JsFlameGraphView, EVENTS.UI_JS_FLAMEGRAPH_RENDERED);
|
||||
await DetailsView.selectView("js-flamegraph");
|
||||
await rendered;
|
||||
|
||||
ok(true, "JsFlameGraphView rendered after recording is stopped.");
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel, {
|
||||
expectedViewClass: "JsFlameGraphView",
|
||||
expectedViewEvent: "UI_JS_FLAMEGRAPH_RENDERED",
|
||||
});
|
||||
|
||||
ok(
|
||||
true,
|
||||
"JsFlameGraphView rendered again after recording completed a second time."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,60 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the memory call tree view renders content after recording.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_ENABLE_ALLOCATIONS_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, DetailsView, MemoryCallTreeView } = panel.panelWin;
|
||||
|
||||
// Enable allocations to test.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_ALLOCATIONS_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
const rendered = once(
|
||||
MemoryCallTreeView,
|
||||
EVENTS.UI_MEMORY_CALL_TREE_RENDERED
|
||||
);
|
||||
await DetailsView.selectView("memory-calltree");
|
||||
await rendered;
|
||||
|
||||
ok(true, "MemoryCallTreeView rendered after recording is stopped.");
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel, {
|
||||
expectedViewClass: "MemoryCallTreeView",
|
||||
expectedViewEvent: "UI_MEMORY_CALL_TREE_RENDERED",
|
||||
});
|
||||
|
||||
ok(
|
||||
true,
|
||||
"MemoryCallTreeView rendered again after recording completed a second time."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,60 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the memory call tree view renders content after recording.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_ENABLE_ALLOCATIONS_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, DetailsView, MemoryFlameGraphView } = panel.panelWin;
|
||||
|
||||
// Enable allocations to test.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_ALLOCATIONS_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
const rendered = once(
|
||||
MemoryFlameGraphView,
|
||||
EVENTS.UI_MEMORY_FLAMEGRAPH_RENDERED
|
||||
);
|
||||
await DetailsView.selectView("memory-flamegraph");
|
||||
await rendered;
|
||||
|
||||
ok(true, "MemoryFlameGraphView rendered after recording is stopped.");
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel, {
|
||||
expectedViewClass: "MemoryFlameGraphView",
|
||||
expectedViewEvent: "UI_MEMORY_FLAMEGRAPH_RENDERED",
|
||||
});
|
||||
|
||||
ok(
|
||||
true,
|
||||
"MemoryFlameGraphView rendered again after recording completed a second time."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,66 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the sidebar is updated with "DOMContentLoaded" and "load" markers.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
reload,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
waitUntil,
|
||||
} = require("devtools/client/performance/test/helpers/wait-utils");
|
||||
|
||||
add_task(async function() {
|
||||
// Run this test without server targets as reload would introduce a target switch
|
||||
// with a new profile record which wouldn't introduce the DOM load events.
|
||||
// This panel has now been deprecated, we might later get rid of this test
|
||||
// once removing client side targets (bug 1721852)
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["devtools.target-switching.server.enabled", false]],
|
||||
});
|
||||
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { PerformanceController } = panel.panelWin;
|
||||
|
||||
await startRecording(panel);
|
||||
await reload(panel);
|
||||
|
||||
await waitUntil(() => {
|
||||
// Wait until we get the necessary markers.
|
||||
const markers = PerformanceController.getCurrentRecording().getMarkers();
|
||||
if (
|
||||
!markers.some(m => m.name == "document::DOMContentLoaded") ||
|
||||
!markers.some(m => m.name == "document::Load")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok(
|
||||
markers.filter(m => m.name == "document::DOMContentLoaded").length == 1,
|
||||
"There should only be one `DOMContentLoaded` marker."
|
||||
);
|
||||
ok(
|
||||
markers.filter(m => m.name == "document::Load").length == 1,
|
||||
"There should only be one `load` marker."
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
await stopRecording(panel);
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,58 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test behavior while target-switching.
|
||||
*/
|
||||
|
||||
const {
|
||||
MAIN_PROCESS_URL,
|
||||
SIMPLE_URL: CONTENT_PROCESS_URL,
|
||||
} = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
BrowserTestUtils,
|
||||
} = require("resource://testing-common/BrowserTestUtils.jsm");
|
||||
const {
|
||||
addTab,
|
||||
} = require("devtools/client/performance/test/helpers/tab-utils");
|
||||
const {
|
||||
initPerformanceInTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
|
||||
add_task(async function() {
|
||||
info("Open a page running on content process");
|
||||
const tab = await addTab({
|
||||
url: CONTENT_PROCESS_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
info("Open the performance panel");
|
||||
const { panel } = await initPerformanceInTab({ tab });
|
||||
const { PerformanceController, PerformanceView, EVENTS } = panel.panelWin;
|
||||
|
||||
info("Start recording");
|
||||
await startRecording(panel);
|
||||
await PerformanceView.once(EVENTS.UI_RECORDING_PROFILER_STATUS_RENDERED);
|
||||
|
||||
info("Navigate to a page running on main process");
|
||||
BrowserTestUtils.loadURI(tab.linkedBrowser, MAIN_PROCESS_URL);
|
||||
await PerformanceView.once(EVENTS.UI_RECORDING_PROFILER_STATUS_RENDERED);
|
||||
|
||||
info("Return to a page running on content process again");
|
||||
BrowserTestUtils.loadURI(tab.linkedBrowser, CONTENT_PROCESS_URL);
|
||||
await PerformanceView.once(EVENTS.UI_RECORDING_PROFILER_STATUS_RENDERED);
|
||||
|
||||
info("Stop recording");
|
||||
await stopRecording(panel);
|
||||
|
||||
const recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 3, "Have a record for every target-switching");
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,146 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Tests that the marker details on GC markers displays allocation
|
||||
* buttons and snaps to the correct range
|
||||
*/
|
||||
async function spawnTest() {
|
||||
let { panel } = await initPerformance(ALLOCS_URL);
|
||||
let { $, $$, EVENTS, PerformanceController, OverviewView, DetailsView, WaterfallView, MemoryCallTreeView } = panel.panelWin;
|
||||
let EPSILON = 0.00001;
|
||||
|
||||
Services.prefs.setBoolPref(ALLOCATIONS_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
await idleWait(1000);
|
||||
await stopRecording(panel);
|
||||
|
||||
injectGCMarkers(PerformanceController, WaterfallView);
|
||||
|
||||
// Select everything
|
||||
let rendered = WaterfallView.once(EVENTS.UI_WATERFALL_RENDERED);
|
||||
OverviewView.setTimeInterval({ startTime: 0, endTime: Number.MAX_VALUE });
|
||||
await rendered;
|
||||
|
||||
let bars = $$(".waterfall-marker-bar");
|
||||
let gcMarkers = PerformanceController.getCurrentRecording().getMarkers();
|
||||
ok(gcMarkers.length === 9, "should have 9 GC markers");
|
||||
ok(bars.length === 9, "should have 9 GC markers rendered");
|
||||
|
||||
/**
|
||||
* Check when it's the second marker of the first GC cycle.
|
||||
*/
|
||||
|
||||
let targetMarker = gcMarkers[1];
|
||||
let targetBar = bars[1];
|
||||
info(`Clicking GC Marker of type ${targetMarker.causeName} ${targetMarker.start}:${targetMarker.end}`);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, targetBar);
|
||||
let showAllocsButton;
|
||||
// On slower machines this can not be found immediately?
|
||||
await waitUntil(() => showAllocsButton = $("#waterfall-details .custom-button[type='show-allocations']"));
|
||||
ok(showAllocsButton, "GC buttons when allocations are enabled");
|
||||
|
||||
rendered = once(MemoryCallTreeView, EVENTS.UI_MEMORY_CALL_TREE_RENDERED);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, showAllocsButton);
|
||||
await rendered;
|
||||
|
||||
is(OverviewView.getTimeInterval().startTime, 0, "When clicking first GC, should use 0 as start time");
|
||||
within(OverviewView.getTimeInterval().endTime, targetMarker.start, EPSILON, "Correct end time range");
|
||||
|
||||
let duration = PerformanceController.getCurrentRecording().getDuration();
|
||||
rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
|
||||
OverviewView.setTimeInterval({ startTime: 0, endTime: duration });
|
||||
await DetailsView.selectView("waterfall");
|
||||
await rendered;
|
||||
|
||||
/**
|
||||
* Check when there is a previous GC cycle
|
||||
*/
|
||||
|
||||
bars = $$(".waterfall-marker-bar");
|
||||
targetMarker = gcMarkers[4];
|
||||
targetBar = bars[4];
|
||||
|
||||
info(`Clicking GC Marker of type ${targetMarker.causeName} ${targetMarker.start}:${targetMarker.end}`);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, targetBar);
|
||||
// On slower machines this can not be found immediately?
|
||||
await waitUntil(() => showAllocsButton = $("#waterfall-details .custom-button[type='show-allocations']"));
|
||||
ok(showAllocsButton, "GC buttons when allocations are enabled");
|
||||
|
||||
rendered = once(MemoryCallTreeView, EVENTS.UI_MEMORY_CALL_TREE_RENDERED);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, showAllocsButton);
|
||||
await rendered;
|
||||
|
||||
within(OverviewView.getTimeInterval().startTime, gcMarkers[2].end, EPSILON,
|
||||
"selection start range is last marker from previous GC cycle.");
|
||||
within(OverviewView.getTimeInterval().endTime, targetMarker.start, EPSILON,
|
||||
"selection end range is current GC marker's start time");
|
||||
|
||||
/**
|
||||
* Now with allocations disabled
|
||||
*/
|
||||
|
||||
// Reselect the entire recording -- due to bug 1196945, the new recording
|
||||
// won't reset the selection
|
||||
duration = PerformanceController.getCurrentRecording().getDuration();
|
||||
rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
|
||||
OverviewView.setTimeInterval({ startTime: 0, endTime: duration });
|
||||
await rendered;
|
||||
|
||||
Services.prefs.setBoolPref(ALLOCATIONS_PREF, false);
|
||||
await startRecording(panel);
|
||||
rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
|
||||
await stopRecording(panel);
|
||||
await rendered;
|
||||
|
||||
injectGCMarkers(PerformanceController, WaterfallView);
|
||||
|
||||
// Select everything
|
||||
rendered = WaterfallView.once(EVENTS.UI_WATERFALL_RENDERED);
|
||||
OverviewView.setTimeInterval({ startTime: 0, endTime: Number.MAX_VALUE });
|
||||
await rendered;
|
||||
|
||||
ok(true, "WaterfallView rendered after recording is stopped.");
|
||||
|
||||
bars = $$(".waterfall-marker-bar");
|
||||
gcMarkers = PerformanceController.getCurrentRecording().getMarkers();
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, bars[0]);
|
||||
showAllocsButton = $("#waterfall-details .custom-button[type='show-allocations']");
|
||||
ok(!showAllocsButton, "No GC buttons when allocations are disabled");
|
||||
|
||||
|
||||
await teardown(panel);
|
||||
finish();
|
||||
}
|
||||
|
||||
function injectGCMarkers(controller, waterfall) {
|
||||
// Push some fake GC markers into the recording
|
||||
let realMarkers = controller.getCurrentRecording().getMarkers();
|
||||
// Invalidate marker cache
|
||||
waterfall._cache.delete(realMarkers);
|
||||
realMarkers.length = 0;
|
||||
for (let gcMarker of GC_MARKERS) {
|
||||
realMarkers.push(gcMarker);
|
||||
}
|
||||
}
|
||||
|
||||
var GC_MARKERS = [
|
||||
{ causeName: "TOO_MUCH_MALLOC", cycle: 1 },
|
||||
{ causeName: "TOO_MUCH_MALLOC", cycle: 1 },
|
||||
{ causeName: "TOO_MUCH_MALLOC", cycle: 1 },
|
||||
{ causeName: "ALLOC_TRIGGER", cycle: 2 },
|
||||
{ causeName: "ALLOC_TRIGGER", cycle: 2 },
|
||||
{ causeName: "ALLOC_TRIGGER", cycle: 2 },
|
||||
{ causeName: "SET_NEW_DOCUMENT", cycle: 3 },
|
||||
{ causeName: "SET_NEW_DOCUMENT", cycle: 3 },
|
||||
{ causeName: "SET_NEW_DOCUMENT", cycle: 3 },
|
||||
].map((marker, i) => {
|
||||
marker.name = "GarbageCollection";
|
||||
marker.start = 50 + (i * 10);
|
||||
marker.end = marker.start + 9;
|
||||
return marker;
|
||||
});
|
||||
/* eslint-enable */
|
@ -1,66 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the toolbox tab for performance is highlighted when recording,
|
||||
* whether already loaded, or via console.profile with an unloaded performance tools.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInTab,
|
||||
initConsoleInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
waitUntil,
|
||||
} = require("devtools/client/performance/test/helpers/wait-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { target, toolbox, console } = await initConsoleInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const tab = toolbox.doc.getElementById("toolbox-tab-performance");
|
||||
|
||||
await console.profile("rust");
|
||||
await waitUntil(() => tab.classList.contains("highlighted"));
|
||||
|
||||
ok(
|
||||
tab.classList.contains("highlighted"),
|
||||
"Performance tab is highlighted during " +
|
||||
"recording from console.profile when unloaded."
|
||||
);
|
||||
|
||||
await console.profileEnd("rust");
|
||||
await waitUntil(() => !tab.classList.contains("highlighted"));
|
||||
|
||||
ok(
|
||||
!tab.classList.contains("highlighted"),
|
||||
"Performance tab is no longer highlighted when console.profile recording finishes."
|
||||
);
|
||||
|
||||
const { panel } = await initPerformanceInTab({ tab: target.localTab });
|
||||
|
||||
await startRecording(panel);
|
||||
|
||||
ok(
|
||||
tab.classList.contains("highlighted"),
|
||||
"Performance tab is highlighted during recording while in performance tool."
|
||||
);
|
||||
|
||||
await stopRecording(panel);
|
||||
|
||||
ok(
|
||||
!tab.classList.contains("highlighted"),
|
||||
"Performance tab is no longer highlighted when recording finishes."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,80 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the recordings view shows the right label while recording, after
|
||||
* recording, and once the record has loaded.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
getSelectedRecording,
|
||||
getDurationLabelText,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, PerformanceController } = panel.panelWin;
|
||||
const { L10N } = require("devtools/client/performance/modules/global");
|
||||
|
||||
await startRecording(panel);
|
||||
|
||||
is(
|
||||
getDurationLabelText(panel, 0),
|
||||
L10N.getStr("recordingsList.recordingLabel"),
|
||||
"The duration node should show the 'recording' message while recording"
|
||||
);
|
||||
|
||||
const recordingStopping = once(
|
||||
PerformanceController,
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
{
|
||||
expectedArgs: ["recording-stopping"],
|
||||
}
|
||||
);
|
||||
const recordingStopped = once(
|
||||
PerformanceController,
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
{
|
||||
expectedArgs: ["recording-stopped"],
|
||||
}
|
||||
);
|
||||
const everythingStopped = stopRecording(panel);
|
||||
|
||||
await recordingStopping;
|
||||
is(
|
||||
getDurationLabelText(panel, 0),
|
||||
L10N.getStr("recordingsList.loadingLabel"),
|
||||
"The duration node should show the 'loading' message while stopping"
|
||||
);
|
||||
|
||||
await recordingStopped;
|
||||
const selected = getSelectedRecording(panel);
|
||||
is(
|
||||
getDurationLabelText(panel, 0),
|
||||
L10N.getFormatStr(
|
||||
"recordingsList.durationLabel",
|
||||
selected.getDuration().toFixed(0)
|
||||
),
|
||||
"The duration node should show the duration after the record has stopped"
|
||||
);
|
||||
|
||||
await everythingStopped;
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,129 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the details view is locked after recording has stopped and before
|
||||
* the recording has finished loading.
|
||||
* Also test that the details view isn't locked if the recording that is being
|
||||
* stopped isn't the active one.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
const {
|
||||
getSelectedRecordingIndex,
|
||||
setSelectedRecording,
|
||||
} = require("devtools/client/performance/test/helpers/recording-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, $, PerformanceController } = panel.panelWin;
|
||||
const detailsContainer = $("#details-pane-container");
|
||||
const recordingNotice = $("#recording-notice");
|
||||
const loadingNotice = $("#loading-notice");
|
||||
const detailsPane = $("#details-pane");
|
||||
|
||||
await startRecording(panel);
|
||||
|
||||
is(
|
||||
detailsContainer.selectedPanel,
|
||||
recordingNotice,
|
||||
"The recording-notice is shown while recording."
|
||||
);
|
||||
|
||||
let recordingStopping = once(
|
||||
PerformanceController,
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
{
|
||||
expectedArgs: ["recording-stopping"],
|
||||
}
|
||||
);
|
||||
let recordingStopped = once(
|
||||
PerformanceController,
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
{
|
||||
expectedArgs: ["recording-stopped"],
|
||||
}
|
||||
);
|
||||
let everythingStopped = stopRecording(panel);
|
||||
|
||||
await recordingStopping;
|
||||
is(
|
||||
detailsContainer.selectedPanel,
|
||||
loadingNotice,
|
||||
"The loading-notice is shown while the record is stopping."
|
||||
);
|
||||
|
||||
await recordingStopped;
|
||||
is(
|
||||
detailsContainer.selectedPanel,
|
||||
detailsPane,
|
||||
"The details panel is shown after the record has stopped."
|
||||
);
|
||||
|
||||
await everythingStopped;
|
||||
await startRecording(panel);
|
||||
|
||||
info("While the 2nd record is still going, switch to the first one.");
|
||||
const recordingSelected = once(
|
||||
PerformanceController,
|
||||
EVENTS.RECORDING_SELECTED
|
||||
);
|
||||
setSelectedRecording(panel, 0);
|
||||
await recordingSelected;
|
||||
|
||||
recordingStopping = once(
|
||||
PerformanceController,
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
{
|
||||
expectedArgs: ["recording-stopping"],
|
||||
}
|
||||
);
|
||||
recordingStopped = once(
|
||||
PerformanceController,
|
||||
EVENTS.RECORDING_STATE_CHANGE,
|
||||
{
|
||||
expectedArgs: ["recording-stopped"],
|
||||
}
|
||||
);
|
||||
everythingStopped = stopRecording(panel);
|
||||
|
||||
await recordingStopping;
|
||||
is(
|
||||
detailsContainer.selectedPanel,
|
||||
detailsPane,
|
||||
"The details panel is still shown while the 2nd record is being stopped."
|
||||
);
|
||||
is(
|
||||
getSelectedRecordingIndex(panel),
|
||||
0,
|
||||
"The first record is still selected."
|
||||
);
|
||||
|
||||
await recordingStopped;
|
||||
|
||||
is(
|
||||
detailsContainer.selectedPanel,
|
||||
detailsPane,
|
||||
"The details panel is still shown after the 2nd record has stopped."
|
||||
);
|
||||
is(getSelectedRecordingIndex(panel), 1, "The second record is now selected.");
|
||||
|
||||
await everythingStopped;
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,143 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Tests if the Marker Details view renders all properties expected
|
||||
* for each marker.
|
||||
*/
|
||||
|
||||
async function spawnTest() {
|
||||
let { target, panel } = await initPerformance(MARKERS_URL);
|
||||
let { $, $$, EVENTS, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
|
||||
|
||||
// Hijack the markers massaging part of creating the waterfall view,
|
||||
// to prevent collapsing markers and allowing this test to verify
|
||||
// everything individually. A better solution would be to just expand
|
||||
// all markers first and then skip the meta nodes, but I'm lazy.
|
||||
WaterfallView._prepareWaterfallTree = markers => {
|
||||
return { submarkers: markers };
|
||||
};
|
||||
|
||||
const MARKER_TYPES = [
|
||||
"Styles", "Reflow", "ConsoleTime", "TimeStamp"
|
||||
];
|
||||
|
||||
await startRecording(panel);
|
||||
ok(true, "Recording has started.");
|
||||
|
||||
await waitUntil(() => {
|
||||
// Wait until we get all the different markers.
|
||||
let markers = PerformanceController.getCurrentRecording().getMarkers();
|
||||
return MARKER_TYPES.every(type => markers.some(m => m.name === type));
|
||||
});
|
||||
|
||||
await stopRecording(panel);
|
||||
ok(true, "Recording has ended.");
|
||||
|
||||
info("No need to select everything in the timeline.");
|
||||
info("All the markers should be displayed by default.");
|
||||
|
||||
let bars = Array.prototype.filter.call($$(".waterfall-marker-bar"),
|
||||
(bar) => MARKER_TYPES.includes(bar.getAttribute("type")));
|
||||
let markers = PerformanceController.getCurrentRecording().getMarkers()
|
||||
.filter(m => MARKER_TYPES.includes(m.name));
|
||||
|
||||
info(`Got ${bars.length} bars and ${markers.length} markers.`);
|
||||
info("Markers types from datasrc: " + Array.from(markers, e => e.name));
|
||||
info("Markers names from sidebar: " + Array.from(bars, e => e.parentNode.parentNode.querySelector(".waterfall-marker-name").getAttribute("value")));
|
||||
|
||||
ok(bars.length >= MARKER_TYPES.length, `Got at least ${MARKER_TYPES.length} markers (1)`);
|
||||
ok(markers.length >= MARKER_TYPES.length, `Got at least ${MARKER_TYPES.length} markers (2)`);
|
||||
|
||||
// Sanity check that markers are in chronologically ascending order
|
||||
markers.reduce((previous, m) => {
|
||||
if (m.start <= previous) {
|
||||
ok(false, "Markers are not in order");
|
||||
info(markers);
|
||||
}
|
||||
return m.start;
|
||||
}, 0);
|
||||
|
||||
// Override the timestamp marker's stack with our own recursive stack, which
|
||||
// can happen for unknown reasons (bug 1246555); we should not cause a crash
|
||||
// when attempting to render a recursive stack trace
|
||||
let timestampMarker = markers.find(m => m.name === "ConsoleTime");
|
||||
ok(typeof timestampMarker.stack === "number", "ConsoleTime marker has a stack before overwriting.");
|
||||
let frames = PerformanceController.getCurrentRecording().getFrames();
|
||||
let frameIndex = timestampMarker.stack = frames.length;
|
||||
frames.push({ line: 1, column: 1, source: "file.js", functionDisplayName: "test", parent: frameIndex + 1});
|
||||
frames.push({ line: 1, column: 1, source: "file.js", functionDisplayName: "test", parent: frameIndex + 2 });
|
||||
frames.push({ line: 1, column: 1, source: "file.js", functionDisplayName: "test", parent: frameIndex });
|
||||
|
||||
const tests = {
|
||||
ConsoleTime: function (marker) {
|
||||
info("Got `ConsoleTime` marker with data: " + JSON.stringify(marker));
|
||||
ok(marker.stack === frameIndex, "Should have the ConsoleTime marker with recursive stack");
|
||||
shouldHaveStack($, "startStack", marker);
|
||||
shouldHaveStack($, "endStack", marker);
|
||||
shouldHaveLabel($, "Timer Name:", "!!!", marker);
|
||||
return true;
|
||||
},
|
||||
TimeStamp: function (marker) {
|
||||
info("Got `TimeStamp` marker with data: " + JSON.stringify(marker));
|
||||
shouldHaveLabel($, "Label:", "go", marker);
|
||||
shouldHaveStack($, "stack", marker);
|
||||
return true;
|
||||
},
|
||||
Styles: function (marker) {
|
||||
info("Got `Styles` marker with data: " + JSON.stringify(marker));
|
||||
if (marker.stack) {
|
||||
shouldHaveStack($, "stack", marker);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
Reflow: function (marker) {
|
||||
info("Got `Reflow` marker with data: " + JSON.stringify(marker));
|
||||
if (marker.stack) {
|
||||
shouldHaveStack($, "stack", marker);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Keep track of all marker tests that are finished so we only
|
||||
// run through each marker test once, so we don't spam 500 redundant
|
||||
// tests.
|
||||
let testsDone = [];
|
||||
|
||||
for (let i = 0; i < bars.length; i++) {
|
||||
let bar = bars[i];
|
||||
let m = markers[i];
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, bar);
|
||||
|
||||
if (tests[m.name]) {
|
||||
if (!testsDone.includes(m.name)) {
|
||||
let fullTestComplete = tests[m.name](m);
|
||||
if (fullTestComplete) {
|
||||
testsDone.push(m.name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(`No tests for ${m.name} -- should be filtered out.`);
|
||||
}
|
||||
|
||||
if (testsDone.length === Object.keys(tests).length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await teardown(panel);
|
||||
finish();
|
||||
}
|
||||
|
||||
function shouldHaveStack($, type, marker) {
|
||||
ok($(`#waterfall-details .marker-details-stack[type=${type}]`), `${marker.name} has a stack: ${type}`);
|
||||
}
|
||||
|
||||
function shouldHaveLabel($, name, value, marker) {
|
||||
let $name = $(`#waterfall-details .marker-details-labelcontainer .marker-details-labelname[value="${name}"]`);
|
||||
let $value = $name.parentNode.querySelector(".marker-details-labelvalue");
|
||||
is($value.getAttribute("value"), value, `${marker.name} has correct label for ${name}:${value}`);
|
||||
}
|
||||
/* eslint-enable */
|
@ -1,40 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that toggling preferences before there are any recordings does not throw.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
await DetailsView.selectView("js-calltree");
|
||||
|
||||
// Manually call the _onPrefChanged function so we can catch an error.
|
||||
try {
|
||||
JsCallTreeView._onPrefChanged(null, "invert-call-tree", true);
|
||||
ok(
|
||||
true,
|
||||
"Toggling preferences before there are any recordings should not fail."
|
||||
);
|
||||
} catch (e) {
|
||||
ok(
|
||||
false,
|
||||
"Toggling preferences before there are any recordings should not fail."
|
||||
);
|
||||
}
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that toggling preferences during a recording does not throw.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await startRecording(panel);
|
||||
|
||||
// Manually call the _onPrefChanged function so we can catch an error.
|
||||
try {
|
||||
JsCallTreeView._onPrefChanged(null, "invert-call-tree", true);
|
||||
ok(true, "Toggling preferences during a recording should not fail.");
|
||||
} catch (e) {
|
||||
ok(false, "Toggling preferences during a recording should not fail.");
|
||||
}
|
||||
|
||||
await stopRecording(panel, {
|
||||
expectedViewClass: "JsCallTreeView",
|
||||
expectedViewEvent: "UI_JS_CALL_TREE_RENDERED",
|
||||
});
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,51 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that toggling meta option prefs change visibility of other options.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_EXPERIMENTAL_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
|
||||
add_task(async function() {
|
||||
Services.prefs.setBoolPref(UI_EXPERIMENTAL_PREF, false);
|
||||
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { $ } = panel.panelWin;
|
||||
const $body = $(".theme-body");
|
||||
const $menu = $("#performance-options-menupopup");
|
||||
|
||||
ok(
|
||||
!$body.classList.contains("experimental-enabled"),
|
||||
"The body node does not have `experimental-enabled` on start."
|
||||
);
|
||||
ok(
|
||||
!$menu.classList.contains("experimental-enabled"),
|
||||
"The menu popup does not have `experimental-enabled` on start."
|
||||
);
|
||||
|
||||
Services.prefs.setBoolPref(UI_EXPERIMENTAL_PREF, true);
|
||||
|
||||
ok(
|
||||
$body.classList.contains("experimental-enabled"),
|
||||
"The body node has `experimental-enabled` after toggle."
|
||||
);
|
||||
ok(
|
||||
$menu.classList.contains("experimental-enabled"),
|
||||
"The menu popup has `experimental-enabled` after toggle."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,77 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that `enable-framerate` toggles the visibility of the fps graph,
|
||||
* as well as enabling ticks data on the PerformanceFront.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_ENABLE_FRAMERATE_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
isVisible,
|
||||
} = require("devtools/client/performance/test/helpers/dom-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { $, PerformanceController } = panel.panelWin;
|
||||
|
||||
// Disable framerate to test.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_FRAMERATE_PREF, false);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration().withTicks,
|
||||
false,
|
||||
"PerformanceFront started without ticks recording."
|
||||
);
|
||||
ok(
|
||||
!isVisible($("#time-framerate")),
|
||||
"The fps graph is hidden when ticks disabled."
|
||||
);
|
||||
|
||||
// Re-enable framerate.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_FRAMERATE_PREF, true);
|
||||
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration().withTicks,
|
||||
false,
|
||||
"PerformanceFront still marked without ticks recording."
|
||||
);
|
||||
ok(
|
||||
!isVisible($("#time-framerate")),
|
||||
"The fps graph is still hidden if recording does not contain ticks."
|
||||
);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration().withTicks,
|
||||
true,
|
||||
"PerformanceFront started with ticks recording."
|
||||
);
|
||||
ok(
|
||||
isVisible($("#time-framerate")),
|
||||
"The fps graph is not hidden when ticks enabled before recording."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,57 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that toggling `enable-memory` during a recording doesn't change that
|
||||
* recording's state and does not break.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_ENABLE_FRAMERATE_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { PerformanceController } = panel.panelWin;
|
||||
|
||||
// Test starting without framerate, and stopping with it.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_FRAMERATE_PREF, false);
|
||||
await startRecording(panel);
|
||||
|
||||
Services.prefs.setBoolPref(UI_ENABLE_FRAMERATE_PREF, true);
|
||||
await stopRecording(panel);
|
||||
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration().withTicks,
|
||||
false,
|
||||
"The recording finished without tracking framerate."
|
||||
);
|
||||
|
||||
// Test starting with framerate, and stopping without it.
|
||||
await startRecording(panel);
|
||||
|
||||
Services.prefs.setBoolPref(UI_ENABLE_FRAMERATE_PREF, false);
|
||||
await stopRecording(panel);
|
||||
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration().withTicks,
|
||||
true,
|
||||
"The recording finished with tracking framerate."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,96 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that `enable-memory` toggles the visibility of the memory graph,
|
||||
* as well as enabling memory data on the PerformanceFront.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_ENABLE_MEMORY_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
isVisible,
|
||||
} = require("devtools/client/performance/test/helpers/dom-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { $, PerformanceController } = panel.panelWin;
|
||||
|
||||
// Disable memory to test.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_MEMORY_PREF, false);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration().withMemory,
|
||||
false,
|
||||
"PerformanceFront started without memory recording."
|
||||
);
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration()
|
||||
.withAllocations,
|
||||
false,
|
||||
"PerformanceFront started without allocations recording."
|
||||
);
|
||||
ok(
|
||||
!isVisible($("#memory-overview")),
|
||||
"The memory graph is hidden when memory disabled."
|
||||
);
|
||||
|
||||
// Re-enable memory.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_MEMORY_PREF, true);
|
||||
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration().withMemory,
|
||||
false,
|
||||
"PerformanceFront still marked without memory recording."
|
||||
);
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration()
|
||||
.withAllocations,
|
||||
false,
|
||||
"PerformanceFront still marked without allocations recording."
|
||||
);
|
||||
ok(
|
||||
!isVisible($("#memory-overview")),
|
||||
"memory graph is still hidden after enabling " +
|
||||
"if recording did not start recording memory"
|
||||
);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration().withMemory,
|
||||
true,
|
||||
"PerformanceFront started with memory recording."
|
||||
);
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration()
|
||||
.withAllocations,
|
||||
false,
|
||||
"PerformanceFront did not record with allocations."
|
||||
);
|
||||
ok(
|
||||
isVisible($("#memory-overview")),
|
||||
"The memory graph is not hidden when memory enabled before recording."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,69 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that toggling `enable-memory` during a recording doesn't change that
|
||||
* recording's state and does not break.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_ENABLE_MEMORY_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { PerformanceController } = panel.panelWin;
|
||||
|
||||
// Test starting without memory, and stopping with it.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_MEMORY_PREF, false);
|
||||
await startRecording(panel);
|
||||
|
||||
Services.prefs.setBoolPref(UI_ENABLE_MEMORY_PREF, true);
|
||||
await stopRecording(panel);
|
||||
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration().withMemory,
|
||||
false,
|
||||
"The recording finished without tracking memory."
|
||||
);
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration()
|
||||
.withAllocations,
|
||||
false,
|
||||
"The recording finished without tracking allocations."
|
||||
);
|
||||
|
||||
// Test starting with memory, and stopping without it.
|
||||
await startRecording(panel);
|
||||
|
||||
Services.prefs.setBoolPref(UI_ENABLE_MEMORY_PREF, false);
|
||||
await stopRecording(panel);
|
||||
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration().withMemory,
|
||||
true,
|
||||
"The recording finished with tracking memory."
|
||||
);
|
||||
is(
|
||||
PerformanceController.getCurrentRecording().getConfiguration()
|
||||
.withAllocations,
|
||||
false,
|
||||
"The recording still is not recording allocations."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,101 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the js flamegraphs get rerendered when toggling `flatten-tree-recursion`.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_FLATTEN_RECURSION_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const {
|
||||
EVENTS,
|
||||
PerformanceController,
|
||||
DetailsView,
|
||||
JsFlameGraphView,
|
||||
} = panel.panelWin;
|
||||
const {
|
||||
FlameGraphUtils,
|
||||
} = require("devtools/client/shared/widgets/FlameGraph");
|
||||
|
||||
Services.prefs.setBoolPref(UI_FLATTEN_RECURSION_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
let rendered = once(JsFlameGraphView, EVENTS.UI_JS_FLAMEGRAPH_RENDERED);
|
||||
await DetailsView.selectView("js-flamegraph");
|
||||
await rendered;
|
||||
|
||||
const thread1 = PerformanceController.getCurrentRecording().getProfile()
|
||||
.threads[0];
|
||||
const rendering1 = FlameGraphUtils._cache.get(thread1);
|
||||
|
||||
ok(thread1, "The samples were retrieved from the controller.");
|
||||
ok(rendering1, "The rendering data was cached.");
|
||||
|
||||
rendered = once(JsFlameGraphView, EVENTS.UI_JS_FLAMEGRAPH_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_FLATTEN_RECURSION_PREF, false);
|
||||
await rendered;
|
||||
ok(true, "JsFlameGraphView rerendered when toggling flatten-tree-recursion.");
|
||||
|
||||
const thread2 = PerformanceController.getCurrentRecording().getProfile()
|
||||
.threads[0];
|
||||
const rendering2 = FlameGraphUtils._cache.get(thread2);
|
||||
|
||||
is(
|
||||
thread1,
|
||||
thread2,
|
||||
"The same samples data should be retrieved from the controller (1)."
|
||||
);
|
||||
isnot(
|
||||
rendering1,
|
||||
rendering2,
|
||||
"The rendering data should be different because other options were used (1)."
|
||||
);
|
||||
|
||||
rendered = once(JsFlameGraphView, EVENTS.UI_JS_FLAMEGRAPH_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_FLATTEN_RECURSION_PREF, true);
|
||||
await rendered;
|
||||
ok(
|
||||
true,
|
||||
"JsFlameGraphView rerendered when toggling back flatten-tree-recursion."
|
||||
);
|
||||
|
||||
const thread3 = PerformanceController.getCurrentRecording().getProfile()
|
||||
.threads[0];
|
||||
const rendering3 = FlameGraphUtils._cache.get(thread3);
|
||||
|
||||
is(
|
||||
thread2,
|
||||
thread3,
|
||||
"The same samples data should be retrieved from the controller (2)."
|
||||
);
|
||||
isnot(
|
||||
rendering2,
|
||||
rendering3,
|
||||
"The rendering data should be different because other options were used (2)."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,124 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the memory flamegraphs get rerendered when toggling
|
||||
* `flatten-tree-recursion`.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_FLATTEN_RECURSION_PREF,
|
||||
UI_ENABLE_ALLOCATIONS_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const {
|
||||
EVENTS,
|
||||
PerformanceController,
|
||||
DetailsView,
|
||||
MemoryFlameGraphView,
|
||||
} = panel.panelWin;
|
||||
|
||||
const {
|
||||
FlameGraphUtils,
|
||||
} = require("devtools/client/shared/widgets/FlameGraph");
|
||||
const RecordingUtils = require("devtools/shared/performance/recording-utils");
|
||||
|
||||
// Enable memory to test
|
||||
Services.prefs.setBoolPref(UI_ENABLE_ALLOCATIONS_PREF, true);
|
||||
Services.prefs.setBoolPref(UI_FLATTEN_RECURSION_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
let rendered = once(
|
||||
MemoryFlameGraphView,
|
||||
EVENTS.UI_MEMORY_FLAMEGRAPH_RENDERED
|
||||
);
|
||||
await DetailsView.selectView("memory-flamegraph");
|
||||
await rendered;
|
||||
|
||||
const allocations1 = PerformanceController.getCurrentRecording().getAllocations();
|
||||
const thread1 = RecordingUtils.getProfileThreadFromAllocations(allocations1);
|
||||
const rendering1 = FlameGraphUtils._cache.get(thread1);
|
||||
|
||||
ok(allocations1, "The allocations were retrieved from the controller.");
|
||||
ok(thread1, "The allocations profile was synthesized by the utility funcs.");
|
||||
ok(rendering1, "The rendering data was cached.");
|
||||
|
||||
rendered = once(MemoryFlameGraphView, EVENTS.UI_MEMORY_FLAMEGRAPH_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_FLATTEN_RECURSION_PREF, false);
|
||||
await rendered;
|
||||
ok(
|
||||
true,
|
||||
"MemoryFlameGraphView rerendered when toggling flatten-tree-recursion."
|
||||
);
|
||||
|
||||
const allocations2 = PerformanceController.getCurrentRecording().getAllocations();
|
||||
const thread2 = RecordingUtils.getProfileThreadFromAllocations(allocations2);
|
||||
const rendering2 = FlameGraphUtils._cache.get(thread2);
|
||||
|
||||
is(
|
||||
allocations1,
|
||||
allocations2,
|
||||
"The same allocations data should be retrieved from the controller (1)."
|
||||
);
|
||||
is(
|
||||
thread1,
|
||||
thread2,
|
||||
"The same allocations profile should be retrieved from the utility funcs. (1)."
|
||||
);
|
||||
isnot(
|
||||
rendering1,
|
||||
rendering2,
|
||||
"The rendering data should be different because other options were used (1)."
|
||||
);
|
||||
|
||||
rendered = once(MemoryFlameGraphView, EVENTS.UI_MEMORY_FLAMEGRAPH_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_FLATTEN_RECURSION_PREF, true);
|
||||
await rendered;
|
||||
ok(
|
||||
true,
|
||||
"MemoryFlameGraphView rerendered when toggling back flatten-tree-recursion."
|
||||
);
|
||||
|
||||
const allocations3 = PerformanceController.getCurrentRecording().getAllocations();
|
||||
const thread3 = RecordingUtils.getProfileThreadFromAllocations(allocations3);
|
||||
const rendering3 = FlameGraphUtils._cache.get(thread3);
|
||||
|
||||
is(
|
||||
allocations2,
|
||||
allocations3,
|
||||
"The same allocations data should be retrieved from the controller (2)."
|
||||
);
|
||||
is(
|
||||
thread2,
|
||||
thread3,
|
||||
"The same allocations profile should be retrieved from the utility funcs. (2)."
|
||||
);
|
||||
isnot(
|
||||
rendering2,
|
||||
rendering3,
|
||||
"The rendering data should be different because other options were used (2)."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,53 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the js call tree views get rerendered when toggling `invert-call-tree`.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_INVERT_CALL_TREE_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
Services.prefs.setBoolPref(UI_INVERT_CALL_TREE_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
let rendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
|
||||
await DetailsView.selectView("js-calltree");
|
||||
await rendered;
|
||||
|
||||
rendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_INVERT_CALL_TREE_PREF, false);
|
||||
await rendered;
|
||||
ok(true, "JsCallTreeView rerendered when toggling invert-call-tree.");
|
||||
|
||||
rendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_INVERT_CALL_TREE_PREF, true);
|
||||
await rendered;
|
||||
ok(true, "JsCallTreeView rerendered when toggling back invert-call-tree.");
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,59 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the memory call tree views get rerendered when toggling `invert-call-tree`.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_ENABLE_ALLOCATIONS_PREF,
|
||||
UI_INVERT_CALL_TREE_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, DetailsView, MemoryCallTreeView } = panel.panelWin;
|
||||
|
||||
// Enable allocations to test.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_ALLOCATIONS_PREF, true);
|
||||
Services.prefs.setBoolPref(UI_INVERT_CALL_TREE_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
let rendered = once(MemoryCallTreeView, EVENTS.UI_MEMORY_CALL_TREE_RENDERED);
|
||||
await DetailsView.selectView("memory-calltree");
|
||||
await rendered;
|
||||
|
||||
rendered = once(MemoryCallTreeView, EVENTS.UI_MEMORY_CALL_TREE_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_INVERT_CALL_TREE_PREF, false);
|
||||
await rendered;
|
||||
ok(true, "MemoryCallTreeView rerendered when toggling invert-call-tree.");
|
||||
|
||||
rendered = once(MemoryCallTreeView, EVENTS.UI_MEMORY_CALL_TREE_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_INVERT_CALL_TREE_PREF, true);
|
||||
await rendered;
|
||||
ok(
|
||||
true,
|
||||
"MemoryCallTreeView rerendered when toggling back invert-call-tree."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,53 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the js flamegraphs views get rerendered when toggling `invert-flame-graph`.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_INVERT_FLAME_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, DetailsView, JsFlameGraphView } = panel.panelWin;
|
||||
|
||||
Services.prefs.setBoolPref(UI_INVERT_FLAME_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
let rendered = once(JsFlameGraphView, EVENTS.UI_JS_FLAMEGRAPH_RENDERED);
|
||||
await DetailsView.selectView("js-flamegraph");
|
||||
await rendered;
|
||||
|
||||
rendered = once(JsFlameGraphView, EVENTS.UI_JS_FLAMEGRAPH_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_INVERT_FLAME_PREF, false);
|
||||
await rendered;
|
||||
ok(true, "JsFlameGraphView rerendered when toggling invert-call-tree.");
|
||||
|
||||
rendered = once(JsFlameGraphView, EVENTS.UI_JS_FLAMEGRAPH_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_INVERT_FLAME_PREF, true);
|
||||
await rendered;
|
||||
ok(true, "JsFlameGraphView rerendered when toggling back invert-call-tree.");
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,63 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that the memory flamegraphs views get rerendered when toggling
|
||||
* `invert-flame-graph`.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
UI_ENABLE_ALLOCATIONS_PREF,
|
||||
UI_INVERT_FLAME_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
const {
|
||||
once,
|
||||
} = require("devtools/client/performance/test/helpers/event-utils");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
const { EVENTS, DetailsView, MemoryFlameGraphView } = panel.panelWin;
|
||||
|
||||
// Enable allocations to test.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_ALLOCATIONS_PREF, true);
|
||||
Services.prefs.setBoolPref(UI_INVERT_FLAME_PREF, true);
|
||||
|
||||
await startRecording(panel);
|
||||
await stopRecording(panel);
|
||||
|
||||
let rendered = once(
|
||||
MemoryFlameGraphView,
|
||||
EVENTS.UI_MEMORY_FLAMEGRAPH_RENDERED
|
||||
);
|
||||
await DetailsView.selectView("memory-flamegraph");
|
||||
await rendered;
|
||||
|
||||
rendered = once(MemoryFlameGraphView, EVENTS.UI_MEMORY_FLAMEGRAPH_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_INVERT_FLAME_PREF, false);
|
||||
await rendered;
|
||||
ok(true, "MemoryFlameGraphView rerendered when toggling invert-call-tree.");
|
||||
|
||||
rendered = once(MemoryFlameGraphView, EVENTS.UI_MEMORY_FLAMEGRAPH_RENDERED);
|
||||
Services.prefs.setBoolPref(UI_INVERT_FLAME_PREF, true);
|
||||
await rendered;
|
||||
ok(
|
||||
true,
|
||||
"MemoryFlameGraphView rerendered when toggling back invert-call-tree."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
@ -1,56 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that setting the `devtools.performance.memory.` prefs propagate to
|
||||
* the memory actor.
|
||||
*/
|
||||
|
||||
const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
|
||||
const {
|
||||
MEMORY_SAMPLE_PROB_PREF,
|
||||
MEMORY_MAX_LOG_LEN_PREF,
|
||||
UI_ENABLE_ALLOCATIONS_PREF,
|
||||
} = require("devtools/client/performance/test/helpers/prefs");
|
||||
const {
|
||||
initPerformanceInNewTab,
|
||||
teardownToolboxAndRemoveTab,
|
||||
} = require("devtools/client/performance/test/helpers/panel-utils");
|
||||
const {
|
||||
startRecording,
|
||||
stopRecording,
|
||||
} = require("devtools/client/performance/test/helpers/actions");
|
||||
|
||||
add_task(async function() {
|
||||
const { panel, toolbox } = await initPerformanceInNewTab({
|
||||
url: SIMPLE_URL,
|
||||
win: window,
|
||||
});
|
||||
|
||||
// Enable allocations to test.
|
||||
Services.prefs.setBoolPref(UI_ENABLE_ALLOCATIONS_PREF, true);
|
||||
Services.prefs.setCharPref(MEMORY_SAMPLE_PROB_PREF, "0.213");
|
||||
Services.prefs.setIntPref(MEMORY_MAX_LOG_LEN_PREF, 777777);
|
||||
|
||||
await startRecording(panel);
|
||||
const performanceFront = await toolbox.target.getFront("performance");
|
||||
const {
|
||||
probability,
|
||||
maxLogLength,
|
||||
} = await performanceFront.getConfiguration();
|
||||
await stopRecording(panel);
|
||||
|
||||
is(
|
||||
probability,
|
||||
0.213,
|
||||
"The allocations probability option is set on memory actor."
|
||||
);
|
||||
is(
|
||||
maxLogLength,
|
||||
777777,
|
||||
"The allocations max log length option is set on memory actor."
|
||||
);
|
||||
|
||||
await teardownToolboxAndRemoveTab(panel);
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user