Stefan Schuermans commited on 2020-05-23 20:45:22
Showing 5 changed files, with 261 additions and 0 deletions.
| ... | ... |
@@ -0,0 +1,27 @@ |
| 1 |
+add_custom_command( |
|
| 2 |
+ OUTPUT |
|
| 3 |
+ ${CMAKE_CURRENT_BINARY_DIR}/upt-gui.glade
|
|
| 4 |
+ DEPENDS |
|
| 5 |
+ ${CMAKE_CURRENT_SOURCE_DIR}/upt-gui.glade
|
|
| 6 |
+ COMMAND |
|
| 7 |
+ cp -a ${CMAKE_CURRENT_SOURCE_DIR}/upt-gui.glade
|
|
| 8 |
+ ${CMAKE_CURRENT_BINARY_DIR}/upt-gui.glade
|
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+add_custom_command( |
|
| 12 |
+ OUTPUT |
|
| 13 |
+ ${CMAKE_CURRENT_BINARY_DIR}/upt-gui.py
|
|
| 14 |
+ DEPENDS |
|
| 15 |
+ ${CMAKE_CURRENT_SOURCE_DIR}/upt-gui.py
|
|
| 16 |
+ COMMAND |
|
| 17 |
+ cp -a ${CMAKE_CURRENT_SOURCE_DIR}/upt-gui.py
|
|
| 18 |
+ ${CMAKE_CURRENT_BINARY_DIR}/upt-gui.py
|
|
| 19 |
+) |
|
| 20 |
+ |
|
| 21 |
+add_custom_target( |
|
| 22 |
+ upt-gui |
|
| 23 |
+ ALL |
|
| 24 |
+ DEPENDS |
|
| 25 |
+ ${CMAKE_CURRENT_BINARY_DIR}/upt-gui.glade
|
|
| 26 |
+ ${CMAKE_CURRENT_BINARY_DIR}/upt-gui.py
|
|
| 27 |
+) |
| ... | ... |
@@ -0,0 +1,126 @@ |
| 1 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 2 |
+<!-- Generated with glade 3.22.1 --> |
|
| 3 |
+<interface> |
|
| 4 |
+ <requires lib="gtk+" version="3.20"/> |
|
| 5 |
+ <object class="GtkTreeStore" id="ProcessesTree"> |
|
| 6 |
+ <columns> |
|
| 7 |
+ <!-- column-name proc_id --> |
|
| 8 |
+ <column type="gint"/> |
|
| 9 |
+ <!-- column-name begin_timestamp --> |
|
| 10 |
+ <column type="gchararray"/> |
|
| 11 |
+ <!-- column-name end_timestamp --> |
|
| 12 |
+ <column type="gchararray"/> |
|
| 13 |
+ <!-- column-name cmdline --> |
|
| 14 |
+ <column type="gchararray"/> |
|
| 15 |
+ </columns> |
|
| 16 |
+ </object> |
|
| 17 |
+ <object class="GtkWindow" id="Application"> |
|
| 18 |
+ <property name="visible">True</property> |
|
| 19 |
+ <property name="can_focus">False</property> |
|
| 20 |
+ <signal name="destroy" handler="onDestroy" swapped="no"/> |
|
| 21 |
+ <child> |
|
| 22 |
+ <placeholder/> |
|
| 23 |
+ </child> |
|
| 24 |
+ <child> |
|
| 25 |
+ <object class="GtkBox" id="TopVBox"> |
|
| 26 |
+ <property name="visible">True</property> |
|
| 27 |
+ <property name="can_focus">False</property> |
|
| 28 |
+ <property name="orientation">vertical</property> |
|
| 29 |
+ <child> |
|
| 30 |
+ <object class="GtkBox" id="TopHBox"> |
|
| 31 |
+ <property name="visible">True</property> |
|
| 32 |
+ <property name="can_focus">False</property> |
|
| 33 |
+ <child> |
|
| 34 |
+ <object class="GtkScrolledWindow" id="ProcessesScoll"> |
|
| 35 |
+ <property name="width_request">256</property> |
|
| 36 |
+ <property name="height_request">256</property> |
|
| 37 |
+ <property name="visible">True</property> |
|
| 38 |
+ <property name="can_focus">True</property> |
|
| 39 |
+ <property name="shadow_type">in</property> |
|
| 40 |
+ <child> |
|
| 41 |
+ <object class="GtkTreeView" id="ProcessesView"> |
|
| 42 |
+ <property name="visible">True</property> |
|
| 43 |
+ <property name="can_focus">True</property> |
|
| 44 |
+ <property name="model">ProcessesTree</property> |
|
| 45 |
+ <property name="reorderable">True</property> |
|
| 46 |
+ <property name="rules_hint">True</property> |
|
| 47 |
+ <property name="search_column">0</property> |
|
| 48 |
+ <property name="fixed_height_mode">True</property> |
|
| 49 |
+ <property name="enable_tree_lines">True</property> |
|
| 50 |
+ <child internal-child="selection"> |
|
| 51 |
+ <object class="GtkTreeSelection"/> |
|
| 52 |
+ </child> |
|
| 53 |
+ <child> |
|
| 54 |
+ <object class="GtkTreeViewColumn" id="Begin"> |
|
| 55 |
+ <property name="resizable">True</property> |
|
| 56 |
+ <property name="sizing">fixed</property> |
|
| 57 |
+ <property name="title" translatable="yes">Begin</property> |
|
| 58 |
+ <property name="clickable">True</property> |
|
| 59 |
+ <property name="reorderable">True</property> |
|
| 60 |
+ <property name="sort_indicator">True</property> |
|
| 61 |
+ <property name="sort_column_id">0</property> |
|
| 62 |
+ <child> |
|
| 63 |
+ <object class="GtkCellRendererText" id="BeginText"/> |
|
| 64 |
+ <attributes> |
|
| 65 |
+ <attribute name="text">1</attribute> |
|
| 66 |
+ </attributes> |
|
| 67 |
+ </child> |
|
| 68 |
+ </object> |
|
| 69 |
+ </child> |
|
| 70 |
+ <child> |
|
| 71 |
+ <object class="GtkTreeViewColumn" id="End"> |
|
| 72 |
+ <property name="resizable">True</property> |
|
| 73 |
+ <property name="sizing">fixed</property> |
|
| 74 |
+ <property name="title" translatable="yes">End</property> |
|
| 75 |
+ <property name="clickable">True</property> |
|
| 76 |
+ <property name="reorderable">True</property> |
|
| 77 |
+ <property name="sort_indicator">True</property> |
|
| 78 |
+ <property name="sort_column_id">1</property> |
|
| 79 |
+ <child> |
|
| 80 |
+ <object class="GtkCellRendererText" id="EndText"/> |
|
| 81 |
+ <attributes> |
|
| 82 |
+ <attribute name="text">2</attribute> |
|
| 83 |
+ </attributes> |
|
| 84 |
+ </child> |
|
| 85 |
+ </object> |
|
| 86 |
+ </child> |
|
| 87 |
+ <child> |
|
| 88 |
+ <object class="GtkTreeViewColumn" id="Command"> |
|
| 89 |
+ <property name="resizable">True</property> |
|
| 90 |
+ <property name="sizing">fixed</property> |
|
| 91 |
+ <property name="title" translatable="yes">Command</property> |
|
| 92 |
+ <property name="clickable">True</property> |
|
| 93 |
+ <property name="reorderable">True</property> |
|
| 94 |
+ <property name="sort_indicator">True</property> |
|
| 95 |
+ <property name="sort_column_id">2</property> |
|
| 96 |
+ <child> |
|
| 97 |
+ <object class="GtkCellRendererText" id="CommandText"/> |
|
| 98 |
+ <attributes> |
|
| 99 |
+ <attribute name="text">3</attribute> |
|
| 100 |
+ </attributes> |
|
| 101 |
+ </child> |
|
| 102 |
+ </object> |
|
| 103 |
+ </child> |
|
| 104 |
+ </object> |
|
| 105 |
+ </child> |
|
| 106 |
+ </object> |
|
| 107 |
+ <packing> |
|
| 108 |
+ <property name="expand">True</property> |
|
| 109 |
+ <property name="fill">True</property> |
|
| 110 |
+ <property name="position">0</property> |
|
| 111 |
+ </packing> |
|
| 112 |
+ </child> |
|
| 113 |
+ <child> |
|
| 114 |
+ <placeholder/> |
|
| 115 |
+ </child> |
|
| 116 |
+ </object> |
|
| 117 |
+ <packing> |
|
| 118 |
+ <property name="expand">True</property> |
|
| 119 |
+ <property name="fill">True</property> |
|
| 120 |
+ <property name="position">0</property> |
|
| 121 |
+ </packing> |
|
| 122 |
+ </child> |
|
| 123 |
+ </object> |
|
| 124 |
+ </child> |
|
| 125 |
+ </object> |
|
| 126 |
+</interface> |
| ... | ... |
@@ -0,0 +1,105 @@ |
| 1 |
+#! /usr/bin/env python3 |
|
| 2 |
+ |
|
| 3 |
+import cairo |
|
| 4 |
+import datetime |
|
| 5 |
+import gi |
|
| 6 |
+import os |
|
| 7 |
+import shlex |
|
| 8 |
+import sys |
|
| 9 |
+import time |
|
| 10 |
+gi.require_version('Gdk', '3.0')
|
|
| 11 |
+gi.require_version('Gtk', '3.0')
|
|
| 12 |
+from gi.repository import Gdk |
|
| 13 |
+from gi.repository import GLib |
|
| 14 |
+from gi.repository import Gtk |
|
| 15 |
+ |
|
| 16 |
+import uproctrace.processes |
|
| 17 |
+ |
|
| 18 |
+ |
|
| 19 |
+def cmdline2str(cmdline: list) -> str: |
|
| 20 |
+ """ |
|
| 21 |
+ Convert command line to string. |
|
| 22 |
+ """ |
|
| 23 |
+ if cmdline is None: |
|
| 24 |
+ return "???" |
|
| 25 |
+ return ' '.join([shlex.quote(s) for s in cmdline]) |
|
| 26 |
+ |
|
| 27 |
+def timestamp2str(timestamp: float) -> str: |
|
| 28 |
+ """ |
|
| 29 |
+ Convert a timestamp to a human-reable time string." |
|
| 30 |
+ """ |
|
| 31 |
+ if timestamp is None: |
|
| 32 |
+ return "???" |
|
| 33 |
+ sec = int(timestamp) |
|
| 34 |
+ nsec = int((timestamp - sec) * 1e9) |
|
| 35 |
+ time_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(sec))
|
|
| 36 |
+ return time_str + f'.{nsec:09d}'
|
|
| 37 |
+ |
|
| 38 |
+ |
|
| 39 |
+class UptGui: |
|
| 40 |
+ def __init__(self, proto_filename): |
|
| 41 |
+ """ |
|
| 42 |
+ Construct the GUI. |
|
| 43 |
+ """ |
|
| 44 |
+ self.builder = Gtk.Builder() |
|
| 45 |
+ script_dir = os.path.dirname(os.path.abspath(__file__)) |
|
| 46 |
+ self.builder.add_from_file(os.path.join(script_dir, 'upt-gui.glade')) |
|
| 47 |
+ self.widProcessesTree = self.builder.get_object('ProcessesTree')
|
|
| 48 |
+ self.widProcessesView = self.builder.get_object('ProcessesView')
|
|
| 49 |
+ handlers = {'onDestroy': self.onDestroy}
|
|
| 50 |
+ self.builder.connect_signals(handlers) |
|
| 51 |
+ # open trace file |
|
| 52 |
+ self.openTrace(proto_filename) |
|
| 53 |
+ |
|
| 54 |
+ def onDestroy(self, widget): |
|
| 55 |
+ """ |
|
| 56 |
+ Window will be destroyed. |
|
| 57 |
+ """ |
|
| 58 |
+ Gtk.main_quit() |
|
| 59 |
+ |
|
| 60 |
+ def openTrace(self, proto_filename: str): |
|
| 61 |
+ """ |
|
| 62 |
+ Open a trace file. |
|
| 63 |
+ """ |
|
| 64 |
+ # forget old data |
|
| 65 |
+ self.widProcessesTree.clear() |
|
| 66 |
+ # lead new data |
|
| 67 |
+ with open(proto_filename, 'rb') as proto_file: |
|
| 68 |
+ self._processes = uproctrace.processes.Processes(proto_file) |
|
| 69 |
+ # add processes to processes tree store |
|
| 70 |
+ to_be_output = [(self._processes.toplevel, None)] |
|
| 71 |
+ while to_be_output: |
|
| 72 |
+ procs, parent_iter = to_be_output[-1] |
|
| 73 |
+ if not procs: |
|
| 74 |
+ del to_be_output[-1] |
|
| 75 |
+ continue |
|
| 76 |
+ proc = procs[0] |
|
| 77 |
+ del procs[0] |
|
| 78 |
+ proc_iter = self.widProcessesTree.append(parent_iter) |
|
| 79 |
+ self.widProcessesTree.set_value(proc_iter, 0, proc.proc_id) |
|
| 80 |
+ self.widProcessesTree.set_value(proc_iter, 1, timestamp2str(proc.begin_timestamp)) |
|
| 81 |
+ self.widProcessesTree.set_value(proc_iter, 2, timestamp2str(proc.end_timestamp)) |
|
| 82 |
+ self.widProcessesTree.set_value(proc_iter, 3, cmdline2str(proc.cmdline)) |
|
| 83 |
+ to_be_output.append((proc.children, proc_iter)) |
|
| 84 |
+ # show all processes |
|
| 85 |
+ self.widProcessesView.expand_all() |
|
| 86 |
+ |
|
| 87 |
+ |
|
| 88 |
+def main(argv): |
|
| 89 |
+ """ |
|
| 90 |
+ Main program. |
|
| 91 |
+ """ |
|
| 92 |
+ if len(argv) < 2: |
|
| 93 |
+ print('usage: ' + argv[0] + ' <trace.proto>', file=sys.stderr)
|
|
| 94 |
+ return 2 |
|
| 95 |
+ proto_filename = argv[1] |
|
| 96 |
+ app = UptGui(proto_filename) |
|
| 97 |
+ try: |
|
| 98 |
+ Gtk.main() |
|
| 99 |
+ except KeyboardInterrupt: |
|
| 100 |
+ pass |
|
| 101 |
+ return 0 |
|
| 102 |
+ |
|
| 103 |
+ |
|
| 104 |
+if __name__ == '__main__': |
|
| 105 |
+ sys.exit(main(sys.argv)) |
|
| 0 | 106 |