source: benchmark/rmit.py @ 160ee4c

ADTast-experimentalpthread-emulationqualifiedEnum
Last change on this file since 160ee4c 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
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):
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
[af333e3]41def parse_option(key, values):
[7a2a3af]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
[f56101f]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
[7a2a3af]68def eval_options(opts):
[f56101f]69        # Find all the options with dependencies
[7a2a3af]70        dependents = [d for d in opts.values() if type(d) is DependentOpt]
[f56101f]71
72        # we need to find all the straglers
[7a2a3af]73        processed = []
[f56101f]74
75        # extract all the necessary inputs
76        input_keys = {}
[7a2a3af]77        for d in dependents:
[f56101f]78                # Mark the dependent as seen
[7a2a3af]79                processed.append(d.key)
[f56101f]80
81                # process each of the dependencies
[7a2a3af]82                for dvar in d.vars:
[f56101f]83                        # Check that it depends on something that exists
[7a2a3af]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
[f56101f]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
[7a2a3af]102                        processed.append(dvar)
103
[f56101f]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)
[7a2a3af]138
[f56101f]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))
[7a2a3af]146
[f56101f]147                # print(inner)
148                formated.append(inner)
[7a2a3af]149
[f56101f]150        return formated
[7a2a3af]151
[3e9ec44]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
[dbb1073]164def actions_eta(actions):
165        time = 0
166        for a in actions:
[3e9ec44]167                o = search_option(a, '-d')
168                if o :
169                        time += int(o)
[dbb1073]170        return time
171
[3e9ec44]172taskset_maps = None
173
174def init_taskset_maps():
175        global taskset_maps
176        known_hosts = {
177                "jax": {
[f56101f]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",
[3e9ec44]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
[7a2a3af]214if __name__ == "__main__":
215        # ================================================================================
216        # parse command line arguments
[883c4d9]217        formats = ['raw', 'csv', 'json']
[7a2a3af]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)
[af333e3]221        parser.add_argument('--trials', help='Number of trials to run per combinaison', type=int, default=3)
[3e9ec44]222        parser.add_argument('--notaskset', help='If specified, the trial will not use taskset to match the -p option', action='store_true')
[7a2a3af]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:
[af333e3]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
[7a2a3af]239        except:
240                sys.exit(1)
241
242        # ================================================================================
243        # Identify the commands to run
[6f27b67]244        command = './' + options.command[0]
245        if options.candidates:
246                commands = [command + "-" + c for c in options.candidates]
247        else:
248                commands = [command]
[7a2a3af]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
[af333e3]261        opts = dict([parse_option(k, v) for k, v in options.option])
[7a2a3af]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 = []
[f56101f]269        for p in itertools.product(range(options.trials), commands, opts):
[7a2a3af]270                act = [p[1]]
271                for o in p[2:]:
272                        act.extend(o)
273                actions.append(act)
274
275        # ================================================================================
[3e9ec44]276        # Fixup the different commands
277
278        # Add tasksets
[f46b26b8]279        withtaskset = False
[3e9ec44]280        if not options.notaskset and init_taskset_maps():
[f46b26b8]281                withtaskset = True
[3e9ec44]282                actions = settaskset(actions)
283
284        # ================================================================================
285        # Now that we know what to run, print it.
[83b22b53]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
[7a2a3af]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
[dbb1073]300        random.shuffle(actions)
[7a2a3af]301
302        # ================================================================================
303        # Run
[af333e3]304        options.file.write("[")
305        first = True
[7a2a3af]306        for i, a in enumerate(actions):
[f46b26b8]307                sa = " ".join(a[3:] if withtaskset else a)
[af333e3]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 = '')
[7a2a3af]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:
[fa6233a]324                                        try:
325                                                fields[match.group(1).strip()] = float(match.group(2).strip().replace(',',''))
326                                        except:
327                                                pass
[7a2a3af]328
[f46b26b8]329                options.file.write(json.dumps([a[3 if withtaskset else 0][2:], sa, fields]))
[af333e3]330                options.file.flush()
[7a2a3af]331
[af333e3]332        options.file.write("]\n")
[7a2a3af]333
[af333e3]334        if options.file != sys.stdout:
[f56101f]335                print("Done                                                                                ")
Note: See TracBrowser for help on using the repository browser.