implement psinfo subcommand
Stefan Schuermans

Stefan Schuermans commited on 2026-03-03 21:51:13
Showing 4 changed files, with 138 additions and 3 deletions.

... ...
@@ -71,6 +71,7 @@ pyfile(formatting)
71 71
 pyfile(gui)
72 72
 pyfile(parse)
73 73
 pyfile(processes)
74
+pyfile(psinfo)
74 75
 pyfile(pstree)
75 76
 pyfile(stats)
76 77
 pyfile(tool)
... ...
@@ -0,0 +1,108 @@
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 info command line interface of UProcTrace: "upt-tool psinfo".
6
+"""
7
+import argparse
8
+import functools
9
+import uproctrace.formatting
10
+import uproctrace.processes
11
+
12
+
13
+def output(key: str, value: str, indent: int = 0):
14
+    """
15
+    Output a string detail of a process.
16
+    """
17
+    indent_str = "  " * (indent + 1)
18
+    print(f"{indent_str}{key}: {value}")
19
+
20
+
21
+def output_list(key: str, values: list[str], indent: int = 0):
22
+    """
23
+    Output a list of string details of a process.
24
+    """
25
+    if values is None:
26
+        output(key, "???", indent)
27
+        return
28
+    output(key, f"{len(values):d} entries", indent)
29
+    for i, value in enumerate(values):
30
+        output(f"{key} {i:d}", value, indent + 1)
31
+
32
+
33
+def output_list_sorted(key: str, values: list[str], indent: int = 0):
34
+    """
35
+    Wrapper for output_list(...) that sorts the list (if any) before.
36
+    """
37
+    values_sorted = sorted(values) if values is not None else None
38
+    return output_list(key, values_sorted, indent)
39
+
40
+
41
+def output_sum(key: str, sub_keys: list, values: list[int], indent: int = 0):
42
+    """
43
+    Output a sum of multiple values of a process and include individual
44
+    values as subtree.
45
+    """
46
+    sum_val = functools.reduce(uproctrace.formatting.add_none, values, 0)
47
+    output(key, uproctrace.formatting.int2str(sum_val), indent)
48
+    for sub_key, val in zip(sub_keys, values):
49
+        output(sub_key, uproctrace.formatting.int2str(val), indent + 1)
50
+
51
+
52
+def psinfo(args: argparse.Namespace) -> None:
53
+    """
54
+    Print process information.
55
+    """
56
+    # pylint: disable=duplicate-code
57
+    for upt_trace in args.trace:
58
+        if len(args.trace) != 1:
59
+            print(f"[{upt_trace:s}]:")
60
+        with open(upt_trace, "rb") as proto_file:
61
+            processes = uproctrace.processes.Processes(proto_file)
62
+
63
+        proc = processes.getProcess(args.proc_id)
64
+        if proc is None:
65
+            print(f"  proc_id {args.proc_id} not found")
66
+            continue
67
+
68
+        output("begin time", uproctrace.formatting.timestamp2str(proc.begin_timestamp))
69
+        output_list("command line", proc.cmdline)
70
+        output_sum(
71
+            "context switches",
72
+            ["involuntary", "voluntary"],
73
+            [proc.n_iv_csw, proc.n_v_csw],
74
+        )
75
+        output("CPU time", uproctrace.formatting.duration2str(proc.cpu_time))
76
+        output("end time", uproctrace.formatting.timestamp2str(proc.end_timestamp))
77
+        output_list_sorted("environment", proc.environ)
78
+        output("executable", uproctrace.formatting.str2str(proc.exe))
79
+        output_sum(
80
+            "file system operations",
81
+            ["input", "output"],
82
+            [proc.in_block, proc.ou_block],
83
+        )
84
+        output("max. resident memory", uproctrace.formatting.kb2str(proc.max_rss_kb))
85
+        output_sum("page faults", ["major", "minor"], [proc.maj_flt, proc.min_flt])
86
+        output("pid", uproctrace.formatting.int2str(proc.pid))
87
+        output("ppid", uproctrace.formatting.int2str(proc.ppid))
88
+        output("system CPU time", uproctrace.formatting.duration2str(proc.sys_time))
89
+        output("user CPU time", uproctrace.formatting.duration2str(proc.user_time))
90
+        output("working directory", uproctrace.formatting.str2str(proc.cwd))
91
+        # output parent
92
+        parent_proc = proc.parent
93
+        if parent_proc is None:
94
+            output("parent", "???")
95
+        else:
96
+            output("parent", uproctrace.formatting.cmdline2str(parent_proc.cmdline))
97
+        # output children
98
+        child_procs = proc.children
99
+        if child_procs is None:
100
+            output("children", "???")
101
+        else:
102
+            output("children", f"{len(child_procs):d} entries")
103
+            for i, child_proc in enumerate(child_procs):
104
+                output(
105
+                    f"child {i:d}",
106
+                    uproctrace.formatting.cmdline2str(child_proc.cmdline),
107
+                    1,
108
+                )
... ...
@@ -37,7 +37,7 @@ def build(
37 37
         row = [indent]
38 38
         # PIDs
39 39
         if args.pids:
40
-            row += [f"{proc.pid}", f"{proc.ppid}"]
40
+            row += [f"{proc.proc_id}", f"{proc.pid}", f"{proc.ppid}"]
41 41
         # command line
42 42
         cmdline_str = uproctrace.formatting.cmdline2str(proc.cmdline)
43 43
         row.append(cmdline_str)
... ...
@@ -71,7 +71,7 @@ def output(args: argparse.Namespace, rows: list[list[str]]) -> None:
71 71
     if args.table:
72 72
         headers = ["tree"]
73 73
         if args.pids:
74
-            headers += ["pid", "ppid"]
74
+            headers += ["proc_id", "pid", "ppid"]
75 75
         headers.append("cmdline")
76 76
         if args.details:
77 77
             headers += [
... ...
@@ -93,6 +93,7 @@ def pstree(args: argparse.Namespace) -> None:
93 93
     """
