support output of details in pstree
Stefan Schuermans

Stefan Schuermans commited on 2026-03-03 21:27:04
Showing 5 changed files, with 331 additions and 206 deletions.

... ...
@@ -67,9 +67,11 @@ endfunction(pyfile)
67 67
 
68 68
 pyfile(__init__)
69 69
 pyfile(dump)
70
+pyfile(formatting)
70 71
 pyfile(gui)
71 72
 pyfile(parse)
72 73
 pyfile(processes)
74
+pyfile(pstree)
73 75
 pyfile(stats)
74 76
 pyfile(tool)
75 77
 
... ...
@@ -0,0 +1,138 @@
1
+# UProcTrace: User-space Process Tracing
2
+# Copyright 2026: Stefan Schuermans, Aachen, Germany <stefan@schuermans.info>
3
+# Copyleft: GNU LESSER GENERAL PUBLIC LICENSE version 3 (see LICENSE)
4
+"""
5
+Formatting of metrics to text for UProcTrace.
6
+"""
7
+
8
+import re
9
+import shlex
10
+import time
11
+
12
+# regular expression for an environment variable assignment
13
+RE_ENV_VAR = re.compile(r"^(?P<name>[A-Za-z_][A-Za-z0-9_]*)=(?P<value>.*)$")
14
+
15
+
16
+def add_none(val_a: int, val_b: int) -> int:
17
+    """
18
+    Integer addition with support for None.
19
+    """
20
+    if val_a is None or val_b is None:
21
+        return None
22
+    return val_a + val_b
23
+
24
+
25
+def cmdline2str(cmdline: list[str]) -> str:
26
+    """
27
+    Convert command line to string.
28
+    """
29
+    if cmdline is None:
30
+        return "???"
31
+    return " ".join([cmdline_str_escape(s) for s in cmdline])
32
+
33
+
34
+def cmdline_str_escape(string: str) -> str:
35
+    """
36
+    Escape a command line string for shell use in a way that also works for
37
+    environment variables (i.e., not escaping the variable name).
38
+    """
39
+    match = RE_ENV_VAR.match(string)
40
+    if not match:
41
+        # not a variable assignment -> escape entire string
42
+        return shlex.quote(string)
43
+    # variable assignment -> escape only value part
44
+    # (also works if it only looks like a variable assignment)
45
+    name = match.group("name")
46
+    value = shlex.quote(match.group("value"))
47
+    return f"{name:s}={value:s}"
48
+
49
+
50
+def duration2str(duration: float) -> str:
51
+    """
52
+    Convert duration to string.
53
+    """
54
+    if duration is None:
55
+        return "???"
56
+    # split into day, hours, minutes, seconds
57
+    dur_s = int(duration)
58
+    dur_m = dur_s // 60
59
+    dur_s = dur_s % 60
60
+    dur_h = dur_m // 60
61
+    dur_m = dur_m % 60
62
+    dur_d = dur_h // 24
63
+    dur_h = dur_h % 24
64
+    # split into ms, us, ns
65
+    dur_ns = int((duration - dur_s) * 1e9)
66
+    dur_us = dur_ns // 1000
67
+    dur_ns = dur_ns % 1000
68
+    dur_ms = dur_us // 1000
69
+    dur_us = dur_us % 1000
70
+    # assemble text
71
+    txt = ""
72
+    if dur_d > 0:
73
+        txt += f"{dur_d:d} d "
74
+    if dur_h > 0 or txt:
75
+        txt += f"{dur_h:d} h "
76
+    if dur_m > 0 or txt:
77
+        txt += f"{dur_m:d} m "
78
+    if dur_s > 0 or txt:
79
+        txt += f"{dur_s:d} s "
80
+    if dur_ms > 0 or txt:
81
+        txt += f"{dur_ms:d} ms "
82
+    if dur_us > 0 or txt:
83
+        txt += f"{dur_us:d} us "
84
+    txt += f"{dur_ns:d} ns "
85
+    txt += f"({duration:f} s)"
86
+    return txt
87
+
88
+
89
+def int2str(val: int) -> str:
90
+    """
91
+    Convert integer to string, support None.
92
+    """
93
+    if val is None:
94
+        return "???"
95
+    return f"{val:d}"
96
+
97
+
98
+def kb2str(size_kb: int) -> str:
99
+    """
100
+    Convert size in KiB to string.
101
+    """
102
+    if size_kb is None:
103
+        return "???"
104
+    # split into GiB, MiB, KiB
105
+    mib = size_kb // 1024
106
+    kib = size_kb % 1024
107
+    gib = mib // 1024
108
+    mib = mib % 1024
109
+    # assemble text
110
+    txt = ""
111
+    if gib > 0:
112
+        txt += f"{gib:d} GiB "
113
+    if mib > 0 or txt:
114
+        txt += f"{mib:d} MiB "
115
+    txt += f"{kib:d} KiB "
116
+    txt += f"({size_kb:d} KiB)"
117
+    return txt
118
+
119
+
120
+def str2str(str_or_none: str) -> str:
121
+    """
122
+    Convert string (or None) to string.
123
+    """
124
+    if str_or_none is None:
125
+        return "???"
126
+    return str_or_none
127
+
128
+
129
+def timestamp2str(timestamp: float) -> str:
130
+    """
131
+    Convert a timestamp to a human-reable time string."
132
+    """
133
+    if timestamp is None:
134
+        return "???"
135
+    sec = int(timestamp)
136
+    nsec = int((timestamp - sec) * 1e9)
137
+    time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(sec))
138
+    return time_str + f".{nsec:09d}"
... ...
@@ -6,11 +6,9 @@ Graphical user interface of UProcTrace.
6 6
 """
7 7
 
8 8
 import functools
9
-import re
10
-import shlex
11 9
 import signal
12
-import time
13 10
 
11
+import uproctrace.formatting
14 12
 import uproctrace.gui_glade
15 13
 import uproctrace.processes
16 14
 
... ...
@@ -21,134 +19,6 @@ gi.require_version("Gtk", "4.0")
21 19
 # pylint: disable=too-many-positional-arguments
22 20
 from gi.repository import Gdk, Gtk, GLib
23 21
 
24
-# regular expression for an environment variable assignment
25
-RE_ENV_VAR = re.compile(r"^(?P<name>[A-Za-z_][A-Za-z0-9_]*)=(?P<value>.*)$")
26
-
27
-
28
-def add_none(val_a: int, val_b: int) -> int:
29
-    """
30
-    Integer addition with support for None.
31
-    """
32
-    if val_a is None or val_b is None:
33
-        return None
34
-    return val_a + val_b
35
-
36
-
37
-def cmdline2str(cmdline: list) -> str:
38
-    """
39
-    Convert command line to string.
40
-    """
41
-    if cmdline is None:
42
-        return "???"
43
-    return " ".join([cmdline_str_escape(s) for s in cmdline])
44
-
45
-
46
-def cmdline_str_escape(string: str) -> str:
47
-    """
48
-    Escape a command line string for shell use in a way that also works for
49
-    environment variables (i.e., not escaping the variable name).
50
-    """
51
-    match = RE_ENV_VAR.match(string)
52
-    if not match:
53
-        # not a variable assignment -> escape entire string
54
-        return shlex.quote(string)
55
-    # variable assignment -> escape only value part
56
-    # (also works if it only looks like a variable assignment)
57
-    name = match.group("name")
58
-    value = shlex.quote(match.group("value"))
59
-    return f"{name:s}={value:s}"
60
-
61
-
62
-def duration2str(duration: float) -> str:
63
-    """
64
-    Convert duration to string.
65
-    """
66
-    if duration is None:
67
-        return "???"
68
-    # split into day, hours, minutes, seconds
69
-    dur_s = int(duration)
70
-    dur_m = dur_s // 60
71
-    dur_s = dur_s % 60
72
-    dur_h = dur_m // 60
73
-    dur_m = dur_m % 60
74
-    dur_d = dur_h // 24
75
-    dur_h = dur_h % 24
76
-    # split into ms, us, ns
77
-    dur_ns = int((duration - dur_s) * 1e9)
78
-    dur_us = dur_ns // 1000
79
-    dur_ns = dur_ns % 1000
80
-    dur_ms = dur_us // 1000
81
-    dur_us = dur_us % 1000
82
-    # assemble text
83
-    txt = ""
84
-    if dur_d > 0:
85
-        txt += f"{dur_d:d} d "
86
-    if dur_h > 0 or txt:
87
-        txt += f"{dur_h:d} h "
88
-    if dur_m > 0 or txt:
89
-        txt += f"{dur_m:d} m "
90
-    if dur_s > 0 or txt:
91
-        txt += f"{dur_s:d} s "
92
-    if dur_ms > 0 or txt:
93
-        txt += f"{dur_ms:d} ms "
94
-    if dur_us > 0 or txt:
95
-        txt += f"{dur_us:d} us "
96
-    txt += f"{dur_ns:d} ns "
97
-    txt += f"({duration:f} s)"
98
-    return txt
99
-
100
-
101
-def int2str(val: int) -> str:
102
-    """
103
-    Convert integer to string, support None.
104
-    """
105
-    if val is None:
106
-        return "???"
107
-    return f"{val:d}"
108
-
109
-
110
-def kb2str(size_kb: int) -> str:
111
-    """
112
-    Convert size in KiB to string.
113
-    """
114
-    if size_kb is None:
115
-        return "???"
116
-    # split into GiB, MiB, KiB
117
-    mib = size_kb // 1024
118
-    kib = size_kb % 1024
119
-    gib = mib // 1024
120
-    mib = mib % 1024
121
-    # assemble text
122
-    txt = ""
123
-    if gib > 0:
124
-        txt += f"{gib:d} GiB "
125
-    if mib > 0 or txt:
126
-        txt += f"{mib:d} MiB "
127
-    txt += f"{kib:d} KiB "
128
-    txt += f"({size_kb:d} KiB)"
129
-    return txt
130
-
131
-
132
-def str2str(str_or_none: str) -> str:
133
-    """
134
-    Convert string (or None) to string.
135
-    """
136
-    if str_or_none is None:
137
-        return "???"
138
-    return str_or_none
139
-
140
-
141
-def timestamp2str(timestamp: float) -> str:
142
-    """
143
-    Convert a timestamp to a human-reable time string."
144
-    """
145
-    if timestamp is None:
146
-        return "???"
147
-    sec = int(timestamp)
148
-    nsec = int((timestamp - sec) * 1e9)
149
-    time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(sec))
150
-    return time_str + f".{nsec:09d}"
151
-
152 22
 
153 23
 class UptGui(Gtk.Application):
154 24
     """
