source: benchmark/rmit.py @ f46b26b8

Last change on this file since f46b26b8 was f46b26b8, checked in by Thierry Delisle <tdelisle@…>, 3 months ago

Fixed printing with regards to taskset

  • Property mode set to 100755
File size: 7.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 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
65def eval_options(opts):
66        dependents = [d for d in opts.values() if type(d) is DependentOpt]
67        processed = []
68        nopts = []
69        for d in dependents:
70                processed.append(d.key)
71                lists = []
72                for dvar in d.vars:
73                        if not dvar in opts.keys():
74                                print('ERROR: extra pattern option {}:{} uses unknown key {}'.format(d.key,d.value,dvar), file=sys.stderr)
75                                sys.exit(1)
76
77                        lists.append([(dvar, o) for o in opts[dvar]])
78                        processed.append(dvar)
79
80                kopt = []
81                for vals in list(itertools.product(*lists)):
82                        res = ['-{}'.format(d.key), "{}".format(eval_one(d.value, vals))]
83                        for k, v in vals:
84                                res.extend(['-{}'.format(k), "{}".format(v)])
85                        kopt.append(res)
86                nopts.append(kopt)
87
88
89        for k, vals in opts.items():
90                if k not in processed:
91                        kopt = []
92                        for v in vals:
93                                kopt.append(['-{}'.format(k), "{}".format(v)])
94                        nopts.append(kopt)
95
96        return nopts
97
98# returns the first option with key 'opt'
99def search_option(action, opt):
100        i = 0
101        while i < len(action):
102                if action[i] == opt:
103                        i += 1
104                        if i != len(action):
105                                return action[i]
106                i += 1
107
108        return None
109
110def actions_eta(actions):
111        time = 0
112        for a in actions:
113                o = search_option(a, '-d')
114                if o :
115                        time += int(o)
116        return time
117
118taskset_maps = None
119
120def init_taskset_maps():
121        global taskset_maps
122        known_hosts = {
123                "jax": {
124                        range(  1,  24) : "48-71",
125                        range( 25,  48) : "48-71,144-167",
126                        range( 49,  96) : "48-95,144-191",
127                        range( 97, 144) : "24-95,120-191",
128                        range(145, 192) : "0-95,96-191",
129                },
130        }
131
132        if (host := socket.gethostname()) in known_hosts:
133                taskset_maps = known_hosts[host]
134                return True
135
136        print("Warning unknown host '{}', disable taskset usage".format(host), file=sys.stderr)
137        return False
138
139
140def settaskset_one(action):
141        o = search_option(action, '-p')
142        if not o:
143                return action
144        try:
145                oi = int(o)
146        except ValueError:
147                return action
148
149        m = "Not found"
150        for key in taskset_maps:
151                if oi in key:
152                        return ['taskset', '-c', taskset_maps[key], *action]
153
154        print("Warning no mapping for {} cores".format(oi), file=sys.stderr)
155        return action
156
157def settaskset(actions):
158        return [settaskset_one(a) for a in actions]
159
160if __name__ == "__main__":
161        # ================================================================================
162        # parse command line arguments
163        formats = ['raw', 'csv', 'json']
164        parser = argparse.ArgumentParser(description='Python Script to implement R.M.I.T. testing : Randomized Multiple Interleaved Trials')
165        parser.add_argument('--list', help='List all the commands that would be run', action='store_true')
166        parser.add_argument('--file', nargs='?', type=argparse.FileType('w'), default=sys.stdout)
167        parser.add_argument('--trials', help='Number of trials to run per combinaison', type=int, default=3)
168        parser.add_argument('--notaskset', help='If specified, the trial will not use taskset to match the -p option', action='store_true')
169        parser.add_argument('command', metavar='command', type=str, nargs=1, help='the command prefix to run')
170        parser.add_argument('candidates', metavar='candidates', type=str, nargs='*', help='the candidate suffix to run')
171
172        try:
173                options, unknown =  parser.parse_known_args()
174
175                options.option = []
176                while unknown:
177                        key = unknown.pop(0)
178                        val = unknown.pop(0)
179
180                        if key[0] != '-':
181                                raise ValueError
182
183                        options.option.append((key[1:], val))
184
185        except:
186                print('ERROR: invalid arguments', file=sys.stderr)
187                parser.print_help(sys.stderr)
188                sys.exit(1)
189
190        # ================================================================================
191        # Identify the commands to run
192        command = './' + options.command[0]
193        if options.candidates:
194                commands = [command + "-" + c for c in options.candidates]
195        else:
196                commands = [command]
197        for c in commands:
198                if not os.path.isfile(c):
199                        print('ERROR: invalid command {}, file does not exist'.format(c), file=sys.stderr)
200                        sys.exit(1)
201
202                if not os.access(c, os.X_OK):
203                        print('ERROR: invalid command {}, file not executable'.format(c), file=sys.stderr)
204                        sys.exit(1)
205
206
207        # ================================================================================
208        # Identify the options to run
209        opts = dict([parse_option(k, v) for k, v in options.option])
210
211        # Evaluate the options (options can depend on the value of other options)
212        opts = eval_options(opts)
213
214        # ================================================================================
215        # Figure out all the combinations to run
216        actions = []
217        for p in itertools.product(range(options.trials), commands, *opts):
218                act = [p[1]]
219                for o in p[2:]:
220                        act.extend(o)
221                actions.append(act)
222
223        # ================================================================================
224        # Fixup the different commands
225
226        # Add tasksets
227        withtaskset = False
228        if not options.notaskset and init_taskset_maps():
229                withtaskset = True
230                actions = settaskset(actions)
231
232        # ================================================================================
233        # Now that we know what to run, print it.
234        if options.list:
235                for a in actions:
236                        print(" ".join(a))
237                sys.exit(0)
238
239
240        # ================================================================================
241        # Prepare to run
242
243        # find expected time
244        time = actions_eta(actions)
245        print("Running {} trials{}".format(len(actions), "" if time == 0 else " (expecting to take {})".format(str(datetime.timedelta(seconds=int(time)))) ))
246
247        random.shuffle(actions)
248
249        # ================================================================================
250        # Run
251        options.file.write("[")
252        first = True
253        for i, a in enumerate(actions):
254                sa = " ".join(a[3:] if withtaskset else a)
255                if first:
256                        first = False
257                else:
258                        options.file.write(",")
259                if options.file != sys.stdout:
260                        print("{}/{} : {}          \r".format(i, len(actions), sa), end = '')
261                fields = {}
262                with subprocess.Popen( a, stdout  = subprocess.PIPE, stderr  = subprocess.PIPE) as proc:
263                        out, err = proc.communicate()
264                        if proc.returncode != 0:
265                                print("ERROR: command '{}' encountered error, returned code {}".format(sa, proc.returncode), file=sys.stderr)
266                                print(err.decode("utf-8"))
267                                sys.exit(1)
268                        for s in out.decode("utf-8").splitlines():
269                                match = re.search("^(.*):(.*)$", s)
270                                if match:
271                                        try:
272                                                fields[match.group(1).strip()] = float(match.group(2).strip().replace(',',''))
273                                        except:
274                                                pass
275
276                options.file.write(json.dumps([a[3 if withtaskset else 0][2:], sa, fields]))
277                options.file.flush()
278
279        options.file.write("]\n")
280
281        if options.file != sys.stdout:
282                print("Done                                                                                ")
Note: See TracBrowser for help on using the repository browser.