source: benchmark/rmit.py@ 2ab31fd

Last change on this file since 2ab31fd was d71db1a, checked in by Thierry Delisle <tdelisle@…>, 3 years ago

rmit not supports unconditional args

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