tools: in performance-log-viewer.py, allow viewing source files ...

... in backtraces

In the performance-log viewer's backtrace viewer, show a document
icon next to stack frames with source-location information, whose
source file is found locally.  Clicking the icon opens the source
file in a text editor at the relevant line.

Two environment variables control this feature:

  - PERFORMANCE_LOG_VIEWER_PATH is a list of colon-separated
    directories in which to look for source files.  If this
    variable is undefined, the current directory is used.

  - PERFORMANCE_LOG_VIEWER_EDITOR is the command to use to launch
    the text editor, for editing a specific file at a specific
    line.  The special strings "{file}" and "{line}" are replaced
    with the filename and line-number, respectively.  If this
    variable is undefined, "xdg-open {file}" is used.

(cherry picked from commit 0f38709259)
This commit is contained in:
Ell
2018-09-30 04:51:19 -04:00
parent 788eb090b8
commit 33269cc8ff

View File

@ -21,7 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
Usage: performance-log-viewer.py < infile Usage: performance-log-viewer.py < infile
""" """
import builtins, sys, math, statistics, functools, enum, re import builtins, sys, os, math, statistics, functools, enum, re, subprocess
from collections import namedtuple from collections import namedtuple
from xml.etree import ElementTree from xml.etree import ElementTree
@ -72,6 +72,39 @@ def get_basename (path):
return match[1] if match else path return match[1] if match else path
search_path = list (filter (
bool,
os.environ.get ("PERFORMANCE_LOG_VIEWER_PATH", ".").split (":")
))
editor_command = os.environ.get ("PERFORMANCE_LOG_VIEWER_EDITOR",
"xdg-open {file}")
editor_command += " &"
def find_file (filename):
filename = re.sub ("[\\\\/]", GLib.DIR_SEPARATOR_S, filename)
if GLib.path_is_absolute (filename):
file = Gio.File.new_for_path (filename)
if file.query_exists ():
return file
for path in search_path:
rest = filename
while rest:
file = Gio.File.new_for_path (GLib.build_filenamev ((path, rest)))
if file.query_exists ():
return file
sep = rest.find (GLib.DIR_SEPARATOR_S)
rest = rest[sep + 1:] if sep >= 0 else ""
return None
VariableType = namedtuple ("VariableType", VariableType = namedtuple ("VariableType",
("parse", "format", "format_numeric")) ("parse", "format", "format_numeric"))
@ -1737,6 +1770,36 @@ class BacktraceViewer (Gtk.Box):
def __init__ (self): def __init__ (self):
Gtk.ListStore.__init__ (self, int, str, str, str, str, str, str) Gtk.ListStore.__init__ (self, int, str, str, str, str, str, str)
class CellRendererViewSource (Gtk.CellRendererPixbuf):
file = GObject.Property (type = Gio.File, default = None)
line = GObject.Property (type = int, default = 0)
def __init__ (self, *args, **kwargs):
Gtk.CellRendererPixbuf.__init__ (
self,
*args,
icon_name = "text-x-generic-symbolic",
mode = Gtk.CellRendererMode.ACTIVATABLE,
**kwargs)
self.connect ("notify::file",
lambda *args:
self.set_property ("visible", bool (self.file)))
def do_activate (self, event, widget, path, *args):
if self.file:
subprocess.call (
editor_command.format (
file = "\"%s\"" % self.file.get_path (),
line = self.line
),
shell = True
)
return True
return False
def __init__ (self, *args, **kwargs): def __init__ (self, *args, **kwargs):
Gtk.Box.__init__ (self, Gtk.Box.__init__ (self,
*args, *args,
@ -1893,9 +1956,33 @@ class BacktraceViewer (Gtk.Box):
col.pack_start (cell, False) col.pack_start (cell, False)
col.add_attribute (cell, "text", self.FrameStore.LINE) col.add_attribute (cell, "text", self.FrameStore.LINE)
def format_view_source_col (tree_col, cell, model, iter, cols):
filename = model[iter][cols[0]] or None
line = model[iter][cols[1]] or "0"
cell.set_property ("file", filename and find_file (filename))
cell.set_property ("line", int (line))
def format_view_source_tooltip (row):
filename = row[store.SOURCE]
if filename:
file = find_file (filename)
if file:
return file.get_path ()
return None
col = Gtk.TreeViewColumn () col = Gtk.TreeViewColumn ()
self.tooltip_columns[col] = format_view_source_tooltip
tree.append_column (col) tree.append_column (col)
cell = self.CellRendererViewSource (xalign = 0)
col.pack_start (cell, False)
col.set_cell_data_func (cell, format_view_source_col, (store.SOURCE,
store.LINE))
selection.connect ("change-complete", self.selection_change_complete) selection.connect ("change-complete", self.selection_change_complete)
@GObject.Property (type = bool, default = False) @GObject.Property (type = bool, default = False)
@ -2030,7 +2117,7 @@ class BacktraceViewer (Gtk.Box):
keyboard_mode) keyboard_mode)
if hit: if hit:
column = -1 column = None
if keyboard_mode: if keyboard_mode:
cursor_path, cursor_col = tree.get_cursor () cursor_path, cursor_col = tree.get_cursor ()
@ -2047,8 +2134,13 @@ class BacktraceViewer (Gtk.Box):
break break
if column >= 0: if column is not None:
value = None
if type (column) == int:
value = model[iter][column] value = model[iter][column]
else:
value = column (model[iter])
if value: if value:
tooltip.set_text (str (value)) tooltip.set_text (str (value))