source: benchmark/rmit.py @ c329bca

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

rmit not supports unconditional args

  • Property mode set to 100755
File size: 9.6 KB
RevLine 
[7a2a3af]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
[dbb1073]12import datetime
[7a2a3af]13import itertools
[883c4d9]14import json
[7a2a3af]15import os
16import random
17import re
[3e9ec44]18import socket
[7a2a3af]19import subprocess
20import sys
21
22
23def parse_range(x):
[d71db1a]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
[7a2a3af]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
[af333e3]41def parse_option(key, values):
[7a2a3af]42        try:
43                num = int(values)
44                return key, [num]
45        except:
46                pass
47
[3613e25]48        if values.startswith('\\'):
49                return key, values[1:].split(',')
50        elif re.search("^[0-9-,]+$", values):
[7a2a3af]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
[f56101f]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
[7a2a3af]70def eval_options(opts):
[f56101f]71        # Find all the options with dependencies
[7a2a3af]72        dependents = [d for d in opts.values() if type(d) is DependentOpt]
[f56101f]73
74        # we need to find all the straglers
[7a2a3af]75        processed = []
[f56101f]76
77        # extract all the necessary inputs
78        input_keys = {}
[7a2a3af]79        for d in dependents:
[f56101f]80                # Mark the dependent as seen
[7a2a3af]81                processed.append(d.key)
[f56101f]82
83                # process each of the dependencies
[7a2a3af]84                for dvar in d.vars:
[f56101f]85                        # Check that it depends on something that exists
[7a2a3af]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
[f56101f]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
[7a2a3af]104                        processed.append(dvar)
105
[f56101f]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)
[7a2a3af]140
[f56101f]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))
[7a2a3af]148
[f56101f]149                # print(inner)
150                formated.append(inner)
[7a2a3af]151
[f56101f]152        return formated
[7a2a3af]153
[3e9ec44]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
[dbb1073]166def actions_eta(actions):
167        time = 0
168        for a in actions:
[3e9ec44]169                o = search_option(a, '-d')
170                if o :
171                        time += int(o)
[dbb1073]172        return time
173
[3e9ec44]174taskset_maps = None
175
176def init_taskset_maps():
177        global taskset_maps
178        known_hosts = {
179                "jax": {
[f56101f]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",
[3e9ec44]185                },
[1b97cc87]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                },
[d71db1a]192                "ocean": {
193                        range(  1,  33) : "0-31",
194                },
[3e9ec44]195        }
196
[d71db1a]197        host = socket.gethostname()
198        if host in known_hosts:
[3e9ec44]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
[7a2a3af]226if __name__ == "__main__":
227        # ================================================================================
228        # parse command line arguments
[883c4d9]229        formats = ['raw', 'csv', 'json']
[7a2a3af]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)
[af333e3]233        parser.add_argument('--trials', help='Number of trials to run per combinaison', type=int, default=3)
[3e9ec44]234        parser.add_argument('--notaskset', help='If specified, the trial will not use taskset to match the -p option', action='store_true')
[d71db1a]235        parser.add_argument('--extra', help='Extra arguments to be added unconditionally', action='append', type=str)
[7a2a3af]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:
[af333e3]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
[7a2a3af]252        except:
253                sys.exit(1)
254
255        # ================================================================================
256        # Identify the commands to run
[6f27b67]257        command = './' + options.command[0]
258        if options.candidates:
259                commands = [command + "-" + c for c in options.candidates]
260        else:
261                commands = [command]
[7a2a3af]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
[af333e3]274        opts = dict([parse_option(k, v) for k, v in options.option])
[7a2a3af]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 = []
[f56101f]282        for p in itertools.product(range(options.trials), commands, opts):
[7a2a3af]283                act = [p[1]]
284                for o in p[2:]:
285                        act.extend(o)
286                actions.append(act)
287
288        # ================================================================================
[3e9ec44]289        # Fixup the different commands
290
[d71db1a]291        # add extras
292        if options.extra:
293                for act in actions:
294                        for e in options.extra:
295                                act.append(e)
296
[3e9ec44]297        # Add tasksets
[f46b26b8]298        withtaskset = False
[3e9ec44]299        if not options.notaskset and init_taskset_maps():
[f46b26b8]300                withtaskset = True
[3e9ec44]301                actions = settaskset(actions)
302
303        # ================================================================================
304        # Now that we know what to run, print it.
[83b22b53]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
[7a2a3af]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
[dbb1073]319        random.shuffle(actions)
[7a2a3af]320
321        # ================================================================================
322        # Run
[af333e3]323        options.file.write("[")
324        first = True
[7a2a3af]325        for i, a in enumerate(actions):
[f46b26b8]326                sa = " ".join(a[3:] if withtaskset else a)
[af333e3]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 = '')
[7a2a3af]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:
[fa6233a]343                                        try:
344                                                fields[match.group(1).strip()] = float(match.group(2).strip().replace(',',''))
345                                        except:
346                                                pass
[7a2a3af]347
[f46b26b8]348                options.file.write(json.dumps([a[3 if withtaskset else 0][2:], sa, fields]))
[af333e3]349                options.file.flush()
[7a2a3af]350
[af333e3]351        options.file.write("]\n")
[7a2a3af]352
[af333e3]353        if options.file != sys.stdout:
[f56101f]354                print("Done                                                                                ")
Note: See TracBrowser for help on using the repository browser.