Use TIMER_FREQ of 19 for grading priority-fifo.
[pintos-anon] / grading / threads / run-tests
1 #! /usr/bin/perl
2
3 # Find the directory that contains the grading files.
4 our ($GRADES_DIR);
5
6 # Add our Perl library directory to the include path. 
7 BEGIN {
8     ($GRADES_DIR = $0) =~ s#/[^/]+$##;
9     -d $GRADES_DIR or die "$GRADES_DIR: stat: $!\n";
10     unshift @INC, "$GRADES_DIR/../lib";
11 }
12
13 use warnings;
14 use strict;
15 use Pintos::Grading;
16
17 our ($hw) = "threads";
18 our (@TESTS);           # Tests to run.
19 our ($test);
20 our (%details);
21 our (%result);
22 our ($action);
23
24 parse_cmd_line qw (alarm-single alarm-multiple alarm-zero alarm-negative
25                    join-simple
26                    join-quick join-multiple join-nested
27                    join-dummy join-invalid join-no
28                    priority-preempt priority-fifo priority-donate-one
29                    priority-donate-multiple priority-donate-nest
30                    mlfqs-on mlfqs-off);
31
32 clean_dir (), exit if $action eq 'clean';
33
34 extract_sources (); 
35 exit if $action eq 'extract';
36
37 build (); 
38 exit if $action eq 'build';
39
40 run_and_grade_tests ();
41 if (defined ($result{'mlfqs-on'}) && defined ($result{'mlfqs-off'})) {
42     grade_mlfqs_speedup ();
43     grade_mlfqs_priority ();
44 }
45 write_grades (); 
46 write_details ();
47 exit success () if $action eq 'test';
48
49 assemble_final_grade ();
50 exit success () if $action eq 'assemble';
51
52 die "Don't know how to '$action'";
53
54 # Runs $test in directory output/$test.
55 # Returns 'ok' if it went ok, otherwise an explanation.
56 sub run_test {
57     # Change constants.h if necessary.
58     my ($defines) = $test ne 'mlfqs-on' ? "" : "#define MLFQS 1\n";
59     $defines .= "#define THREAD_JOIN_IMPLEMENTED 1\n";
60     if ($defines ne snarf ("pintos/src/constants.h")) {
61         open (CONSTANTS, ">pintos/src/constants.h");
62         print CONSTANTS $defines;
63         close (CONSTANTS);
64     }
65
66     # Changes devices/timer.c if necessary.
67     my ($new_time_slice) = $test eq 'priority-fifo' ? 100 : 1;
68     my (@timer_c) = snarf ("pintos/src/devices/timer.c");
69     if (!grep (/^\#define TIME_SLICE $new_time_slice$/, @timer_c)) {
70         @timer_c = grep (!/^\#define TIME_SLICE/, @timer_c);
71         unshift (@timer_c, "#define TIME_SLICE $new_time_slice");
72         open (TIMER_C, ">pintos/src/devices/timer.c");
73         print TIMER_C map ("$_\n", @timer_c);
74         close (TIMER_C);
75     }
76
77     # Changes devices/timer.h if necessary.
78     my ($new_timer_freq) = $test eq 'priority-fifo' ? 19 : 100;
79     my (@timer_h) = snarf ("pintos/src/devices/timer.h");
80     if (!grep (/^\#define TIMER_FREQ $new_time_slice$/, @timer_h)) {
81         @timer_h = grep (!/^\#define TIMER_FREQ/, @timer_h);
82         unshift (@timer_h, "#define TIMER_FREQ $new_timer_freq");
83         open (TIMER_H, ">pintos/src/devices/timer.h");
84         print TIMER_H map ("$_\n", @timer_h);
85         close (TIMER_H);
86     }
87
88     # Copy in the new test.c and delete enough files to ensure a full rebuild.
89     my ($src) = "$GRADES_DIR/" . ($test !~ /^mlfqs/ ? "$test.c" : "mlfqs.c");
90     -e $src or die "$src: stat: $!\n";
91     xsystem ("cp $src pintos/src/threads/test.c", DIE => "cp failed\n");
92     unlink ("pintos/src/threads/build/threads/test.o");
93     unlink ("pintos/src/threads/build/kernel.o");
94     unlink ("pintos/src/threads/build/kernel.bin");
95     unlink ("pintos/src/threads/build/os.dsk");
96
97     # Build.
98     if (xsystem ("cd pintos/src/threads && make",
99                  LOG => "$test/make") ne 'ok') {
100         $details{$test} = snarf ("output/$test/make.err");
101         return "Compile error";
102     }
103
104     # Copy out files for backtraces later.
105     xsystem ("cp pintos/src/threads/build/kernel.o output/$test");
106     xsystem ("cp pintos/src/threads/build/os.dsk output/$test");
107
108     # Run.
109     my ($timeout) = $test !~ /^mlfqs/ ? 15 : 600;
110     return run_pintos (["-v", "run", "-q"],
111                        CHDIR => "pintos/src/threads/build",
112                        LOG => "$test/run",
113                        TIMEOUT => $timeout);
114 }
115 \f
116 sub grade_alarm_single {
117     verify_alarm (1, @_);
118 }
119
120 sub grade_alarm_multiple {
121     verify_alarm (7, @_);
122 }
123
124 sub verify_alarm {
125     my ($iterations, @output) = @_;
126
127     verify_common (@output);
128
129     my (@products);
130     for (my ($i) = 0; $i < $iterations; $i++) {
131         for (my ($t) = 0; $t < 5; $t++) {
132             push (@products, ($i + 1) * ($t + 1) * 10);
133         }
134     }
135     @products = sort {$a <=> $b} @products;
136
137     local ($_);
138     foreach (@output) {
139         die $_ if /out of order/i;
140
141         my ($p) = /product=(\d+)$/;
142         next if !defined $p;
143
144         my ($q) = shift (@products);
145         die "Too many wakeups.\n" if !defined $q;
146         die "Out of order wakeups ($p vs. $q).\n" if $p != $q; # FIXME
147     }
148     die scalar (@products) . " fewer wakeups than expected.\n"
149         if @products != 0;
150 }
151
152 sub grade_alarm_zero {
153     my (@output) = @_;
154     verify_common (@output);
155     die "Crashed in timer_sleep()\n" if !grep (/^Success\.$/, @output);
156 }
157
158 sub grade_alarm_negative {
159     my (@output) = @_;
160     verify_common (@output);
161     die "Crashed in timer_sleep()\n" if !grep (/^Success\.$/, @output);
162 }
163
164 sub grade_join_invalid {
165     my (@output) = @_;
166     verify_common (@output);
167     grep (/Testing invalid join/, @output) or die "Test didn't start\n";
168     grep (/Invalid join test done/, @output) or die "Test didn't complete\n";
169 }
170
171 sub grade_join_no {
172     my (@output) = @_;
173     verify_common (@output);
174     grep (/Testing no join/, @output) or die "Test didn't start\n";
175     grep (/No join test done/, @output) or die "Test didn't complete\n";
176 }
177
178 sub grade_join_multiple {
179     my (@output) = @_;
180
181     verify_common (@output);
182     my (@t);
183     $t[4] = $t[5] = $t[6] = -1;
184     local ($_);
185     foreach (@output) {
186         my ($idx) = /^Thread (\d+)/ or next;
187         my ($iter) = /iteration (\d+)$/;
188         $iter = 5 if /done!$/;
189         die "Malformed output\n" if !defined $iter;
190         if ($idx == 6) {
191             die "Thread 6 started before either other thread finished\n"
192                 if $t[4] < 5 && $t[5] < 5;
193             die "Thread 6 started before thread 4 finished\n"
194                 if $t[4] < 5;
195             die "Thread 6 started before thread 5 finished\n"
196                 if $t[5] < 5;
197         }
198         die "Thread $idx out of order output\n" if $t[$idx] != $iter - 1;
199         $t[$idx] = $iter;
200     }
201
202     my ($err) = "";
203     for my $idx (4, 5, 6) {
204         if ($t[$idx] == -1) {
205             $err .= "Thread $idx did not run at all\n";
206         } elsif ($t[$idx] != 5) {
207             $err .= "Thread $idx only completed $t[$idx] iterations\n";
208         }
209     }
210     die $err if $err ne '';
211 }
212
213 sub grade_priority_fifo {
214     my (@output) = @_;
215
216     verify_common (@output);
217     my ($thread_cnt) = 10;
218     my ($iter_cnt) = 5;
219     my (@order);
220     my (@t) = (-1) x $thread_cnt;
221     local ($_);
222     foreach (@output) {
223         my ($idx) = /^Thread (\d+)/ or next;
224         my ($iter) = /iteration (\d+)$/;
225         $iter = $iter_cnt if /done!$/;
226         die "Malformed output\n" if !defined $iter;
227         if (@order < $thread_cnt) {
228             push (@order, $idx);
229             die "Thread $idx repeated within first $thread_cnt iterations: "
230                 . join (' ', @order) . ".\n"
231                 if grep ($_ == $idx, @order) != 1;
232         } else {
233             die "Thread $idx ran when $order[0] should have.\n"
234                 if $idx != $order[0];
235             push (@order, shift @order);
236         }
237         die "Thread $idx out of order output.\n" if $t[$idx] != $iter - 1;
238         $t[$idx] = $iter;
239     }
240
241     my ($err) = "";
242     for my $idx (0..$#t) {
243         if ($t[$idx] == -1) {
244             $err .= "Thread $idx did not run at all.\n";
245         } elsif ($t[$idx] != $iter_cnt) {
246             $err .= "Thread $idx only completed $t[$idx] iterations.\n";
247         }
248     }
249     die $err if $err ne '';
250 }
251
252 sub grade_mlfqs_on {
253     my (@output) = @_;
254     verify_common (@output);
255     our (@mlfqs_on_stats) = mlfqs_stats (@output);
256 }
257
258 sub grade_mlfqs_off {
259     my (@output) = @_;
260     verify_common (@output);
261     our (@mlfqs_off_stats) = mlfqs_stats (@output);
262 }
263
264 sub grade_mlfqs_speedup {
265     our (@mlfqs_off_stats);
266     our (@mlfqs_on_stats);
267     eval {
268         check_mlfqs ();
269         my ($off_ticks) = $mlfqs_off_stats[1];
270         my ($on_ticks) = $mlfqs_on_stats[1];
271         die "$off_ticks ticks without MLFQS, $on_ticks with MLFQS\n"
272             if $on_ticks >= $off_ticks;
273         die "ok\n";
274     };
275     chomp $@;
276     $result{'mlfqs-speedup'} = $@;
277 }
278
279 sub grade_mlfqs_priority {
280     our (@mlfqs_off_stats);
281     our (@mlfqs_on_stats);
282     eval {
283         check_mlfqs () if !defined (@mlfqs_on_stats);
284         for my $cat qw (CPU IO MIX) {
285             die "Priority changed away from PRI_DEFAULT (29) without MLFQS\n"
286                 if $mlfqs_off_stats[0]{$cat}{MIN} != 29
287                 || $mlfqs_off_stats[0]{$cat}{MAX} != 29;
288             die "Minimum priority never changed from PRI_DEFAULT (29) "
289                 . "with MLFQS\n"
290                 if $mlfqs_on_stats[0]{$cat}{MIN} == 29;
291             die "Maximum priority never changed from PRI_DEFAULT (29) "
292                 . "with MLFQS\n"
293                 if $mlfqs_on_stats[0]{$cat}{MAX} == 29;
294         }
295         die "ok\n";
296     };
297     chomp $@;
298     $result{'mlfqs-priority'} = $@;
299 }
300
301 sub check_mlfqs {
302     our (@mlfqs_off_stats);
303     our (@mlfqs_on_stats);
304     die "p1-4 didn't finish with MLFQS on or off\n"
305         if !defined (@mlfqs_off_stats) && !defined (@mlfqs_on_stats);
306     die "p1-4 didn't finish with MLFQS on\n"
307         if !defined (@mlfqs_on_stats);
308     die "p1-4 didn't finish with MLFQS off\n"
309         if !defined (@mlfqs_off_stats);
310 }
311
312 sub mlfqs_stats {
313     my (@output) = @_;
314     my (%stats) = (CPU => {}, IO => {}, MIX => {});
315     my (%map) = ("CPU intensive" => 'CPU',
316                  "IO intensive" => 'IO',
317                  "Alternating IO/CPU" => 'MIX');
318     my (%rmap) = reverse %map;
319     my ($ticks);
320     local ($_);
321     foreach (@output) {
322         $ticks = $1 if /Timer: (\d+) ticks/;
323         my ($thread, $pri) = /^([A-Za-z\/ ]+): (\d+)$/ or next;
324         my ($t) = $map{$thread} or next;
325         
326         my ($s) = $stats{$t};
327         $$s{N}++;
328         $$s{SUM} += $pri;
329         $$s{SUM2} += $pri * $pri;
330         $$s{MIN} = $pri if !defined ($$s{MIN}) || $pri < $$s{MIN};
331         $$s{MAX} = $pri if !defined ($$s{MAX}) || $pri > $$s{MAX};
332     }
333
334     my (%expect_n) = (CPU => 5000, IO => 1000, MIX => 12000);
335     for my $cat (values (%map)) {
336         my ($s) = $stats{$cat};
337         die "$rmap{$cat} printed $$s{N} times, not $expect_n{$cat}\n"
338             if $$s{N} != $expect_n{$cat};
339         die "$rmap{$cat} priority dropped to $$s{MIN}, below PRI_MIN (0)\n"
340             if $$s{MIN} < 0;
341         die "$rmap{$cat} priority rose to $$s{MAX}, above PRI_MAX (59)\n"
342             if $$s{MAX} > 59;
343         $$s{MEAN} = $$s{SUM} / $$s{N};
344     }
345
346     return (\%stats, $ticks);
347 }