... ...
@@ -243,53 +113,55 @@ class UptGui(Gtk.Application):
243 113
             proc_iter,
244 114
             self.PROC_BEGIN_TIMESTAMP,
245 115
             self.PROC_BEGIN_TIMESTAMP_TEXT,
246
-            timestamp2str,
116
+            uproctrace.formatting.timestamp2str,
247 117
             proc.begin_timestamp,
248 118
         )
249 119
         self.fillProcessesEntryAttr(
250 120
             proc_iter,
251 121
             self.PROC_END_TIMESTAMP,
252 122
             self.PROC_END_TIMESTAMP_TEXT,
253
-            timestamp2str,
123
+            uproctrace.formatting.timestamp2str,
254 124
             proc.end_timestamp,
255 125
         )
256 126
         self.wid_processes_tree.set_value(
257
-            proc_iter, self.PROC_CMDLINE, cmdline2str(proc.cmdline)
127
+            proc_iter,
128
+            self.PROC_CMDLINE,
129
+            uproctrace.formatting.cmdline2str(proc.cmdline),
258 130
         )
259 131
         self.fillProcessesEntryAttr(
260 132
             proc_iter,
261 133
             self.PROC_CPU_TIME,
262 134
             self.PROC_CPU_TIME_TEXT,
263
-            duration2str,
135
+            uproctrace.formatting.duration2str,
264 136
             proc.cpu_time,
265 137
         )