94 94
     Print process tree.
95 95
     """
96
+    # pylint: disable=duplicate-code
96 97
     for upt_trace in args.trace:
97 98
         if len(args.trace) != 1:
98 99
             print(f"[{upt_trace:s}]:")
... ...
@@ -56,6 +56,15 @@ def gui(args):
56 56
     return 0
57 57
 
58 58
 
59
+def psinfo(args):
60
+    """
61
+    Print information about a process.
62
+    """
63
+    import uproctrace.psinfo
64
+
65
+    uproctrace.psinfo.psinfo(args)
66
+
67
+
59 68
 def pstree(args):
60 69
     """
61 70
     Print process tree.
... ...
@@ -101,6 +110,22 @@ def parse_args():
101 110
     )
102 111
     gui_parser.set_defaults(func=gui)
103 112
 
113
+    # psinfo
114
+    psinfo_parser = subparsers.add_parser(
115
+        "psinfo",
116
+        help="""
117
+        Print information about a process.
118
+        """,
119
+    )
120
+    psinfo_parser.add_argument(
121
+        "--proc_id",
122
+        "-i",
123
+        type=int,
124
+        required=True,
125
+        help="proc_id of process (this is not the pid)",
126
+    )
127
+    psinfo_parser.set_defaults(func=psinfo)
128
+
104 129
     # pstree
105 130
     pstree_parser = subparsers.add_parser(
106 131
         "pstree",
... ...
@@ -118,7 +143,7 @@ def parse_args():
118 143
         "--pids",
119 144
         "-p",
120 145
         action="store_true",
121
-        help="show PID and parent PID (in front of cmdline)",
146
+        help="show proc_id, pid, parent pid (in front of cmdline)",
122 147
     )
123 148
     pstree_parser.add_argument(
124 149
         "--table", "-t", action="store_true", help="output in form of a table"
125 150