jjb: babeltrace: use clang-format-16
[lttng-ci.git] / scripts / babeltrace-benchmark / time.py
1 #!/usr/bin/python3
2 # Copyright (C) 2019 - Jonathan Rajotte Julien <jonathan.rajotte-julien@efficios.com>
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 import argparse
18 import os
19 import subprocess
20 import tempfile
21 import json
22 from collections import defaultdict
23
24 def wall_clock_parser(value):
25 """
26 Parse /usr/bin/time wall clock value.
27 Wall clock value is expressed in different formats depending on the actual
28 elapsed time.
29 """
30 total = 0.0
31 pos = value.find(".")
32 if value.find("."):
33 total += float(value[pos:])
34 value = value[:pos]
35
36 v_split = value.split(":")
37 if len(v_split) == 2:
38 total += float(v_split[0]) * 60.0
39 total += float(v_split[1]) * 1.0
40 elif len(v_split) == 3:
41 total += float(v_split[0]) * 360.0
42 total += float(v_split[1]) * 60.0
43 total += float(v_split[2]) * 1.0
44 else:
45 return 0.0
46
47 return total
48
49
50 def percent_parser(value):
51 """
52 Parse /usr/bin/time percent value.
53 """
54 parsed = value.replace("%", "").replace("?", "")
55 if parsed:
56 return float(parsed)
57 return 0
58
59
60 _METRIC = {
61 "User time (seconds)": float,
62 "System time (seconds)": float,
63 "Percent of CPU this job got": percent_parser,
64 "Elapsed (wall clock) time (h:mm:ss or m:ss)": wall_clock_parser,
65 "Average shared text size (kbytes)": int,
66 "Average unshared data size (kbytes)": int,
67 "Average stack size (kbytes)": int,
68 "Average total size (kbytes)": int,
69 "Maximum resident set size (kbytes)": int,
70 "Average resident set size (kbytes)": int,
71 "Major (requiring I/O) page faults": int,
72 "Minor (reclaiming a frame) page faults": int,
73 "Voluntary context switches": int,
74 "Involuntary context switches": int,
75 "Swaps": int,
76 "File system inputs": int,
77 "File system outputs": int,
78 "Socket messages sent": int,
79 "Socket messages received": int,
80 "Signals delivered": int,
81 "Page size (bytes)": int,
82 }
83
84
85
86 def parse(path, results):
87 """
88 Parser and accumulator for /usr/bin/time results.
89 """
90 with open(path, "r") as data:
91 for line in data:
92 if line.rfind(":") == -1:
93 continue
94 key, value = line.lstrip().rsplit(": ")
95 if key in _METRIC:
96 results[key].append(_METRIC[key](value))
97
98 return results
99
100
101 def save(path, results):
102 """
103 Save the result in json format to path.
104 """
105 with open(path, "w") as out:
106 json.dump(results, out, sort_keys=True, indent=4)
107
108
109 def run(command, iteration, output, stdout, stderr):
110 """
111 Run the command throught /usr/bin/time n iterations and parse each result.
112 """
113 results = defaultdict(list)
114 for i in range(iteration):
115 time_stdout = tempfile.NamedTemporaryFile(delete=False)
116 # We must delete this file later on.
117 time_stdout.close()
118 with open(stdout, "a+") as out, open(stderr, "a+") as err:
119 cmd = "/usr/bin/time -v --output='{}' {}".format(time_stdout.name, command)
120 ret = subprocess.run(cmd, shell=True, stdout=out, stderr=err)
121 if ret.returncode != 0:
122 print("Iteration: {}, Command failed: {}".format(str(i), cmd))
123 results = parse(time_stdout.name, results)
124 os.remove(time_stdout.name)
125 save(output, results)
126
127
128 def main():
129 """
130 Run /usr/bin/time N time and collect the result.
131 The resulting json have the following form:
132 {
133 "/usr/bin/time": {
134 "User time (seconds)": [],
135 "System time (seconds)": [],
136 "Percent of CPU this job got": [],
137 "Elapsed (wall clock) time (h:mm:ss or m:ss)": [],
138 "Average shared text size (kbytes)": [],
139 "Average unshared data size (kbytes)": [],
140 "Average stack size (kbytes)": [],
141 "Average total size (kbytes)": [],
142 "Maximum resident set size (kbytes)": [],
143 "Average resident set size (kbytes)": [],
144 "Major (requiring I/O) page faults": [],
145 "Minor (reclaiming a frame) page faults": [],
146 "Voluntary context switches": [],
147 "Involuntary context switches": [],
148 "Swaps": [],
149 "File system inputs": [],
150 "File system outputs": [],
151 "Socket messages sent": [],
152 "Socket messages received": [],
153 "Signals delivered": [],
154 "Page size (bytes)": [],
155 }
156 }
157 """
158 parser = argparse.ArgumentParser(
159 description="Run command N time using /usr/bin/time and collect the statistics"
160 )
161 parser.add_argument("--output", help="Where to same the result", required=True)
162 parser.add_argument("--command", help="The command to benchmark", required=True)
163 parser.add_argument(
164 "--iteration",
165 type=int,
166 default=5,
167 help="The number of iteration to run the command (default: 5)",
168 required=True,
169 )
170 parser.add_argument(
171 "--stdout",
172 default="/dev/null",
173 help="Where to append the stdout of each command (default: /dev/null)",
174 )
175 parser.add_argument(
176 "--stderr",
177 default=os.path.join(os.getcwd(), "stderr.out"),
178 help="Where to append the stderr of each command (default: $CWD/stderr.out)",
179 )
180
181 args = parser.parse_args()
182 run(args.command, args.iteration, args.output, args.stdout, args.stderr)
183
184
185 if __name__ == "__main__":
186 main()
This page took 0.033755 seconds and 4 git commands to generate.