266 138
         self.fillProcessesEntryAttr(
267 139
             proc_iter,
268 140
             self.PROC_MAX_RSS_KB,
269 141
             self.PROC_MAX_RSS_KB_TEXT,
270
-            kb2str,
142
+            uproctrace.formatting.kb2str,
271 143
             proc.max_rss_kb,
272 144
         )
273 145
         self.fillProcessesEntryAttr(
274 146
             proc_iter,
275 147
             self.PROC_PAGE_FAULTS,
276 148
             self.PROC_PAGE_FAULTS_TEXT,
277
-            int2str,
278
-            add_none(proc.min_flt, proc.maj_flt),
149
+            uproctrace.formatting.int2str,
150
+            uproctrace.formatting.add_none(proc.min_flt, proc.maj_flt),
279 151
         )
280 152
         self.fillProcessesEntryAttr(
281 153
             proc_iter,
282 154
             self.PROC_FILE_SYS_OPS,
283 155
             self.PROC_FILE_SYS_OPS_TEXT,
284
-            int2str,
285
-            add_none(proc.in_block, proc.ou_block),
156
+            uproctrace.formatting.int2str,
157
+            uproctrace.formatting.add_none(proc.in_block, proc.ou_block),
286 158
         )
287 159
         self.fillProcessesEntryAttr(
288 160
             proc_iter,
289 161
             self.PROC_CTX_SW,
290 162
             self.PROC_CTX_SW_TEXT,
291
-            int2str,
292
-            add_none(proc.n_v_csw, proc.n_iv_csw),
163
+            uproctrace.formatting.int2str,
164
+            uproctrace.formatting.add_none(proc.n_v_csw, proc.n_iv_csw),
293 165
         )
