source: benchmark/rmit.py@ f6737ae1

ADT ast-experimental pthread-emulation qualifiedEnum
Last change on this file since f6737ae1 was f56101f, checked in by Thierry Delisle <tdelisle@…>, 3 years ago

Many fixes to rmit

  • Property mode set to 100755
File size: 9.2 KB
Line 
1#!/usr/bin/python3
2"""
3Python Script to implement R.M.I.T. testing : Randomized Multiple Interleaved Trials
4
5./rmit.py run COMMAND CANDIDATES
6-t trials
7-o option:values
8"""
9
10
11import argparse
12import datetime
13import itertools
14import json
15import os
16import random
17import re
18import socket
19import subprocess
20import sys
21
22
23def parse_range(x):
24 result = []
25 for part in x.split(','):
26 if '-' in part:
27 a, b = part.split('-')
28 a, b = int(a), int(b)
29 result.extend(range(a, b + 1))
30 else:
31 a = int(part)
32 result.append(a)
33 return result
34
35class DependentOpt:
36 def __init__(self, key, value):
37 self.key = key
38 self.value = value
39 self.vars = re.findall("[a-zA-Z]", value)
40
41def parse_option(key, values):
42 try:
43 num = int(values)
44 return key, [num]
45 except:
46 pass
47
48 if re.search("^[0-9-,]+$", values):
49 values = parse_range(values)
50 return key, [v for v in values]
51 else:
52 return key, DependentOpt(key, values)
53
54def eval_one(fmt, vals):
55 orig = fmt
56 for k, v in vals:
57 fmt = fmt.replace(k, str(v))
58
59 if not re.search("^[0-9-/*+ ]+$", fmt):
60 print('ERROR: pattern option {} (interpreted as {}) could not be evaluated'.format(orig, fmt), file=sys.stderr)
61 sys.exit(1)
62
63 return eval(fmt)
64
65# Evaluate all the options
66# options can be of the for key = val or key = some_math(other_key)
67# produce a list of all the options to replace some_math(other_key) with actual value
68def eval_options(opts):
69 # Find all the options with dependencies
70 dependents = [d for d in opts.values() if type(d) is DependentOpt]
71
72 # we need to find all the straglers
73 processed = []
74
75 # extract all the necessary inputs
76 input_keys = {}
77 for d in dependents:
78 # Mark the dependent as seen
79 processed.append(d.key)
80
81 # process each of the dependencies
82 for dvar in d.vars:
83 # Check that it depends on something that exists
84 if not dvar in opts.keys():
85 print('ERROR: extra pattern option {}:{} uses unknown key {}'.format(d.key,d.value,dvar), file=sys.stderr)
86 sys.exit(1)
87
88 # Check that it's not nested
89 if type(dvar) is DependentOpt:
90 print('ERROR: dependent options cannot be nested {}:{} uses key {}'.format(d.key,d.value,dvar), file=sys.stderr)
91 sys.exit(1)
92
93 # Add the values to the input keys
94 if dvar not in input_keys:
95 input_keys[dvar] = opts[dvar]
96 else :
97 if input_keys[dvar] != opts[dvar]:
98 print('INTERNAL ERROR: repeat input do not match {}:{} vs {}'.format(dvar,opts[dvar],input_keys[dvar]), file=sys.stderr)
99 sys.exit(1)
100
101 # Mark the input as seen
102 processed.append(dvar)
103
104 # add in all the straglers they should cause too many problems
105 for k, v in opts.items():
106 if type(v) is DependentOpt:
107 continue
108
109 if k in processed:
110 # consistency check
111 if k not in input_keys:
112 print('INTERNAL ERROR: key \'{}\' marked as processed but not in input_keys'.format(k), file=sys.stderr)
113 sys.exit(1)
114 continue
115
116 # consistency check
117 if k in input_keys:
118 print('INTERNAL ERROR: key \'{}\' in input_keys but not marked as processed'.format(k), file=sys.stderr)
119 sys.exit(1)
120
121 # add the straggler
122 input_keys[k] = v
123
124 # flatten the dict into a list of pairs so it's easier to work with
125 input_list = []
126 for k, v in input_keys.items():
127 input_list.append([(k, o) for o in v])
128
129 # evaluate all the dependents
130 # they are not allowed to produce new values so it's a one-to-one mapping from here
131 evaluated = []
132 for inputs in list(itertools.product(*input_list)):
133 this_eval = list(inputs)
134 for d in dependents:
135 this_eval.append((d.key, eval_one(d.value, inputs)))
136
137 evaluated.append(this_eval)
138
139 # reformat everything to a list of arguments
140 formated = []
141 for o in evaluated:
142 inner = []
143 for k,v in o:
144 inner.append("-{}".format(k))
145 inner.append("{}".format(v))
146
147 # print(inner)
148 formated.append(inner)
149
150 return formated
151
152# returns the first option with key 'opt'
153def search_option(action, opt):
154 i = 0
155 while i < len(action):
156 if action[i] == opt:
157 i += 1
158 if i != len(action):
159 return action[i]
160 i += 1
161
162 return None
163
164def actions_eta(actions):
165 time = 0
166 for a in actions:
167 o = search_option(a, '-d')
168 if o :
169 time += int(o)
170 return time
171
172taskset_maps = None
173
174def init_taskset_maps():
175 global taskset_maps
176 known_hosts = {
177 "jax": {
178 range( 1, 25) : "48-71",
179 range( 25, 49) : "48-71,144-167",
180 range( 49, 97) : "48-95,144-191",
181 range( 97, 145) : "24-95,120-191",
182 range(145, 193) : "0-95,96-191",
183 },
184 }
185
186 if (host := socket.gethostname()) in known_hosts:
187 taskset_maps = known_hosts[host]
188 return True
189
190 print("Warning unknown host '{}', disable taskset usage".format(host), file=sys.stderr)
191 return False
192
193
194def settaskset_one(action):
195 o = search_option(action, '-p')
196 if not o:
197 return action
198 try:
199 oi = int(o)
200 except ValueError:
201 return action
202
203 m = "Not found"
204 for key in taskset_maps:
205 if oi in key:
206 return ['taskset', '-c', taskset_maps[key], *action]
207
208 print("Warning no mapping for {} cores".format(oi), file=sys.stderr)
209 return action
210
211def settaskset(actions):
212 return [settaskset_one(a) for a in actions]
213
214if __name__ == "__main__":
215 # ================================================================================
216 # parse command line arguments
217 formats = ['raw', 'csv', 'json']
218 parser = argparse.ArgumentParser(description='Python Script to implement R.M.I.T. testing : Randomized Multiple Interleaved Trials')
219 parser.add_argument('--list', help='List all the commands that would be run', action='store_true')
220 parser.add_argument('--file', nargs='?', type=argparse.FileType('w'), default=sys.stdout)
221 parser.add_argument('--trials', help='Number of trials to run per combinaison', type=int, default=3)
222 parser.add_argument('--notaskset', help='If specified, the trial will not use taskset to match the -p option', action='store_true')
223 parser.add_argument('command', metavar='command', type=str, nargs=1, help='the command prefix to run')
224 parser.add_argument('candidates', metavar='candidates', type=str, nargs='*', help='the candidate suffix to run')
225
226 try:
227 options, unknown = parser.parse_known_args()
228
229 options.option = []
230 while unknown:
231 key = unknown.pop(0)
232 val = unknown.pop(0)
233
234 if key[0] != '-':
235 raise ValueError
236
237 options.option.append((key[1:], val))
238
239 except:
240 sys.exit(1)
241
242 # ================================================================================
243 # Identify the commands to run
244 command = './' + options.command[0]
245 if options.candidates:
246 commands = [command + "-" + c for c in options.candidates]
247 else:
248 commands = [command]
249 for c in commands:
250 if not os.path.isfile(c):
251 print('ERROR: invalid command {}, file does not exist'.format(c), file=sys.stderr)
252 sys.exit(1)
253
254 if not os.access(c, os.X_OK):
255 print('ERROR: invalid command {}, file not executable'.format(c), file=sys.stderr)
256 sys.exit(1)
257
258
259 # ================================================================================
260 # Identify the options to run
261 opts = dict([parse_option(k, v) for k, v in options.option])
262
263 # Evaluate the options (options can depend on the value of other options)
264 opts = eval_options(opts)
265
266 # ================================================================================
267 # Figure out all the combinations to run
268 actions = []
269 for p in itertools.product(range(options.trials), commands, opts):
270 act = [p[1]]
271 for o in p[2:]:
272 act.extend(o)
273 actions.append(act)
274
275 # ================================================================================
276 # Fixup the different commands
277
278 # Add tasksets
279 withtaskset = False
280 if not options.notaskset and init_taskset_maps():
281 withtaskset = True
282 actions = settaskset(actions)
283
284 # ================================================================================
285 # Now that we know what to run, print it.
286 # find expected time
287 time = actions_eta(actions)
288 print("Running {} trials{}".format(len(actions), "" if time == 0 else " (expecting to take {})".format(str(datetime.timedelta(seconds=int(time)))) ))
289
290 # dry run if options ask for it
291 if options.list:
292 for a in actions:
293 print(" ".join(a))
294 sys.exit(0)
295
296
297 # ================================================================================
298 # Prepare to run
299
300 random.shuffle(actions)
301
302 # ================================================================================
303 # Run
304 options.file.write("[")
305 first = True
306 for i, a in enumerate(actions):
307 sa = " ".join(a[3:] if withtaskset else a)
308 if first:
309 first = False
310 else:
311 options.file.write(",")
312 if options.file != sys.stdout:
313 print("{}/{} : {} \r".format(i, len(actions), sa), end = '')
314 fields = {}
315 with subprocess.Popen( a, stdout = subprocess.PIPE, stderr = subprocess.PIPE) as proc:
316 out, err = proc.communicate()
317 if proc.returncode != 0:
318 print("ERROR: command '{}' encountered error, returned code {}".format(sa, proc.returncode), file=sys.stderr)
319 print(err.decode("utf-8"))
320 sys.exit(1)
321 for s in out.decode("utf-8").splitlines():
322 match = re.search("^(.*):(.*)$", s)
323 if match:
324 try:
325 fields[match.group(1).strip()] = float(match.group(2).strip().replace(',',''))
326 except:
327 pass
328
329 options.file.write(json.dumps([a[3 if withtaskset else 0][2:], sa, fields]))
330 options.file.flush()
331
332 options.file.write("]\n")
333
334 if options.file != sys.stdout:
335 print("Done ")
Note: See TracBrowser for help on using the repository browser.