diff --git a/devel-docs/performance-logs/performance-logs.md b/devel-docs/performance-logs/performance-logs.md index efcf433be6..6efff2897e 100644 --- a/devel-docs/performance-logs/performance-logs.md +++ b/devel-docs/performance-logs/performance-logs.md @@ -18,16 +18,17 @@ report performance-related issues. - [4.1.1. Selecting Samples](#411-selecting-samples) - [4.2. Information Area](#42-information-area) - [4.2.1. Information Page](#421-information-page) - - [4.2.2. Variables Page](#422-variables-page) - - [4.2.3. Backtrace Page](#423-backtrace-page) - - [4.2.3.1. Threads Pane](#4231-threads-pane) - - [4.2.3.2. Stack Pane](#4232-stack-pane) - - [4.2.4. Profile Page](#424-profile-page) - - [4.2.4.1. Root Column](#4241-root-column) - - [4.2.4.1.1. Thread Filter](#42411-thread-filter) - - [4.2.4.1.2. Call-Graph Direction](#42412-call-graph-direction) - - [4.2.4.2. Function Columns](#4242-function-columns) - - [4.2.4.3. Source Columns](#4243-source-columns) + - [4.2.2. Markers Page](#422-markers-page) + - [4.2.3. Variables Page](#423-variables-page) + - [4.2.4. Backtrace Page](#424-backtrace-page) + - [4.2.4.1. Threads Pane](#4241-threads-pane) + - [4.2.4.2. Stack Pane](#4242-stack-pane) + - [4.2.5. Profile Page](#425-profile-page) + - [4.2.5.1. Root Column](#4251-root-column) + - [4.2.5.1.1. Thread Filter](#42511-thread-filter) + - [4.2.5.1.2. Call-Graph Direction](#42512-call-graph-direction) + - [4.2.5.2. Function Columns](#4252-function-columns) + - [4.2.5.3. Source Columns](#4253-source-columns) - [4.3. Selection Modifiers](#43-selection-modifiers) - [4.3.1. Searching Samples](#431-searching-samples) - [4.4. History Navigation](#44-history-navigation) @@ -249,7 +250,17 @@ associated with any sample, including: The key/value lists are searchable by key name. -#### 4.2.2. Variables Page +#### 4.2.4. Markers Page + +The *markers page* lists the event markers contained in the log, displaying +their number, relative time, and description. +It is only present in logs containing event markers. + +If the current selection contains samples corresponding to any markers, the +markers are selected in the markers-page list. Conversely, if any markers are +selected in the markers-page list, the corresponding samples are selected. + +#### 4.2.3. Variables Page The *variables page* shows instrumentation-variable statistics for the current selection. @@ -267,13 +278,13 @@ standard deviation. The variable list is searchable by variable name, and its tooltip shows the variable descriptions. -#### 4.2.3. Backtrace Page +#### 4.2.4. Backtrace Page The *backtrace page* shows the program backtrace at the current sample. It is only available when a single sample is selected, in logs containing backtraces. -##### 4.2.3.1. Threads Pane +##### 4.2.4.1. Threads Pane The *threads pane*, on the left side of the page, lists all active threads at the time of the sample, displaying the following information: @@ -306,7 +317,7 @@ The thread list is searchable by thread name. Double-clicking on a thread selects all samples at which the thread is in the running state. -##### 4.2.3.2. Stack Pane +##### 4.2.4.2. Stack Pane The *stack pane*, on the right side of the page, shows the selected thread's call stack at the time of the sample, displaying the following information: @@ -350,7 +361,7 @@ The frame list is searchable by function name. Double-clicking on a frame selects all samples at which the corresponding function is present in the backtrace. -#### 4.2.4. Profile Page +#### 4.2.5. Profile Page The *profile page* shows a fully context-sensitive *call graph*, annotated with frequency information, for the current selection. @@ -368,7 +379,7 @@ Each non-root column lists the direct *descendants* (*callers* or *callees*) of a given function; selecting a descendant opens a new column to the right of the current column, showing the descendants of the selected function, and so on. -##### 4.2.4.1. Root Column +##### 4.2.5.1. Root Column The *root column* of the call graph shows a list of all functions included in the graph. @@ -398,14 +409,14 @@ Pressing *Escape* while the list has focus deselects the current item. The root-column header buttons allow controlling the structure of the call graph: -###### 4.2.4.1.1. Thread Filter +###### 4.2.5.1.1. Thread Filter The *Threads* button opens the *thread filter*, allowing control over which threads, and which states of each thread, are included in the graph. The thread filter lists all threads included in the current selection. Each thread is identified by ID and name, as described in -[section *4.2.3.1*](#4231-threads-pane). +[section *4.2.4.1*](#4241-threads-pane). Next to each thread is a row of toggles, corresponding to the different thread states; only call stacks during which the thread was in one of the active states are included in the graph. @@ -413,7 +424,7 @@ Clicking on a thread-state column title toggles the entire column. The thread list can be searched by thread name. -###### 4.2.4.1.2. Call-Graph Direction +###### 4.2.5.1.2. Call-Graph Direction By default, the graph direction is *caller β†’ callee*β€”the direct descendants of each function are its callees. @@ -421,7 +432,7 @@ The *Call-Graph Direction* button allows toggling the graph between the *caller β†’ callee* direction, and the reverse *callee β†’ caller* direction, in which the direct descendants of each function are its callers. -##### 4.2.4.2. Function Columns +##### 4.2.5.2. Function Columns When a function from the root column is selected, a new *function column* opens to the right of the root column, listing the direct descendants of the @@ -472,7 +483,7 @@ corresponding to the current column, that is, all the samples whose call stacks contribute to column. The button's tooltip shows a textual description of the samples. -##### 4.2.4.3. Source Columns +##### 4.2.5.3. Source Columns When the *[Self]* item of a function column is selected, if the log contains source-location information for the function, and the corresponding source file @@ -535,7 +546,7 @@ A number of sample-dependent variables and functions are provided: thread name. The optional `state` argument, if not `None`, may specify a thread state - (see [section *4.2.3.1*](#4231-threads-pane)). + (see [section *4.2.4.1*](#4241-threads-pane)). Only samples at which the thread is in the given state are matched. The argument may be a regular expression, which should fully match the thread state. diff --git a/tools/performance-log-viewer.py b/tools/performance-log-viewer.py index a0f78f2840..50cd7e8a7a 100755 --- a/tools/performance-log-viewer.py +++ b/tools/performance-log-viewer.py @@ -288,15 +288,16 @@ Frame = namedtuple ("Frame", ("id", "address", "info")) Sample = namedtuple ("Sample", ("t", "vars", "markers", "backtrace")) Marker = namedtuple ("Marker", ("id", "t", "description")) -samples = [] -markers = [] +samples = [] +markers = [] +last_marker = 0 for element in log.find ("samples"): if element.tag == "sample": sample = Sample ( t = int (element.get ("t")), vars = {}, - markers = markers, + markers = markers[last_marker:], backtrace = [] ) @@ -347,7 +348,7 @@ for element in log.find ("samples"): samples.append (sample) - markers = [] + last_marker = len (markers) elif element.tag == "marker": marker = Marker ( id = int (element.get ("id")), @@ -357,10 +358,8 @@ for element in log.find ("samples"): markers.append (marker) -if samples and markers: - samples[-1].markers += markers - -markers = None +if samples: + samples[-1].markers.extend (markers[last_marker:]) DELTA_SAME = __builtins__.object () @@ -1675,6 +1674,128 @@ class InformationViewer (Gtk.ScrolledWindow): for element in info: add_element (element) +class MarkersViewer (Gtk.ScrolledWindow): + class Store (Gtk.ListStore): + ID = 0 + TIME = 1 + DESC = 2 + + def __init__ (self): + Gtk.ListStore.__init__ (self, int, int, str) + + for marker in markers: + self.append ((marker.id, marker.t, marker.description)) + + def __init__ (self, *args, **kwargs): + Gtk.Box.__init__ (self, + *args, + hscrollbar_policy = Gtk.PolicyType.AUTOMATIC, + vscrollbar_policy = Gtk.PolicyType.AUTOMATIC, + **kwargs) + + self.needs_update = True + + store = self.Store () + self.store = store + + tree = Gtk.TreeView (model = store) + self.tree = tree + self.add (tree) + tree.show () + + tree.get_selection ().set_mode (Gtk.SelectionMode.MULTIPLE) + + self.tree_selection_changed_handler = tree.get_selection ().connect ( + "changed", self.tree_selection_changed + ) + + col = Gtk.TreeViewColumn (title = "#") + tree.append_column (col) + col.set_resizable (True) + + cell = Gtk.CellRendererText (xalign = 1) + col.pack_start (cell, False) + col.add_attribute (cell, "text", store.ID) + + def format_time_col (tree_col, cell, model, iter, col): + time = model[iter][col] + + cell.set_property ("text", format_duration (time / 1000000)) + + col = Gtk.TreeViewColumn (title = "Time") + tree.append_column (col) + col.set_resizable (True) + col.set_alignment (0.5) + + cell = Gtk.CellRendererText (xalign = 1) + col.pack_start (cell, False) + col.set_cell_data_func (cell, format_time_col, store.TIME) + + col = Gtk.TreeViewColumn (title = "Description") + tree.append_column (col) + col.set_resizable (True) + col.set_alignment (0.5) + + cell = Gtk.CellRendererText () + col.pack_start (cell, False) + col.add_attribute (cell, "text", store.DESC) + + col = Gtk.TreeViewColumn () + tree.append_column (col) + + selection.connect ("change-complete", self.selection_change_complete) + + def update (self): + markers = set () + + if not self.needs_update: + return + + self.needs_update = False + + for i in selection.selection: + markers.update (marker.id for marker in samples[i].markers) + + tree_sel = self.tree.get_selection () + + GObject.signal_handler_block (tree_sel, + self.tree_selection_changed_handler) + + tree_sel.unselect_all () + + for row in self.store: + if row[self.store.ID] in markers: + tree_sel.select_iter (row.iter) + + GObject.signal_handler_unblock (tree_sel, + self.tree_selection_changed_handler) + + def do_map (self): + self.update () + + Gtk.ScrolledWindow.do_map (self) + + def selection_change_complete (self, selection): + self.needs_update = True + + if self.get_mapped (): + self.update () + + def tree_selection_changed (self, tree_sel): + sel = set () + + for row in self.store: + if tree_sel.iter_is_selected (row.iter): + id = row[self.store.ID] + + for i in range (len (samples)): + if any (marker.id == id for marker in samples[i].markers): + sel.add (i) + + selection.select (sel) + + selection.change_complete () + class VariablesViewer (Gtk.ScrolledWindow): class Store (Gtk.ListStore): NAME = 0 @@ -3466,6 +3587,11 @@ class LogViewer (Gtk.Window): stack.add_titled (info_viewer, "information", "Information") info_viewer.show () + if markers: + markers_viewer = MarkersViewer () + stack.add_titled (markers_viewer, "markers", "Markers") + markers_viewer.show () + vars_viewer = VariablesViewer () stack.add_titled (vars_viewer, "variables", "Variables") vars_viewer.show ()