294 166
 
295 167
     def fillProcessesEntryAttr(
... ...
@@ -329,7 +201,7 @@ class UptGui(Gtk.Application):
329 201
                     self.wid_details_tree.get_value(child_iter, self.DETAIL_VALUE)
330 202
                 )
331 203
                 child_iter = self.wid_details_tree.iter_next(child_iter)
332
-            string = cmdline2str(strings)
204
+            string = uproctrace.formatting.cmdline2str(strings)
333 205
         self.storeInClipboardAndNotify(string)
334 206
         # get proc_id of selected row, nothing else to do if none
335 207
         proc_id = self.wid_details_tree.get_value(detail_iter, self.DETAIL_PROC_ID)
... ...
@@ -379,11 +251,13 @@ class UptGui(Gtk.Application):
379 251
         # ( cd <workdir>; env -i <environment> <cmdline> )
380 252
         string = "("
381 253
         if proc.cwd:
382
-            string += " cd " + cmdline_str_escape(proc.cwd) + ";"
254
+            string += " cd " + uproctrace.formatting.cmdline_str_escape(proc.cwd) + ";"
383 255
         if proc.environ:
384
-            string += " env -i " + cmdline2str(sorted(proc.environ))
256
+            string += " env -i " + uproctrace.formatting.cmdline2str(
257
+                sorted(proc.environ)
258
+            )
385 259
         if proc.cmdline:
386
-            string += " " + cmdline2str(proc.cmdline)
260
+            string += " " + uproctrace.formatting.cmdline2str(proc.cmdline)
387 261
         string += " )"
388 262
         self.storeInClipboardAndNotify(string)
389 263
 
... ...
@@ -459,6 +333,7 @@ class UptGui(Gtk.Application):
459 333
         to_be_output = [(self.processes.toplevel, None)]
460 334
         while to_be_output:
461 335
             procs, parent_iter = to_be_output[-1]
336
+            # pylint: disable=duplicate-code
462 337
             if not procs:
463 338
                 del to_be_output[-1]
464 339
                 continue
... ...
@@ -555,16 +430,16 @@ class UptGui(Gtk.Application):
555 430
             Add to specified parent (if parent_iter is specified).
556 431
             Return iterator to added top-level of detail subtree.
