upt-gui -> upt-tool gui
Stefan Schuermans

Stefan Schuermans commited on 2020-05-24 13:56:01
Showing 9 changed files, with 131 additions and 121 deletions.

... ...
@@ -24,7 +24,6 @@ add_custom_target(
24 24
 add_subdirectory(libuptev)
25 25
 add_subdirectory(libuptpl)
26 26
 add_subdirectory(python3)
27
-add_subdirectory(upt-gui)
28 27
 add_subdirectory(upt-tool)
29 28
 add_subdirectory(upt-trace)
30 29
 add_subdirectory(tests)
... ...
@@ -83,7 +83,7 @@ upt-tool mytrace.proto dump
83 83
 
84 84
 To explore a trace in the graphical user interface, run:
85 85
 ```
86
-<build dir>/upt-gui/upt-gui.py mytrace.proto
86
+upt-tool mytrace.proto gui
87 87
 ```
88 88
 
89 89
 ## Example: Trace Build Process
... ...
@@ -124,5 +124,5 @@ upt-tool mytrace.proto dump
124 124
 To explore the trace in the graphical user interface, run:
125 125
 
126 126
 ```
127
-<build dir>/upt-gui/upt-gui.py mytrace.proto
127
+upt-tool mytrace.proto gui
128 128
 ```
... ...
@@ -9,6 +9,21 @@ add_custom_command(
9 9
          uproctrace.proto
10 10
 )
11 11
 
12
+add_custom_command(
13
+  OUTPUT
14
+  ${CMAKE_CURRENT_BINARY_DIR}/gui_glade.py
15
+  DEPENDS
16
+  ${CMAKE_CURRENT_SOURCE_DIR}/gui.glade
17
+  ${CMAKE_CURRENT_SOURCE_DIR}/gui_glade_header.py
18
+  ${CMAKE_CURRENT_SOURCE_DIR}/gui_glade_footer.py
19
+  COMMAND
20
+  cat
21
+  ${CMAKE_CURRENT_SOURCE_DIR}/gui_glade_header.py
22
+  ${CMAKE_CURRENT_SOURCE_DIR}/gui.glade
23
+  ${CMAKE_CURRENT_SOURCE_DIR}/gui_glade_footer.py
24
+  > ${CMAKE_CURRENT_BINARY_DIR}/gui_glade.py
25
+)
26
+
12 27
 function(pyfile NAME)
13 28
   add_custom_command(
14 29
     OUTPUT
... ...
@@ -25,6 +40,7 @@ endfunction(pyfile)
25 40
 
26 41
 pyfile(__init__)
27 42
 pyfile(dump)
43
+pyfile(gui)
28 44
 pyfile(parse)
29 45
 pyfile(processes)
30 46
 pyfile(tool)
... ...
@@ -34,5 +50,6 @@ add_custom_target(
34 50
   ALL
35 51
   DEPENDS
36 52
   ${CMAKE_CURRENT_BINARY_DIR}/uproctrace_pb2.py
53
+  ${CMAKE_CURRENT_BINARY_DIR}/gui_glade.py
37 54
   ${PYFILES}
38 55
 )
... ...
@@ -1,15 +1,19 @@
1
-#! /usr/bin/env python3
1
+"""
2
+Graphical user interface of UProcTrace.
3
+"""
2 4
 
3
-import gi
4
-import os
5 5
 import shlex
6
-import sys
7 6
 import time
8
-gi.require_version('Gtk', '3.0')
9
-from gi.repository import Gtk
10 7
 
8
+import uproctrace.gui_glade
11 9
 import uproctrace.processes
12 10
 
11
+# pylint: disable=C0411
12
+import gi
13
+gi.require_version('Gtk', '3.0')
14
+# pylint: disable=C0413
15
+from gi.repository import Gtk
16
+
13 17
 
14 18
 def cmdline2str(cmdline: list) -> str:
15 19
     """
... ...
@@ -27,47 +31,47 @@ def duration2str(duration: float) -> str:
27 31
     if duration is None:
28 32
         return '???'
29 33
     # split into day, hours, minutes, seconds
30
-    s = int(duration)
31
-    m = s // 60
32
-    s = s % 60
33
-    h = m // 60
34
-    m = m % 60
35
-    d = h // 24
36
-    h = h % 24
34
+    dur_s = int(duration)
35
+    dur_m = dur_s // 60
36
+    dur_s = dur_s % 60
37
+    dur_h = dur_m // 60
38
+    dur_m = dur_m % 60
39
+    dur_d = dur_h // 24
40
+    dur_h = dur_h % 24
37 41
     # split into ms, us, ns
38
-    ns = int((duration - s) * 1e9)
39
-    us = ns // 1000
40
-    ns = ns % 1000
41
-    ms = us // 1000
42
-    us = us % 1000
42
+    dur_ns = int((duration - dur_s) * 1e9)
43
+    dur_us = dur_ns // 1000
44
+    dur_ns = dur_ns % 1000
45
+    dur_ms = dur_us // 1000
46
+    dur_us = dur_us % 1000
43 47
     # assemble text
44 48
     txt = ''
45
-    if d > 0:
46
-        txt += f'{d:d} d '
47
-    if h > 0 or txt:
48
-        txt += f'{h:d} h '
49
-    if m > 0 or txt:
50
-        txt += f'{m:d} m '
51
-    if s > 0 or txt:
52
-        txt += f'{s:d} s '
53
-    if ms > 0 or txt:
54
-        txt += f'{ms:d} ms '
55
-    if us > 0 or txt:
56
-        txt += f'{us:d} us '
57
-    txt += f'{ns:d} ns '
49
+    if dur_d > 0:
50
+        txt += f'{dur_d:d} d '
51
+    if dur_h > 0 or txt:
52
+        txt += f'{dur_h:d} h '
53
+    if dur_m > 0 or txt:
54
+        txt += f'{dur_m:d} m '
55
+    if dur_s > 0 or txt:
56
+        txt += f'{dur_s:d} s '
57
+    if dur_ms > 0 or txt:
58
+        txt += f'{dur_ms:d} ms '
59
+    if dur_us > 0 or txt:
60
+        txt += f'{dur_us:d} us '
61
+    txt += f'{dur_ns:d} ns '
58 62
     txt += f'({duration:f} s)'
59 63
     return txt
60 64
 
61 65
 
62
-def kb2str(kb: int) -> str:
66
+def kb2str(size_kb: int) -> str:
63 67
     """
64 68
     Convert size in KiB to string.
65 69
     """
66
-    if kb is None:
70
+    if size_kb is None:
67 71
         return '???'
68 72
     # split into GiB, MiB, KiB
69
-    mib = kb // 1024
70
-    kib = kb % 1024
73
+    mib = size_kb // 1024
74
+    kib = size_kb % 1024
71 75
     gib = mib // 1024
72 76
     mib = mib % 1024
73 77
     # assemble text
... ...
@@ -77,7 +81,7 @@ def kb2str(kb: int) -> str:
77 81
     if mib > 0 or txt:
78 82
         txt += f'{mib:d} MiB '
79 83
     txt += f'{kib:d} KiB '
80
-    txt += f'({kb:d} KiB)'
84
+    txt += f'({size_kb:d} KiB)'
81 85
     return txt
82 86
 
83 87
 
... ...
@@ -94,6 +98,9 @@ def timestamp2str(timestamp: float) -> str:
94 98
 
95 99
 
96 100
 class UptGui:
101
+    """
102
+    Graphical user interface of UProcTrace.
103
+    """
97 104
 
98 105
     DETAIL_PROC_ID = 0
99 106
     DETAIL_KEY = 1
... ...
@@ -114,12 +121,11 @@ class UptGui:
114 121
         Construct the GUI.
115 122
         """
116 123
         self.builder = Gtk.Builder()
117
-        script_dir = os.path.dirname(os.path.abspath(__file__))
118
-        self.builder.add_from_file(os.path.join(script_dir, 'upt-gui.glade'))
119
-        self.widDetailsTree = self.builder.get_object('DetailsTree')
120
-        self.widDetailsView = self.builder.get_object('DetailsView')
121
-        self.widProcessesTree = self.builder.get_object('ProcessesTree')
122
-        self.widProcessesView = self.builder.get_object('ProcessesView')
124
+        self.builder.add_from_string(uproctrace.gui_glade.DATA)
125
+        self.wid_details_tree = self.builder.get_object('DetailsTree')
126
+        self.wid_details_view = self.builder.get_object('DetailsView')
127
+        self.wid_processes_tree = self.builder.get_object('ProcessesTree')
128
+        self.wid_processes_view = self.builder.get_object('ProcessesView')
123 129
         handlers = {
124 130
             'onDestroy': self.onDestroy,
125 131
             'onDetailsRowActivated': self.onDetailsRowActivated,
... ...
@@ -129,24 +135,24 @@ class UptGui:
129 135
         # open trace file
130 136
         self.openTrace(proto_filename)
131 137
 
132
-    def onDestroy(self, widget):
138
+    def onDestroy(self, _widget):
133 139
         """
134 140
         Window will be destroyed.
135 141
         """
136 142
         Gtk.main_quit()
137 143
 
138
-    def onDetailsRowActivated(self, widget, row, col):
144
+    def onDetailsRowActivated(self, _widget, _row, _col):
139 145
         """
140 146
         Row in details view has been activated.
141 147
         """
142 148
         # get proc_id of selected row (if any)
143
-        detail_sel = self.widDetailsView.get_selection()
149
+        detail_sel = self.wid_details_view.get_selection()
144 150
         if detail_sel is None:
145 151
             return
146 152
         detail_iter = detail_sel.get_selected()[1]
147 153
         if detail_iter is None:
148 154
             return
149
-        proc_id = self.widDetailsTree.get_value(detail_iter,
155
+        proc_id = self.wid_details_tree.get_value(detail_iter,
150 156
                                                   self.DETAIL_PROC_ID)
151 157
         # do nothing for rows without valid proc_id
152 158
         if proc_id < 0:
... ...
@@ -156,12 +162,12 @@ class UptGui:
156 162
         # show details of selected process
157 163
         self.showDetails(proc_id)
158 164
 
159
-    def onProcessesCursorChanged(self, widget):
165
+    def onProcessesCursorChanged(self, _widget):
160 166
         """
161 167
         Cursor changed in processes tree view.
162 168
         """
163 169
         # get proc_id of selected process
164
-        proc_sel = self.widProcessesView.get_selection()
170
+        proc_sel = self.wid_processes_view.get_selection()
165 171
         if proc_sel is None:
166 172
             self.showDetails(None)
167 173
             return
... ...
@@ -169,7 +175,8 @@ class UptGui:
169 175
         if proc_iter is None:
170 176
             self.showDetails(None)
171 177
             return
172
-        proc_id = self.widProcessesTree.get_value(proc_iter, self.PROC_PROC_ID)
178
+        proc_id = self.wid_processes_tree.get_value(proc_iter,
179
+                                                    self.PROC_PROC_ID)
173 180
         # show details of selected process
174 181
         self.showDetails(proc_id)
175 182
 
... ...
@@ -178,7 +185,7 @@ class UptGui:
178 185
         Open a trace file.
179 186
         """
180 187
         # forget old processes
181
-        self.widProcessesTree.clear()
188
+        self.wid_processes_tree.clear()
182 189
         # lead new data
183 190
         with open(proto_filename, 'rb') as proto_file:
184 191
             self.processes = uproctrace.processes.Processes(proto_file)
... ...
@@ -191,41 +198,43 @@ class UptGui:
191 198
                 continue
192 199
             proc = procs[0]
193 200
             del procs[0]
194
-            proc_iter = self.widProcessesTree.append(parent_iter)
195
-            self.widProcessesTree.set_value(proc_iter, self.PROC_PROC_ID,
201
+            proc_iter = self.wid_processes_tree.append(parent_iter)
202
+            self.wid_processes_tree.set_value(proc_iter, self.PROC_PROC_ID,
196 203
                                               proc.proc_id)
197
-            self.widProcessesTree.set_value(proc_iter,
204
+            self.wid_processes_tree.set_value(proc_iter,
198 205
                                               self.PROC_BEGIN_TIMESTAMP,
199 206
                                               proc.begin_timestamp)
200
-            self.widProcessesTree.set_value(
207
+            self.wid_processes_tree.set_value(
201 208
                 proc_iter, self.PROC_BEGIN_TIMESTAMP_TEXT,
202 209
                 timestamp2str(proc.begin_timestamp))
203
-            self.widProcessesTree.set_value(proc_iter, self.PROC_END_TIMESTAMP,
210
+            self.wid_processes_tree.set_value(proc_iter,
211
+                                              self.PROC_END_TIMESTAMP,
204 212
                                               proc.end_timestamp)
205
-            self.widProcessesTree.set_value(proc_iter,
206
-                                            self.PROC_END_TIMESTAMP_TEXT,
213
+            self.wid_processes_tree.set_value(
214
+                proc_iter, self.PROC_END_TIMESTAMP_TEXT,
207 215
                 timestamp2str(proc.end_timestamp))
208
-            self.widProcessesTree.set_value(proc_iter, self.PROC_CMDLINE,
216
+            self.wid_processes_tree.set_value(proc_iter, self.PROC_CMDLINE,
209 217
                                               cmdline2str(proc.cmdline))
210
-            self.widProcessesTree.set_value(proc_iter, self.PROC_CPU_TIME,
218
+            self.wid_processes_tree.set_value(proc_iter, self.PROC_CPU_TIME,
211 219
                                               proc.cpu_time)
212
-            self.widProcessesTree.set_value(proc_iter, self.PROC_CPU_TIME_TEXT,
220
+            self.wid_processes_tree.set_value(proc_iter,
221
+                                              self.PROC_CPU_TIME_TEXT,
213 222
                                               duration2str(proc.cpu_time))
214
-            self.widProcessesTree.set_value(proc_iter, self.PROC_MAX_RSS_KB,
223
+            self.wid_processes_tree.set_value(proc_iter, self.PROC_MAX_RSS_KB,
215 224
                                               proc.max_rss_kb)
216
-            self.widProcessesTree.set_value(proc_iter,
225
+            self.wid_processes_tree.set_value(proc_iter,
217 226
                                               self.PROC_MAX_RSS_KB_TEXT,
218 227
                                               kb2str(proc.max_rss_kb))
219 228
             to_be_output.append((proc.children, proc_iter))
220 229
         # show all processes
221
-        self.widProcessesView.expand_all()
230
+        self.wid_processes_view.expand_all()
222 231
 
223 232
     def selectProcess(self, proc_id: int):
224 233
         """
225 234
         Select a process.
226 235
         """
227 236
         # get selection
228
-        proc_sel = self.widProcessesView.get_selection()
237
+        proc_sel = self.wid_processes_view.get_selection()
229 238
         if proc_sel is None:
230 239
             return
231 240
         # deselect all processes
... ...
@@ -239,16 +248,16 @@ class UptGui:
239 248
             if proc_store.get_value(proc_iter, self.PROC_PROC_ID) != proc_id:
240 249
                 return
241 250
             proc_sel.select_iter(proc_iter)
242
-            self.widProcessesView.scroll_to_cell(proc_path)
251
+            self.wid_processes_view.scroll_to_cell(proc_path)
243 252
 
244
-        self.widProcessesTree.foreach(update, None)
253
+        self.wid_processes_tree.foreach(update, None)
245 254
 
246 255
     def showDetails(self, proc_id: int):
247 256
         """
248 257
         Show details of process.
249 258
         """
250 259
         # forget old details
251
-        self.widDetailsTree.clear()
260
+        self.wid_details_tree.clear()
252 261
         # leave if invalid proc_id
253 262
         # get process
254 263
         if proc_id is None or proc_id < 0:
... ...
@@ -258,10 +267,11 @@ class UptGui:
258 267
             return
259 268
         # add details of new process
260 269
         def add(key: str, value: str, parent_iter=None):
261
-            detail_iter = self.widDetailsTree.append(parent_iter)
262
-            self.widDetailsTree.set_value(detail_iter, self.DETAIL_PROC_ID, -1)
263
-            self.widDetailsTree.set_value(detail_iter, self.DETAIL_KEY, key)
264
-            self.widDetailsTree.set_value(detail_iter, self.DETAIL_VALUE,
270
+            detail_iter = self.wid_details_tree.append(parent_iter)
271
+            self.wid_details_tree.set_value(detail_iter, self.DETAIL_PROC_ID,
272
+                                            -1)
273
+            self.wid_details_tree.set_value(detail_iter, self.DETAIL_KEY, key)
274
+            self.wid_details_tree.set_value(detail_iter, self.DETAIL_VALUE,
265 275
                                             value)
266 276
             return detail_iter
267 277
 
... ...
@@ -275,8 +285,8 @@ class UptGui:
275 285
 
276 286
         add('begin time', timestamp2str(proc.begin_timestamp))
277 287
         cmdline_iter = add_list('command line', proc.cmdline)
278
-        self.widDetailsView.expand_row(
279
-            self.widDetailsTree.get_path(cmdline_iter), True)
288
+        self.wid_details_view.expand_row(
289
+            self.wid_details_tree.get_path(cmdline_iter), True)
280 290
         add('CPU time', duration2str(proc.cpu_time))
281 291
         add('end time', timestamp2str(proc.end_timestamp))
282 292
         add_list('environment', sorted(proc.environ))
... ...
@@ -291,7 +301,7 @@ class UptGui:
291 301
             add('parent', '???')
292 302
         else:
293 303
             parent_iter = add('parent', cmdline2str(parent_proc.cmdline))
294
-            self.widDetailsTree.set_value(parent_iter, self.DETAIL_PROC_ID,
304
+            self.wid_details_tree.set_value(parent_iter, self.DETAIL_PROC_ID,
295 305
                                             parent_proc.proc_id)
296 306
         # add children
297 307
         child_procs = proc.children
... ...
@@ -302,27 +312,21 @@ class UptGui:
302 312
             for i, child_proc in enumerate(child_procs):
303 313
                 child_iter = add(f'child {i:d}',
304 314
                                  cmdline2str(child_proc.cmdline), list_iter)
305
-                self.widDetailsTree.set_value(child_iter, self.DETAIL_PROC_ID,
315
+                self.wid_details_tree.set_value(child_iter,
316
+                                                self.DETAIL_PROC_ID,
306 317
                                                 child_proc.proc_id)
307
-            self.widDetailsView.expand_row(
308
-                self.widDetailsTree.get_path(list_iter), True)
318
+            self.wid_details_view.expand_row(
319
+                self.wid_details_tree.get_path(list_iter), True)
309 320
 
310 321
 
311
-def main(argv):
322
+def run(proto_filename):
312 323
     """
313
-    Main program.
324
+    Run the graphical user interface for the specified trace file.
314 325
     """
315
-    if len(argv) < 2:
316
-        print('usage: ' + argv[0] + ' <trace.proto>', file=sys.stderr)
317
-        return 2
318
-    proto_filename = argv[1]
319 326
     app = UptGui(proto_filename)
320 327
     try:
321 328
         Gtk.main()
322 329
     except KeyboardInterrupt:
323 330
         pass
324
-    return 0
325
-
326
-
327
-if __name__ == '__main__':
328
-    sys.exit(main(sys.argv))
331
+    finally:
332
+        del app
... ...
@@ -0,0 +1,5 @@
1
+"""
2
+Glade data for the graphical user interface of UProcTrace.
3
+"""
4
+
5
+DATA = """
... ...
@@ -6,23 +6,30 @@ import argparse
6 6
 import shlex
7 7
 import sys
8 8
 
9
-import uproctrace.dump
10
-import uproctrace.processes
11
-
12 9
 
13 10
 def dump(args):
14 11
     """
15 12
     Dump all events in trace file to standard output.
16 13
     """
14
+    import uproctrace.dump
17 15
     with open(args.trace, 'rb') as proto_file:
18 16
         while uproctrace.dump.dump_event(proto_file, sys.stdout):
19 17
             pass
20 18
 
21 19
 
20
+def gui(args):
21
+    """
22
+    Run the graphical user interface.
23
+    """
24
+    import uproctrace.gui
25
+    uproctrace.gui.run(args.trace)
26
+
27
+
22 28
 def pstree(args):
23 29
     """
24 30
     Print process tree.
25 31
     """
32
+    import uproctrace.processes
26 33
     with open(args.trace, 'rb') as proto_file:
27 34
         processes = uproctrace.processes.Processes(proto_file)
28 35
     # tree output (iterative)
... ...
@@ -55,6 +62,10 @@ def parse_args():
55 62
     # dump
56 63
     dump_parser = subparsers.add_parser('dump', help='Dump events to stdout.')
57 64
     dump_parser.set_defaults(func=dump)
65
+    # gui
66
+    gui_parser = subparsers.add_parser('gui',
67
+                                       help='Run graphical user interface.')
68
+    gui_parser.set_defaults(func=gui)
58 69
     # pstree
59 70
     pstree_parser = subparsers.add_parser('pstree', help='Print process tree.')
60 71
     pstree_parser.set_defaults(func=pstree)
... ...
@@ -1,27 +0,0 @@
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
-)
28 0