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 |