557 432
             """
558
-            sum_val = functools.reduce(add_none, values, 0)
559
-            sum_iter = add(key, int2str(sum_val), parent_iter)
433
+            sum_val = functools.reduce(uproctrace.formatting.add_none, values, 0)
434
+            sum_iter = add(key, uproctrace.formatting.int2str(sum_val), parent_iter)
560 435
             for sub_key, val in zip(sub_keys, values):
561
-                add(sub_key, int2str(val), sum_iter)
436
+                add(sub_key, uproctrace.formatting.int2str(val), sum_iter)
562 437
             self.wid_details_view.expand_row(
563 438
                 self.wid_details_tree.get_path(sum_iter), True
564 439
             )
565 440
             return sum_iter
566 441
 
567
-        add("begin time", timestamp2str(proc.begin_timestamp))
442
+        add("begin time", uproctrace.formatting.timestamp2str(proc.begin_timestamp))
568 443
         cmdline_iter = add_list("command line", proc.cmdline)
569 444
         self.wid_details_view.expand_row(
570 445
             self.wid_details_tree.get_path(cmdline_iter), True
... ...
@@ -574,28 +449,30 @@ class UptGui(Gtk.Application):
574 449
             ["involuntary", "voluntary"],
575 450
             [proc.n_iv_csw, proc.n_v_csw],
576 451
         )
577
-        add("CPU time", duration2str(proc.cpu_time))
578
-        add("end time", timestamp2str(proc.end_timestamp))
452
+        add("CPU time", uproctrace.formatting.duration2str(proc.cpu_time))
453
+        add("end time", uproctrace.formatting.timestamp2str(proc.end_timestamp))
579 454
         add_list_sorted("environment", proc.environ)
580
-        add("executable", str2str(proc.exe))
455
+        add("executable", uproctrace.formatting.str2str(proc.exe))
581 456
         add_sum(
582 457
             "file system operations",
583 458
             ["input", "output"],
584 459
             [proc.in_block, proc.ou_block],
585 460
         )
586
-        add("max. resident memory", kb2str(proc.max_rss_kb))
461
+        add("max. resident memory", uproctrace.formatting.kb2str(proc.max_rss_kb))
587 462
         add_sum("page faults", ["major", "minor"], [proc.maj_flt, proc.min_flt])
588
-        add("pid", int2str(proc.pid))
589
-        add("ppid", int2str(proc.ppid))
590
-        add("system CPU time", duration2str(proc.sys_time))
591
-        add("user CPU time", duration2str(proc.user_time))
592
-        add("working directory", str2str(proc.cwd))
463
+        add("pid", uproctrace.formatting.int2str(proc.pid))
464
+        add("ppid", uproctrace.formatting.int2str(proc.ppid))
465
+        add("system CPU time", uproctrace.formatting.duration2str(proc.sys_time))
466
+        add("user CPU time", uproctrace.formatting.duration2str(proc.user_time))
467
+        add("working directory", uproctrace.formatting.str2str(proc.cwd))
593 468
         # add parent
594 469
         parent_proc = proc.parent
595 470
         if parent_proc is None:
596 471
             add("parent", "???")
597 472
         else:
598
-            parent_iter = add("parent", cmdline2str(parent_proc.cmdline))
473
+            parent_iter = add(
474
+                "parent", uproctrace.formatting.cmdline2str(parent_proc.cmdline)
475
+            )
599 476
             self.wid_details_tree.set_value(
600 477
                 parent_iter, self.DETAIL_PROC_ID, parent_proc.proc_id
601 478
             )
... ...
@@ -607,7 +484,9 @@ class UptGui(Gtk.Application):
607 484
             list_iter = add("children", f"{len(child_procs):d} entries")
608 485
             for i, child_proc in enumerate(child_procs):
609 486
                 child_iter = add(
610
-                    f"child {i:d}", cmdline2str(child_proc.cmdline), list_iter
487
+                    f"child {i:d}",
488
+                    uproctrace.formatting.cmdline2str(child_proc.cmdline),
489
+                    list_iter,
611 490
                 )
612 491
                 self.wid_details_tree.set_value(
613 492
                     child_iter, self.DETAIL_PROC_ID, child_proc.proc_id
... ...
@@ -0,0 +1,103 @@
1
+# UProcTrace: User-space Process Tracing
2
+# Copyright 2020: Stefan Schuermans, Aachen, Germany <stefan@schuermans.info>
3
+# Copyleft: GNU LESSER GENERAL PUBLIC LICENSE version 3 (see LICENSE)
4
+"""
5
+Process tree command line interface of UProcTrace: "upt-tool pstree".
6
+"""
7
+import argparse
8
+import tabulate
9
+import uproctrace.formatting
10
+import uproctrace.processes
11
+
12
+
13
+def build(
14
+    args: argparse.Namespace, processes: uproctrace.processes.Processes
15
+) -> list[list[str]]:
16
+    """
17
+    Build rows for pstree command.
18
+    """
19
+
20
+    # build process tree (iterative)
21
+    to_be_output = [processes.toplevel]
22
+    rows: list[list[str]] = []
23
+    while to_be_output:
24
+        procs = to_be_output[-1]
25
+        # pylint: disable=duplicate-code
26
+        if not procs:
27
+            del to_be_output[-1]
28
+            continue
29
+        proc = procs[0]
30
+        del procs[0]
31
+
32
+        # tree / indentation
33
+        if args.table:
34
+            indent = "--" * (len(to_be_output) - 1) + ">"
35
+        else:
36
+            indent = "  " * (len(to_be_output) - 1)
37
+        row = [indent]
38
+        # PIDs
39
+        if args.pids:
40
+            row += [f"{proc.pid}", f"{proc.ppid}"]
41
+        # command line
42
+        cmdline_str = uproctrace.formatting.cmdline2str(proc.cmdline)
43
+        row.append(cmdline_str)
44
+        # details
45
+        if args.details:
46
+            row += [
47
+                uproctrace.formatting.timestamp2str(proc.begin_timestamp),
48
+                uproctrace.formatting.timestamp2str(proc.end_timestamp),
49
+                uproctrace.formatting.duration2str(proc.cpu_time),
50
+                uproctrace.formatting.kb2str(proc.max_rss_kb),
51
+                uproctrace.formatting.int2str(
52
+                    uproctrace.formatting.add_none(proc.min_flt, proc.maj_flt)
53
+                ),
54
+                uproctrace.formatting.int2str(
55
+                    uproctrace.formatting.add_none(proc.in_block, proc.ou_block)
56
+                ),
57
+                uproctrace.formatting.int2str(
58
+                    uproctrace.formatting.add_none(proc.n_v_csw, proc.n_iv_csw)
59
+                ),
60
+            ]
61
+        rows.append(row)
62
+        to_be_output.append(proc.children)
63
+
64
+    return rows
65
+
66
+
67
+def output(args: argparse.Namespace, rows: list[list[str]]) -> None:
68
+    """
69
+    Output rows of pstree command.
70
+    """
71
+    if args.table:
72
+        headers = ["tree"]
73
+        if args.pids:
74
+            headers += ["pid", "ppid"]
75
+        headers.append("cmdline")
76
+        if args.details:
77
+            headers += [
78
+                "begin",
79
+                "end",
80
+                "CPU time",
81
+                "memory",
82
+                "page faults",
83
+                "filesys ops",
84
+                "ctx switches",
85
+            ]
86
+        print(tabulate.tabulate(rows, headers))
87
+    else:
88
+        for row in rows:
89
+            print(" ".join(row))
90
+
91
+
92
+def pstree(args: argparse.Namespace) -> None:
93
+    """
94
+    Print process tree.
95
+    """
96
+    for upt_trace in args.trace:
97
+        if len(args.trace) != 1:
98
+            print(f"[{upt_trace:s}]:")
99
+        with open(upt_trace, "rb") as proto_file:
100
+            processes = uproctrace.processes.Processes(proto_file)
101
+
102
+        rows = build(args, processes)
103
+        output(args, rows)
... ...
@@ -6,9 +6,10 @@ Command line interface of UProcTrace: "upt-tool".
6 6
 """
