Florian Walbroel commited on 2020-09-04 14:56:18
Showing 4 changed files, with 158 additions and 10 deletions.
... | ... |
@@ -25,7 +25,7 @@ apt-get install -y build-essential cmake gcc \ |
25 | 25 |
libprotobuf-c-dev libprotobuf-dev \ |
26 | 26 |
ninja-build \ |
27 | 27 |
protobuf-c-compiler protobuf-compiler \ |
28 |
- pylint3 python3 python3-protobuf |
|
28 |
+ pylint3 python3 python3-protobuf python3-tabulate |
|
29 | 29 |
``` |
30 | 30 |
|
31 | 31 |
For the graphical user interface, install the additional dependencies: |
... | ... |
@@ -0,0 +1,81 @@ |
1 |
+# UProcTrace: User-space Process Tracing |
|
2 |
+# Copyright 2020: Florian Walbroel, Bonn, Germany <florian@hswalbroel.de> |
|
3 |
+# Copyleft: GNU LESSER GENERAL PUBLIC LICENSE version 3 (see LICENSE) |
|
4 |
+""" |
|
5 |
+Statistics for uproctrace trace files. |
|
6 |
+""" |
|
7 |
+ |
|
8 |
+import tabulate |
|
9 |
+import uproctrace.processes |
|
10 |
+ |
|
11 |
+# Map of process attribute to attribute title and unit |
|
12 |
+_PROCESS_ATTRS = { |
|
13 |
+ "cpu_time": ("CPU Time", "s"), |
|
14 |
+ "sys_time": ("Kernel Time", "s"), |
|
15 |
+ "user_time": ("User Time", "s"), |
|
16 |
+ "in_block": ("Fileystem Input Operations", ""), |
|
17 |
+ "ou_block": ("Fileystem Output Operations", ""), |
|
18 |
+ "maj_flt": ("Major page fault count", ""), |
|
19 |
+ "min_flt": ("Minor page fault count", ""), |
|
20 |
+ "max_rss_kb": ("Maximum Resident Set Size", "KiB"), |
|
21 |
+} |
|
22 |
+ |
|
23 |
+ |
|
24 |
+def calculate_stats(upt_traces: list) -> dict: |
|
25 |
+ """ |
|
26 |
+ Calculates trace statistics, such like the CPU time of processes, for |
|
27 |
+ the given list of traces and returns mapping of process attribute to |
|
28 |
+ tuple of (min value, mean value, max value, cummulative value). |
|
29 |
+ """ |
|
30 |
+ |
|
31 |
+ # The overall statistics |
|
32 |
+ attr_values = dict((attr, []) for attr in _PROCESS_ATTRS) |
|
33 |
+ |
|
34 |
+ for upt_trace in upt_traces: |
|
35 |
+ |
|
36 |
+ # Load all processes of the trace file |
|
37 |
+ with open(upt_trace, "rb") as f: |
|
38 |
+ processes = uproctrace.processes.Processes(f) |
|
39 |
+ |
|
40 |
+ for process in processes._all_processes.values(): |
|
41 |
+ |
|
42 |
+ # Ignore processes for which we do not have full information |
|
43 |
+ if process._begin is None or process._end is None: |
|
44 |
+ continue |
|
45 |
+ |
|
46 |
+ # Update the values |
|
47 |
+ for attr in _PROCESS_ATTRS: |
|
48 |
+ attr_values[attr].append(getattr(process, attr)) |
|
49 |
+ |
|
50 |
+ # Calulate the statistics |
|
51 |
+ stats = dict() |
|
52 |
+ for attr, values in attr_values.items(): |
|
53 |
+ if len(values) == 0: |
|
54 |
+ stats[attr] = (0, 0, 0, 0) |
|
55 |
+ continue |
|
56 |
+ |
|
57 |
+ vmin = min(values) |
|
58 |
+ vmax = max(values) |
|
59 |
+ vcum = sum(values) |
|
60 |
+ vmean = vcum / len(values) |
|
61 |
+ stats[attr] = (vmin, vmean, vmax, vcum) |
|
62 |
+ |
|
63 |
+ return stats |
|
64 |
+ |
|
65 |
+ |
|
66 |
+def dump_stats(upt_traces: list): |
|
67 |
+ """ |
|
68 |
+ Calculates trace statistics, such like the CPU time of processes, for |
|
69 |
+ the given list of traces and dumps the statistics to standard output as |
|
70 |
+ a table. |
|
71 |
+ """ |
|
72 |
+ # Calulate the statistics |
|
73 |
+ stats = calculate_stats(upt_traces) |
|
74 |
+ |
|
75 |
+ rows = [] |
|
76 |
+ for attr, values in stats.items(): |
|
77 |
+ title, unit = _PROCESS_ATTRS[attr] |
|
78 |
+ rows += [[title] + [f"{v:.2f}{unit:s}" for v in values]] |
|
79 |
+ |
|
80 |
+ headers = ["Attribute", "Min", "Mean", "Max", "Cumulative"] |
|
81 |
+ print(tabulate.tabulate(rows, headers=headers)) |
... | ... |
@@ -16,17 +15,42 @@ def dump(args): |
16 | 15 |
Dump all events in trace file to standard output. |
17 | 16 |
""" |
18 | 17 |
import uproctrace.dump |
19 |
- with open(args.trace, 'rb') as proto_file: |
|
18 |
+ for upt_trace in args.trace: |
|
19 |
+ if len(args.trace) != 1: |
|
20 |
+ print(f"[{upt_trace:s}]:") |
|
21 |
+ with open(upt_trace, 'rb') as proto_file: |
|
20 | 22 |
while uproctrace.dump.dump_event(proto_file, sys.stdout): |
21 | 23 |
pass |
24 |
+ if len(args.trace) != 1: |
|
25 |
+ print("") |
|
26 |
+ |
|
27 |
+ |
|
28 |
+def stats(args): |
|
29 |
+ """ |
|
30 |
+ Calculate trace statistics of trace file(s) and dump them to standard |
|
31 |
+ output. |
|
32 |
+ """ |
|
33 |
+ import uproctrace.stats |
|
34 |
+ if not args.per_trace: |
|
35 |
+ uproctrace.stats.dump_stats(args.trace) |
|
36 |
+ return |
|
37 |
+ for upt_trace in args.trace: |
|
38 |
+ print(f"[{upt_trace:s}]:") |
|
39 |
+ uproctrace.stats.dump_stats([upt_trace]) |
|
40 |
+ print("") |
|
22 | 41 |
|
23 | 42 |
|
24 | 43 |
def gui(args): |
25 | 44 |
""" |
26 | 45 |
Run the graphical user interface. |
27 | 46 |
""" |
47 |
+ if len(args.trace) != 1: |
|
48 |
+ print( |
|
49 |
+ "error: upt-tool gui: only one trace file allowed", |
|
50 |
+ file=sys.stderr) |
|
51 |
+ return 1 |
|
28 | 52 |
import uproctrace.gui |
29 |
- uproctrace.gui.run(args.trace) |
|
53 |
+ uproctrace.gui.run(args.trace[0]) |
|
30 | 54 |
|
31 | 55 |
|
32 | 56 |
def pstree(args): |
... | ... |
@@ -34,8 +58,12 @@ def pstree(args): |
34 | 58 |
Print process tree. |
35 | 59 |
""" |
36 | 60 |
import uproctrace.processes |
37 |
- with open(args.trace, 'rb') as proto_file: |
|
61 |
+ for upt_trace in args.trace: |
|
62 |
+ if len(args.trace) != 1: |
|
63 |
+ print(f"[{upt_trace:s}]:") |
|
64 |
+ with open(upt_trace, 'rb') as proto_file: |
|
38 | 65 |
processes = uproctrace.processes.Processes(proto_file) |
66 |
+ |
|
39 | 67 |
# tree output (iterative) |
40 | 68 |
to_be_output = [processes.toplevel] |
41 | 69 |
while to_be_output: |
... | ... |
@@ -61,18 +89,55 @@ def parse_args(): |
61 | 89 |
""" |
62 | 90 |
# set up main parser |
63 | 91 |
parser = argparse.ArgumentParser(description='UProcTrace tool.') |
64 |
- parser.add_argument('trace', metavar='<trace.upt>', help='trace file') |
|
92 |
+ parser.add_argument( |
|
93 |
+ 'trace', |
|
94 |
+ metavar='<trace.upt>', |
|
95 |
+ nargs='+', |
|
96 |
+ help=""" |
|
97 |
+ The UPT trace file(s). |
|
98 |
+ """) |
|
99 |
+ |
|
100 |
+ # Create sub parsers |
|
65 | 101 |
subparsers = parser.add_subparsers() |
102 |
+ |
|
66 | 103 |
# dump |
67 |
- dump_parser = subparsers.add_parser('dump', help='Dump events to stdout.') |
|
104 |
+ dump_parser = subparsers.add_parser( |
|
105 |
+ 'dump', help=""" |
|
106 |
+ Dump events to stdout. |
|
107 |
+ """) |
|
68 | 108 |
dump_parser.set_defaults(func=dump) |
109 |
+ |
|
69 | 110 |
# gui |
70 |
- gui_parser = subparsers.add_parser('gui', |
|
71 |
- help='Run graphical user interface.') |
|
111 |
+ gui_parser = subparsers.add_parser( |
|
112 |
+ 'gui', |
|
113 |
+ help=""" |
|
114 |
+ Run graphical user interface. Only supports a single trace file. |
|
115 |
+ """) |
|
72 | 116 |
gui_parser.set_defaults(func=gui) |
117 |
+ |
|
73 | 118 |
# pstree |
74 |
- pstree_parser = subparsers.add_parser('pstree', help='Print process tree.') |
|
119 |
+ pstree_parser = subparsers.add_parser( |
|
120 |
+ 'pstree', help=""" |
|
121 |
+ Print process tree. |
|
122 |
+ """) |
|
75 | 123 |
pstree_parser.set_defaults(func=pstree) |
124 |
+ |
|
125 |
+ # stats |
|
126 |
+ stats_parser = subparsers.add_parser( |
|
127 |
+ 'stats', help=""" |
|
128 |
+ Dump trace statistics to stdout. |
|
129 |
+ """) |
|
130 |
+ stats_parser.add_argument( |
|
131 |
+ "-p", |
|
132 |
+ "--per-trace", |
|
133 |
+ default=False, |
|
134 |
+ action="store_true", |
|
135 |
+ help=""" |
|
136 |
+ If to calculate trace statistics per individual trace, instead of |
|
137 |
+ calculating statistics over all traces. |
|
138 |
+ """) |
|
139 |
+ stats_parser.set_defaults(func=stats) |
|
140 |
+ |
|
76 | 141 |
# parse |
77 | 142 |
args = parser.parse_args() |
78 | 143 |
if not hasattr(args, 'func'): |
... | ... |
@@ -87,4 +152,4 @@ def main(): |
87 | 152 |
Parse command line arguments and execute selected action. |
88 | 153 |
""" |
89 | 154 |
args = parse_args() |
90 |
- args.func(args) |
|
155 |
+ sys.exit(args.func(args)) |
|
91 | 156 |