| 141 |
Kevin |
1 |
#!/usr/bin/python
|
|
|
2 |
#
|
|
|
3 |
# Check that build and output of threadpool exercise match expected output
|
|
|
4 |
#
|
|
|
5 |
# Written for CS3214 Fall 2009 by G. Back (godmar@gmail.com)
|
|
|
6 |
# Last Updated Fall 2011
|
|
|
7 |
#
|
|
|
8 |
|
|
|
9 |
import re, os, sys, subprocess, operator, signal
|
|
|
10 |
|
|
|
11 |
exe = "./threadpool_test"
|
|
|
12 |
helgrind = ["/home/courses/cs3214/valgrind-3.7.0-install/bin/valgrind", "--tool=helgrind"]
|
|
|
13 |
|
|
|
14 |
print "Checking correctness of threadpool exercise."
|
|
|
15 |
print "Compiling..."
|
|
|
16 |
|
|
|
17 |
if os.system("make clean " + exe):
|
|
|
18 |
raise Exception("make failed, run 'make clean " + exe + "' to see why")
|
|
|
19 |
|
|
|
20 |
if not os.access(exe, os.X_OK):
|
|
|
21 |
raise Exception(exe + " did not build")
|
|
|
22 |
|
|
|
23 |
print "Ok."
|
|
|
24 |
|
|
|
25 |
hex = "[0-9A-Fa-f]{8,16}"
|
|
|
26 |
print "Checking that threadpool.o does not use global variables...",
|
|
|
27 |
allowedsymbols = [ "future_free", "future_get", \
|
|
|
28 |
"thread_pool_new", "thread_pool_shutdown",
|
|
|
29 |
"thread_pool_submit" ]
|
|
|
30 |
|
|
|
31 |
symbols = subprocess.Popen(["nm", "threadpool.o"], stdout=subprocess.PIPE)\
|
|
|
32 |
.communicate()[0].split("\n")
|
|
|
33 |
|
|
|
34 |
for sym in symbols:
|
|
|
35 |
if sym == "" or re.match("\s+U (\S+)", sym):
|
|
|
36 |
continue
|
|
|
37 |
|
|
|
38 |
m = re.match(hex + " (\S) (\S+)", sym)
|
|
|
39 |
if not m:
|
|
|
40 |
raise Exception("unexpected line in nm:\n" + sym)
|
|
|
41 |
|
|
|
42 |
if m.group(1) == "t":
|
|
|
43 |
continue
|
|
|
44 |
# ignore symbols produced by 'assert()'
|
|
|
45 |
if m.group(1) == "r" and m.group(2).startswith("__PRETTY_FUNCTION__"):
|
|
|
46 |
continue
|
|
|
47 |
if m.group(1) == "T":
|
|
|
48 |
if m.group(2) in allowedsymbols:
|
|
|
49 |
continue
|
|
|
50 |
|
|
|
51 |
raise Exception("threadpool.o defines global function '" + m.group(2)
|
|
|
52 |
+ "'\nallowed functions are: " + str(allowedsymbols));
|
|
|
53 |
|
|
|
54 |
raise Exception("threadpool.o must not define any global or "
|
|
|
55 |
+ "static variables, but you define: " + m.group(2))
|
|
|
56 |
|
|
|
57 |
print "Ok."
|
|
|
58 |
|
|
|
59 |
# test scenarios (#threads, #tasks)
|
|
|
60 |
threads2tasks = [ (4, 4), (4, 2), (4, 1), \
|
|
|
61 |
(4, 8), (4, 12), \
|
|
|
62 |
(2, 2), (2, 4), (2, 8), \
|
|
|
63 |
(1, 2), (1, 4), (1, 8), \
|
|
|
64 |
(3, 1), (3, 2), (3, 3), (3, 4), (3, 5) \
|
|
|
65 |
]
|
|
|
66 |
|
|
|
67 |
for (threads, tasks) in threads2tasks:
|
|
|
68 |
print "Testing", threads, "threads and", tasks, "tasks"
|
|
|
69 |
sp = subprocess.Popen([exe, str(threads), str(tasks)],
|
|
|
70 |
stdout=subprocess.PIPE)
|
|
|
71 |
output = sp.communicate()[0]
|
|
|
72 |
|
|
|
73 |
if sp.returncode < 0:
|
|
|
74 |
signames = dict((k, v) for v, k in signal.__dict__.iteritems() if v.startswith('SIG'))
|
|
|
75 |
signum = -sp.returncode
|
|
|
76 |
raise Exception("Program terminated with signal %d (%s)\n" % (signum, signames[signum]))
|
|
|
77 |
|
|
|
78 |
match = re.match("Main thread: 0x("+hex+")\n(.*)\n"\
|
|
|
79 |
"Done\.", output, re.MULTILINE | re.DOTALL)
|
|
|
80 |
if not match:
|
|
|
81 |
raise Exception("Output did not match expected format:\n" + output)
|
|
|
82 |
|
|
|
83 |
mainthread = match.group(1)
|
|
|
84 |
futures = []
|
|
|
85 |
for future in match.group(2).split("\n"):
|
|
|
86 |
m = re.match("Future #(\d+) Thread #0x("+hex+") "\
|
|
|
87 |
"start=(\d+)\.(\d+) end=(\d+)\.(\d+)", future)
|
|
|
88 |
if not m:
|
|
|
89 |
raise Exception("Future did not match expected format:\n" + future)
|
|
|
90 |
|
|
|
91 |
futures.append((m.group(1), m.group(2),
|
|
|
92 |
float(m.group(3)) + float(m.group(4)) / 1e6,
|
|
|
93 |
float(m.group(5)) + float(m.group(6)) / 1e6))
|
|
|
94 |
|
|
|
95 |
# print futures
|
|
|
96 |
|
|
|
97 |
# consistency checks
|
|
|
98 |
# Futures should be printed in increasing order
|
|
|
99 |
print "Checking correct order of futures...",
|
|
|
100 |
|
|
|
101 |
# all is available only in Python 2.5 and up
|
|
|
102 |
def all(l):
|
|
|
103 |
return reduce(lambda x, y: x and y, l, True)
|
|
|
104 |
if not all([ int(futures[i][0]) < int(futures[i+1][0]) \
|
|
|
105 |
for i in range(len(futures)-1) ]):
|
|
|
106 |
raise Exception("Futures are out of order:\n" + output)
|
|
|
107 |
|
|
|
108 |
print "Ok."
|
|
|
109 |
|
|
|
110 |
print "Checking that correct number of threads were used...",
|
|
|
111 |
threadsused = len(set([ f[1] for f in futures ] + [ mainthread ]))
|
|
|
112 |
threadsexpected = min(threads, tasks) + 1
|
|
|
113 |
if threadsexpected != threadsused:
|
|
|
114 |
raise Exception("Thread pool used " + str(threadsused)
|
|
|
115 |
+ " distinct threads including the main thread, should have used "
|
|
|
116 |
+ str(threadsexpected) + ":\n" + output)
|
|
|
117 |
|
|
|
118 |
print "Ok."
|
|
|
119 |
|
|
|
120 |
# each pair within each 'threads'-sized chunk of tasks
|
|
|
121 |
# should have executed in parallel.
|
|
|
122 |
print "Checking tasks were executed in parallel...",
|
|
|
123 |
def inparallel((start0, end0), (start1, end1)):
|
|
|
124 |
return start1 < end0 and start0 < end1
|
|
|
125 |
|
|
|
126 |
def allpairs(l):
|
|
|
127 |
return reduce(operator.add, [ [(l[i], e) for e in l[i+1:]] \
|
|
|
128 |
for i in range(len(l)-1) ], [])
|
|
|
129 |
|
|
|
130 |
for i in range(0, len(futures), threads):
|
|
|
131 |
for (f1, f2) in allpairs(futures[i:i+threads]):
|
|
|
132 |
if not inparallel(f1[2:4], f2[2:4]):
|
|
|
133 |
raise Exception("Future #" + f1[0] + " and #" + f2[0]
|
|
|
134 |
+ " did not run in parallel:\n" + output)
|
|
|
135 |
|
|
|
136 |
print "Ok."
|
|
|
137 |
|
|
|
138 |
print "You have met minimum requirements.\n"
|
|
|
139 |
|
|
|
140 |
print "Now checking for race conditions using helgrind"
|
|
|
141 |
for (threads, tasks) in threads2tasks:
|
|
|
142 |
cmd = helgrind + [exe, str(threads), str(tasks)]
|
|
|
143 |
print "Running", " ".join(cmd), "...",
|
|
|
144 |
sp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
145 |
stderr = sp.communicate()[1]
|
|
|
146 |
if not re.search("ERROR SUMMARY: 0 errors from 0 contexts", stderr):
|
|
|
147 |
raise Exception("Your program contains race conditions:\n" + stderr)
|
|
|
148 |
|
|
|
149 |
print "Ok."
|
|
|
150 |
|
|
|
151 |
print "Congratulations, you've passed the race condition checker tests.\n"
|
|
|
152 |
|
|
|
153 |
print "Testing whether your thread pool implementation runs Quicksort"
|
|
|
154 |
quicksort = "quicksort"
|
|
|
155 |
if os.system("make clean " + quicksort):
|
|
|
156 |
print "make failed, run 'make clean " + quicksort + "' to see why"
|
|
|
157 |
sys.exit(1)
|
|
|
158 |
|
|
|
159 |
sp = subprocess.Popen("./quicksort -q -s 1 1000000".split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
160 |
stdout = sp.communicate()[0]
|
|
|
161 |
if not re.search("Processed 14 segments of sizes: 6316 19753 62328 62520 68838 82083 104576 141938 152189 256767 325607 450365 592305 674390", stdout):
|
|
|
162 |
print "Quicksort did not produce the expected result."
|
|
|
163 |
else:
|
|
|
164 |
print "Quicksort appears to work, you can now start measuring parallel speedup."
|
|
|
165 |
|