7 7
 
8 8
 import argparse
9
-import shlex
10 9
 import sys
11 10
 
11
+# pylint: disable=import-outside-toplevel
12
+
12 13
 
13 14
 def dump(args):
14 15
     """
... ...
@@ -18,7 +20,7 @@ def dump(args):
18 20
     for upt_trace in args.trace:
19 21
         if len(args.trace) != 1:
20 22
             print(f"[{upt_trace:s}]:")
21
-        with open(upt_trace, 'rb') as proto_file:
23
+        with open(upt_trace, "rb") as proto_file:
22 24
             while uproctrace.dump.dump_event(proto_file, sys.stdout):
23 25
                 pass
24 26
         if len(args.trace) != 1:
... ...
@@ -45,11 +48,10 @@ def gui(args):
45 48
     Run the graphical user interface.
46 49
     """
47 50
     if len(args.trace) != 1:
48
-        print(
49
-            "error: upt-tool gui: only one trace file allowed",
50
-            file=sys.stderr)
51
+        print("error: upt-tool gui: only one trace file allowed", file=sys.stderr)
51 52
         return 1
52 53
     import uproctrace.gui
54
+
53 55
     uproctrace.gui.run(args.trace[0])
54 56
     return 0
55 57
 
... ...
@@ -58,30 +60,9 @@ def pstree(args):
58 60
     """
59 61
     Print process tree.
60 62
     """
