chiark / gitweb /
more stuff found lying about
[wordchain] / get-timing-data
1 #! /usr/bin/python3
2
3 import bisect as BS
4 import curses as CU
5 import optparse as OP
6 import os as OS
7 import random as RND
8 import resource as RSC
9 import subprocess as SUB
10 import sys as SYS
11
12 op = OP.OptionParser\
13        (usage = "%prog [-t] [-d FILE] [-j JOBS] [-n SAMPLES] [-r REF] VAR ...")
14 op.disable_interspersed_args()
15 for short, long, kw in \
16     [("-d", "--dictionary",
17       dict(type = "string", metavar = "DICT",
18            dest = "dict", default = "/usr/share/dict/words",
19            help = "word list to process")),
20      ("-j", "--jobs",
21       dict(type = "int", metavar = "JOBS",
22            dest = "njobs", default = 1,
23            help = "number of jobs to run concurrently")),
24      ("-n", "--samples",
25       dict(type = "int", metavar = "SAMPLES",
26            dest = "nsamples", default = 5,
27            help = "number of samples for each variant")),
28      ("-r", "--reference",
29       dict(type = "string", metavar = "REF",
30            dest = "ref", default = None,
31            help = "reference output data expected")),
32      ("-t", "--terminal",
33       dict(action = "store_true", dest = "terminal", default = None,
34            help = "show progress display"))]:
35   op.add_option(short, long, **kw)
36 OPTS, ARGS = op.parse_args()
37
38 REF = OPTS.ref
39 if REF is None:
40   REF = "REF.%s" % OS.path.basename(OPTS.dict)
41   if not OS.path.exists(REF): REF = None
42
43 IDLE = 0
44 LIVE = 1
45 DONE = 2
46
47 class BasicVariant (object):
48
49   def __init__(me, name, row):
50     me.name = name
51     me.samples = []
52     me.row = row
53     me._prog = "./chain.%s" % name
54     me.state = IDLE
55
56   @property
57   def n(me):
58     return len(me.samples)
59
60   def start(me, out):
61     SYS.stdin.close()
62     fd = OS.open(OPTS.dict, OS.O_RDONLY)
63     if fd != 0: OS.dup2(fd, 0); OS.close(fd)
64     SYS.stdout.close()
65     fd = OS.open(out, OS.O_WRONLY | OS.O_CREAT | OS.O_TRUNC, 0o666)
66     if fd != 1: OS.dup2(fd, 1); OS.close(fd)
67     OS.execv(me._prog, [me._prog])
68
69   def end(me, time):
70     me.samples.append(time)
71     return time
72
73 class LispVariant (BasicVariant):
74   def __init__(me, name, *args, **kw):
75     super(LispVariant, me).__init__(name, *args, **kw)
76     me._time = "time.%s" % name
77     me._lisp = name[5:]
78     me._prog = "chain.lisp"
79
80   def start(me, out):
81     SYS.stdout.close()
82     fd = OS.open(out, OS.O_WRONLY | OS.O_CREAT | OS.O_TRUNC, 0o666)
83     if fd != 1: OS.dup2(fd, 1); OS.close(fd)
84     OS.execvp("runlisp", ["runlisp", "-L%s" % me._lisp,
85                           me._prog, "-T%s" % me._time, OPTS.dict])
86
87   def end(me, time):
88     #with open(me._time) as f: time = 1000*float(f.read())
89     OS.unlink(me._time)
90     me.samples.append(time)
91     return time
92
93 def strwait(st):
94   if OS.WIFEXITED(st): return "rc = %d" % OS.WEXITSTATUS(st)
95   elif OS.WIFSIGNALED(st): return "signal = %d" % OS.WTERMSIG(st)
96   else: return "?st = %d" % st
97
98 class State (object):
99
100   def __init__(me):
101     me.vars = []
102     me.idle = []
103     me.done = []
104     me.live = {}
105     me.bad = False
106
107     me._nextrow = 0
108     me._dpy = None
109
110   def _setdpy(me, dpy):
111     me._dpy = dpy
112
113   def addvar(me, var):
114     if arg.startswith("lisp-"): var = LispVariant(arg, me._nextrow)
115     else: var = BasicVariant(arg, me._nextrow)
116     me.vars.append(var); me.idle.append(var); me._nextrow += 1
117
118   def cycle(me):
119     if me.idle and not me.bad and len(me.live) < OPTS.njobs:
120       n = len(me.idle)
121       k = RND.randrange(n); pick = me.idle[k]; assert pick.state == IDLE
122       me.idle[k] = me.idle[n - 1]; me.idle.pop()
123       kid = OS.fork()
124       if not kid: pick.start("chain.%s.out" % pick.name)
125       #me._dpy.msg(";; start %s\n" % pick.name)
126       me.live[kid] = pick; pick.state = LIVE
127       me._dpy.paint_var(pick)
128       return True
129     elif me.live:
130       kid, st, ru = OS.wait3(0)
131       try: var = me.live.pop(kid)
132       except KeyError:
133         me._dpy.msg("ignoring unexpected child %d (%s)" %
134                     (kid, strwait(st)))
135         return True
136       assert var.state == LIVE
137       if not (OS.WIFEXITED(st) and OS.WEXITSTATUS(st) == 0):
138         me._dpy.msg("chain.%s failed (%s)" % (var.name, strwait(st)))
139         me.bad = True
140         return True
141       out = "chain.%s.out" % var.name
142       if REF is not None:
143         if SUB.call(["./chkref", out, REF]):
144           me._dpy.msg("chain.%s produced incorrect output" % var.name)
145           me.bad = True
146           return True
147       OS.unlink(out)
148       time = var.end(1000*(ru.ru_utime + ru.ru_stime))
149       #me._dpy.msg(";; end %s, time = %.3f s" % (var.name, time))
150       if var.n < OPTS.nsamples:
151         me.idle.append(var); var.state = IDLE
152       else:
153         me.done.append(var); var.state = DONE
154         #me._dpy.msg(";; done %s\n" % var.name)
155       me._dpy.paint_var(var)
156       return True
157     else:
158       return False
159
160 if OPTS.terminal or SYS.stdout.isatty():
161
162   class Display (object):
163
164     def __init__(me, state):
165       me._st = state
166       me._st._setdpy(me)
167       namewd = 16
168       for var in me._st.vars:
169         if len(var.name) >= namewd: namewd = len(var.name) + 1
170       me._a_idle = CU.A_NORMAL
171       me._a_live = CU.A_BOLD
172       me._namewd = namewd
173       me._msgs = []
174
175     def __enter__(me):
176
177       if not SYS.stdout.isatty():
178         ttyfd = OS.open("/dev/tty", OS.O_RDWR)
179         SYS.stdout = OS.fdopen(OS.dup(1), "w")
180         OS.dup2(ttyfd, 1)
181         OS.close(ttyfd)
182
183       me._scr = CU.initscr()
184       me._resize()
185       CU.start_color()
186       CU.use_default_colors()
187       if CU.COLORS and CU.COLOR_PAIRS >= 3:
188         CU.init_pair(1, CU.COLOR_GREEN, -1)
189         me._a_done = CU.color_pair(1)
190         CU.init_pair(2, CU.COLOR_BLACK, CU.COLOR_GREEN)
191         CU.init_pair(3, CU.COLOR_BLACK, CU.COLOR_YELLOW)
192         me._a_bar_left = CU.color_pair(2)
193         me._a_bar_right = CU.color_pair(3)
194       else:
195         me._a_live = CU.A_DIM
196         me._a_bar_left = CU.A_REVERSE
197         me._a_bar_right = None
198       me._msgrow = min(len(me._st.vars) + 1, me._ht - 1)
199       me._nmsgs = me._ht - me._msgrow
200       me._scr.setscrreg(me._msgrow, me._ht - 1)
201       me._scr.scrollok(True)
202       me._repaint()
203
204     def __exit__(me, exval, exty, tb):
205       CU.endwin()
206       for m in me._msgs: SYS.stderr.write(m + "\n")
207
208     def _resize(me):
209       me._ht, me._wd = me._scr.getmaxyx()
210       barwd = me._wd - me._namewd
211       if OPTS.nsamples <= barwd: me._barsc = 1
212       else: me._barsc = float(barwd)/OPTS.nsamples
213
214     def paint_var(me, var, refresh = True):
215       if var.row >= me._ht - 2: return
216       rbar = int(me._barsc*OPTS.nsamples + 0.5)
217       if var.state == LIVE: attr = me._a_live
218       elif var.state == DONE: attr = me._a_done
219       else: attr = me._a_idle
220       me._scr.addstr(var.row, 0, var.name, attr)
221       mbar = int(me._barsc*len(var.samples))
222       me._scr.attrset(me._a_bar_left)
223       me._scr.hline(var.row, me._namewd, " ", mbar)
224       if me._a_bar_right is not None:
225         me._scr.attrset(me._a_bar_right)
226         me._scr.hline(var.row, me._namewd + mbar, " ", rbar - mbar)
227         me._scr.move(var.row, me._namewd + rbar)
228       else:
229         me._scr.move(var.row, me._namewd + mbar)
230       me._scr.attrset(0)
231       if var.samples:
232         avg = "%.3f ms" % (sum(var.samples)/len(var.samples))
233         if me._wd - (me._namewd + rbar) > len(avg):
234           me._scr.addstr(var.row, me._namewd + rbar + 1, avg)
235       me._scr.clrtoeol()
236       if refresh: me._scr.refresh()
237
238     def msg(me, msg):
239       n = len(me._msgs)
240       if n >= me._nmsgs:
241         me._scr.scroll(1)
242       me._scr.addstr(min(me._msgrow + n, me._ht - 1), 0, msg)
243       me._msgs.append(msg)
244       me._scr.refresh()
245
246     def _repaint(me):
247       me._scr.erase()
248       for var in me._st.vars: me.paint_var(var, refresh = False)
249       n = len(me._msgs); y = me._msgrow
250       for i in range(max(0, n - me._nmsgs), n):
251         me._scr.addstr(y, 0, me._msg[i]); y += 1
252       me._scr.refresh()
253
254 else:
255
256   class Display (object):
257
258     def __init__(me, state):
259       me._st = state
260       me._st._setdpy(me)
261
262     def __enter__(me):
263       pass
264     def __exit__(me, exval, exty, tb):
265       pass
266
267     def paint_var(me, var):
268       if var.state == IDLE:
269         SYS.stderr.write(";; end %s, time = %.3f ms\n" %
270                          (var.name, var.samples[-1]))
271       elif var.state == LIVE:
272         SYS.stderr.write(";; start %s\n" % var.name)
273       elif var.state == DONE:
274         SYS.stderr.write(";; %s all done\n" % var.name)
275
276     def msg(me, msg):
277       SYS.stderr.write("%s\n" % msg)
278
279 RND.seed()
280 STATE = State()
281 for arg in ARGS: STATE.addvar(arg)
282 with Display(STATE):
283   while STATE.cycle(): pass
284 if STATE.bad: SYS.exit(2)
285
286 def quartile(i, x):
287   n = len(x)
288   k = i*n
289   j = k//4
290   if not k%4: return x[j]
291   else: return (x[j] + x[j + 1])/2
292
293 def stats(x):
294   q1, mid, q3 = quartile(1, x), quartile(2, x), quartile(3, x)
295   iqr = q3 - q1
296   locut = BS.bisect_left(x, q1 - 1.5*iqr); lo = x[locut]
297   hicut = BS.bisect_right(x, q3 + 1.5*iqr); hi = x[hicut - 1]
298   avg = sum(x)/len(x)
299   return lo, q1, mid, avg, q3, hi
300
301 done = STATE.vars
302 done.sort(key = lambda var: var.name)
303 n = OPTS.nsamples - 1
304 for var in done:
305   x = [xi for xi in var.samples]; x.sort()
306   lo, q1, mid, avg, q3, hi = stats(x)
307   print("%-16s %s; %s" %
308         (var.name,
309          " ".join("%.3f" % xi for xi in var.samples),
310          " ".join("%.3f" % xi for xi in [lo, q1, mid, avg, q3, hi])))