3 # Find the directory that contains the grading files.
6 # Add our Perl library directory to the include path.
8 ($GRADES_DIR = $0) =~ s#/[^/]+$##;
9 -d $GRADES_DIR or die "$GRADES_DIR: stat: $!\n";
10 unshift @INC, "$GRADES_DIR/../lib";
17 our ($hw) = "threads";
18 our (@TESTS); # Tests to run.
26 # Default set of tests.
27 @TESTS = ("alarm-single", "alarm-multiple", "alarm-zero", "alarm-negative",
29 "join-quick", "join-multiple", "join-nested",
30 "join-dummy", "join-invalid", "join-no",
31 "priority-preempt", "priority-fifo", "priority-donate-one",
32 "priority-donate-multiple", "priority-donate-nest",
33 "mlfqs-on", "mlfqs-off")
36 clean_dir (), exit if $action eq 'clean';
39 exit if $action eq 'extract';
42 exit if $action eq 'build';
44 run_and_grade_tests ();
45 grade_mlfqs_speedup ();
46 grade_mlfqs_priority ();
49 exit if $action eq 'test';
51 assemble_final_grade ();
52 exit if $action eq 'assemble';
54 die "Don't know how to '$action'";
56 # Runs $test in directory output/$test.
57 # Returns 'ok' if it went ok, otherwise an explanation.
59 # Change constants.h if necessary.
60 my ($defines) = $test ne 'mlfqs-on' ? "" : "#define MLFQS 1\n";
61 if ($defines ne snarf ("pintos/src/constants.h")) {
62 open (CONSTANTS, ">pintos/src/constants.h");
63 print CONSTANTS $defines;
67 # Changes devices/timer.c if necessary.
68 my ($new_time_slice) = $test eq 'priority-fifo' ? 100 : 1;
69 my (@timer) = snarf ("pintos/src/devices/timer.c");
70 if (!grep (/^\#define TIME_SLICE $new_time_slice$/, @timer)) {
71 @timer = grep (!/^\#define TIME_SLICE/, @timer);
72 unshift (@timer, "#define TIME_SLICE $new_time_slice");
73 open (TIMER, ">pintos/src/devices/timer.c");
74 print TIMER map ("$_\n", @timer);
78 # Copy in the new test.c and delete enough files to ensure a full rebuild.
79 my ($src) = "$GRADES_DIR/" . ($test !~ /^mlfqs/ ? "$test.c" : "mlfqs.c");
80 -e $src or die "$src: stat: $!\n";
81 xsystem ("cp $src pintos/src/threads/test.c", DIE => "cp failed\n");
82 unlink ("pintos/src/threads/build/threads/test.o");
83 unlink ("pintos/src/threads/build/kernel.o");
84 unlink ("pintos/src/threads/build/kernel.bin");
85 unlink ("pintos/src/threads/build/os.dsk");
88 if (xsystem ("cd pintos/src/threads && make",
89 LOG => "$test/make") ne 'ok') {
90 $details{$test} = snarf ("output/$test/make.err");
91 return "Compile error";
94 # Copy out files for backtraces later.
95 xsystem ("cp pintos/src/threads/build/kernel.o output/$test");
96 xsystem ("cp pintos/src/threads/build/os.dsk output/$test");
99 my ($timeout) = $test !~ /^mlfqs/ ? 10 : 600;
100 return run_pintos ("cd pintos/src/threads/build && pintos -v run -q",
102 TIMEOUT => $timeout);
105 sub grade_alarm_single {
106 verify_alarm (1, @_);
109 sub grade_alarm_multiple {
110 verify_alarm (7, @_);
114 my ($iterations, @output) = @_;
116 verify_common (@output);
119 for (my ($i) = 0; $i < $iterations; $i++) {
120 for (my ($t) = 0; $t < 5; $t++) {
121 push (@products, ($i + 1) * ($t + 1) * 10);
124 @products = sort {$a <=> $b} @products;
128 die $_ if /Out of order/;
130 my ($p) = /product=(\d+)$/;
133 my ($q) = shift (@products);
134 die "Too many wakeups.\n" if !defined $q;
135 die "Out of order wakeups ($p vs. $q).\n" if $p != $q; # FIXME
137 die scalar (@products) . " fewer wakeups than expected.\n"
141 sub grade_alarm_zero {
143 verify_common (@output);
144 die "Crashed in timer_sleep()\n" if !grep (/^Success\.$/, @output);
147 sub grade_alarm_negative {
149 verify_common (@output);
150 die "Crashed in timer_sleep()\n" if !grep (/^Success\.$/, @output);
153 sub grade_join_invalid {
155 verify_common (@output);
156 grep (/Testing invalid join/, @output) or die "Test didn't start\n";
157 grep (/Invalid join test done/, @output) or die "Test didn't complete\n";
162 verify_common (@output);
163 grep (/Testing no join/, @output) or die "Test didn't start\n";
164 grep (/No join test done/, @output) or die "Test didn't complete\n";
167 sub grade_join_multiple {
170 verify_common (@output);
172 $t[4] = $t[5] = $t[6] = -1;
175 my ($idx) = /^Thread (\d+)/ or next;
176 my ($iter) = /iteration (\d+)$/;
177 $iter = 5 if /done!$/;
178 die "Malformed output\n" if !defined $iter;
180 die "Thread 6 started before either other thread finished\n"
181 if $t[4] < 5 && $t[5] < 5;
182 die "Thread 6 started before thread 4 finished\n"
184 die "Thread 6 started before thread 5 finished\n"
187 die "Thread $idx out of order output\n" if $t[$idx] != $iter - 1;
192 for my $idx (4, 5, 6) {
193 if ($t[$idx] == -1) {
194 $err .= "Thread $idx did not run at all\n";
195 } elsif ($t[$idx] != 5) {
196 $err .= "Thread $idx only completed $t[$idx] iterations\n";
199 die $err if $err ne '';
202 sub grade_priority_fifo {
205 verify_common (@output);
206 my ($thread_cnt) = 10;
209 my (@t) = (-1) x $thread_cnt;
212 my ($idx) = /^Thread (\d+)/ or next;
213 my ($iter) = /iteration (\d+)$/;
214 $iter = $iter_cnt if /done!$/;
215 die "Malformed output\n" if !defined $iter;
216 if (@order < $thread_cnt) {
218 die "Thread $idx repeated within first $thread_cnt iterations: "
219 . join (' ', @order) . ".\n"
220 if grep ($_ == $idx, @order) != 1;
222 die "Thread $idx ran when $order[0] should have.\n"
223 if $idx != $order[0];
224 push (@order, shift @order);
226 die "Thread $idx out of order output.\n" if $t[$idx] != $iter - 1;
231 for my $idx (0..$#t) {
232 if ($t[$idx] == -1) {
233 $err .= "Thread $idx did not run at all.\n";
234 } elsif ($t[$idx] != $iter_cnt) {
235 $err .= "Thread $idx only completed $t[$idx] iterations.\n";
238 die $err if $err ne '';
243 verify_common (@output);
244 our (@mlfqs_on_stats) = mlfqs_stats (@output);
247 sub grade_mlfqs_off {
249 verify_common (@output);
250 our (@mlfqs_off_stats) = mlfqs_stats (@output);
253 sub grade_mlfqs_speedup {
254 our (@mlfqs_off_stats);
255 our (@mlfqs_on_stats);
258 my ($off_ticks) = $mlfqs_off_stats[1];
259 my ($on_ticks) = $mlfqs_on_stats[1];
260 die "$off_ticks ticks without MLFQS, $on_ticks with MLFQS\n"
261 if $on_ticks >= $off_ticks;
265 $result{'mlfqs-speedup'} = $@;
268 sub grade_mlfqs_priority {
269 our (@mlfqs_off_stats);
270 our (@mlfqs_on_stats);
272 check_mlfqs () if !defined (@mlfqs_on_stats);
273 for my $cat qw (CPU IO MIX) {
274 die "Priority changed away from PRI_DEFAULT (29) without MLFQS\n"
275 if $mlfqs_off_stats[0]{$cat}{MIN} != 29
276 || $mlfqs_off_stats[0]{$cat}{MAX} != 29;
277 die "Minimum priority never changed from PRI_DEFAULT (29) "
279 if $mlfqs_on_stats[0]{$cat}{MIN} == 29;
280 die "Maximum priority never changed from PRI_DEFAULT (29) "
282 if $mlfqs_on_stats[0]{$cat}{MAX} == 29;
287 $result{'mlfqs-priority'} = $@;
291 our (@mlfqs_off_stats);
292 our (@mlfqs_on_stats);
293 die "p1-4 didn't finish with MLFQS on or off\n"
294 if !defined (@mlfqs_off_stats) && !defined (@mlfqs_on_stats);
295 die "p1-4 didn't finish with MLFQS on\n"
296 if !defined (@mlfqs_on_stats);
297 die "p1-4 didn't finish with MLFQS off\n"
298 if !defined (@mlfqs_off_stats);
303 my (%stats) = (CPU => {}, IO => {}, MIX => {});
304 my (%map) = ("CPU intensive" => 'CPU',
305 "IO intensive" => 'IO',
306 "Alternating IO/CPU" => 'MIX');
307 my (%rmap) = reverse %map;
311 $ticks = $1 if /Timer: (\d+) ticks/;
312 my ($thread, $pri) = /^([A-Za-z\/ ]+): (\d+)$/ or next;
313 my ($t) = $map{$thread} or next;
315 my ($s) = $stats{$t};
318 $$s{SUM2} += $pri * $pri;
319 $$s{MIN} = $pri if !defined ($$s{MIN}) || $pri < $$s{MIN};
320 $$s{MAX} = $pri if !defined ($$s{MAX}) || $pri > $$s{MAX};
323 my (%expect_n) = (CPU => 5000, IO => 1000, MIX => 12000);
324 for my $cat (values (%map)) {
325 my ($s) = $stats{$cat};
326 die "$rmap{$cat} printed $$s{N} times, not $expect_n{$cat}\n"
327 if $$s{N} != $expect_n{$cat};
328 die "$rmap{$cat} priority dropped to $$s{MIN}, below PRI_MIN (0)\n"
330 die "$rmap{$cat} priority rose to $$s{MAX}, above PRI_MAX (59)\n"
332 $$s{MEAN} = $$s{SUM} / $$s{N};
335 return (\%stats, $ticks);