61
-    import uproctrace.processes
62
-    for upt_trace in args.trace:
63
-        if len(args.trace) != 1:
64
-            print(f"[{upt_trace:s}]:")
65
-        with open(upt_trace, 'rb') as proto_file:
66
-            processes = uproctrace.processes.Processes(proto_file)
67
-
68
-        # tree output (iterative)
69
-        to_be_output = [processes.toplevel]
70
-        while to_be_output:
71
-            procs = to_be_output[-1]
72
-            if not procs:
73
-                del to_be_output[-1]
74
-                continue
75
-            indent = '  ' * (len(to_be_output) - 1)
76
-            proc = procs[0]
77
-            del procs[0]
78
-            cmdline = proc.cmdline
79
-            if cmdline is None:
80
-                cmdline_str = '???'
81
-            else:
82
-                cmdline_str = ' '.join([shlex.quote(s) for s in cmdline])
83
-            print(indent + cmdline_str)
84
-            to_be_output.append(proc.children)
63
+    import uproctrace.pstree
64
+
65
+    uproctrace.pstree.pstree(args)
85 66
 
86 67
 
87 68
 def parse_args():
... ...
@@ -89,45 +70,68 @@ def parse_args():
89 70
     Parse command line arguments.
90 71
     """
91 72
     # set up main parser
92
-    parser = argparse.ArgumentParser(description='UProcTrace tool.')
73
+    parser = argparse.ArgumentParser(description="UProcTrace tool.")
93 74
     parser.add_argument(
94
-        'trace',
95
-        metavar='<trace.upt>',
96
-        nargs='+',
75
+        "trace",
76
+        metavar="<trace.upt>",
77
+        nargs="+",
97 78
         help="""
98 79
         The UPT trace file(s).
99
-        """)
80
+        """,
81
+    )
100 82
 
101 83
     # Create sub parsers
102 84
     subparsers = parser.add_subparsers()
103 85
 
104 86
     # dump
105 87
     dump_parser = subparsers.add_parser(
106
-        'dump', help="""
88
+        "dump",
89
+        help="""
107 90
         Dump events to stdout.
108
-        """)
91
+        """,
92
+    )
109 93
     dump_parser.set_defaults(func=dump)
110 94
 
111 95
     # gui
112 96
     gui_parser = subparsers.add_parser(
113
-        'gui',
97
+        "gui",
114 98
         help="""
115 99
         Run graphical user interface. Only supports a single trace file.
116
-        """)
100
+        """,
101
+    )
117 102
     gui_parser.set_defaults(func=gui)
118 103
 
119 104
     # pstree
120 105
     pstree_parser = subparsers.add_parser(
121
-        'pstree', help="""
106
+        "pstree",
107
+        help="""
122 108
         Print process tree.
123
-        """)
109
+        """,
110
+    )
111
+    pstree_parser.add_argument(
112
+        "--details",
113
+        "-d",
114
+        action="store_true",
115
+        help="show process details (behind cmdline)",
116
+    )
117
+    pstree_parser.add_argument(
118
+        "--pids",
119
+        "-p",
120
+        action="store_true",
121
+        help="show PID and parent PID (in front of cmdline)",
122
+    )
123
+    pstree_parser.add_argument(
124
+        "--table", "-t", action="store_true", help="output in form of a table"
125
+    )
124 126
     pstree_parser.set_defaults(func=pstree)
125 127
 
126 128
     # stats
127 129
     stats_parser = subparsers.add_parser(
128
-        'stats', help="""
130
+        "stats",
131
+        help="""
129 132
       Dump trace statistics to stdout.
130
-      """)
133
+      """,
134
+    )
131 135
     stats_parser.add_argument(
132 136
         "-p",
133 137
         "--per-trace",
... ...
@@ -136,13 +140,14 @@ def parse_args():
136 140
         help="""
137 141
         If to calculate trace statistics per individual trace, instead of
138 142
         calculating statistics over all traces.
139
-        """)
143
+        """,
144
+    )
140 145
     stats_parser.set_defaults(func=stats)
141 146
 
142 147
     # parse
143 148
     args = parser.parse_args()
144
-    if not hasattr(args, 'func'):
145
-        print('error: no sub-command specified', file=sys.stderr)
149
+    if not hasattr(args, "func"):
150
+        print("error: no sub-command specified", file=sys.stderr)
146 151
         sys.exit(3)
147 152
     return args
148 153
 
149 154