source: gs2-extensions/parallel-building/trunk/src/bin/script/generate_gantt.pl@ 30354

Last change on this file since 30354 was 30354, checked in by jmt12, 8 years ago

Extending manifest v2 support to allow for directories to be listed in manifest. Matched with changes in Directory plugin to allow paths into systems like HDFS to be listed in manifest.cd

  • Property svn:executable set to *
File size: 18.8 KB
Line 
1#!/usr/bin/perl
2
3# Pragma
4use strict;
5use warnings;
6
7BEGIN
8{
9 if ( !defined $ENV{'GEXTPARALLELBUILDING_INSTALLED'}) {
10 die "GEXTPARALLELBUILDING_INSTALLED not set\n";
11 }
12 # Installed CPAN packages for GEXT*INSTALL
13 my $perl_version = `perl-version.pl`;
14 my $perl_path = sprintf("%s/lib/perl/%s", $ENV{'GEXTPARALLELBUILDING_INSTALLED'}, $perl_version);
15 unshift (@INC, $perl_path);
16}
17
18# Modules
19use Sort::Naturally;
20use POSIX qw(floor strftime);
21
22print "\n===== Generate Timing (GANTT) =====\n";
23
24# 0. Init
25# - configurables
26my $chart_width = 1600;
27# - any video more than 95% complete is probably complete with rounding errors
28my $complete_threshold = 95;
29
30my $debug = 0;
31my $color_master = 'blue';
32my $color_worker = 'green';
33my $color_nlocal = 'red';
34my $disable_header = 0;
35my $max_worker_count = 0;
36# - globals
37my $chart_count = 0;
38my @months = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
39
40# 1. Parse options
41while (defined $ARGV[0] && $ARGV[0] =~ /^-/)
42{
43 my $option = shift(@ARGV);
44 if ($option eq '-debug')
45 {
46 $debug = 1;
47 }
48 elsif ($option eq '-width')
49 {
50 if (!defined $ARGV[0])
51 {
52 &printUsage('Error! No width value specified');
53 }
54 my $value = shift(@ARGV);
55 if ($value !~ /^\d+$/)
56 {
57 &printUsage('Error! Chart width not a number');
58 }
59 $chart_width = $value;
60 }
61 elsif ($option eq '-grayscale')
62 {
63 $color_master = '#1D1D1D';
64 $color_worker = '#969696';
65 $color_nlocal = '#4C4C4C';
66 }
67 elsif ($option eq '-noheader')
68 {
69 $disable_header = 1;
70 }
71 elsif ($option eq '-maxworkers')
72 {
73 if (!defined $ARGV[0])
74 {
75 &printUsage('Error! No maxworkers value specified');
76 }
77 my $value = shift(@ARGV);
78 if ($value !~ /^\d+$/)
79 {
80 &printUsage('Error! Maxworkers not a number');
81 }
82 $max_worker_count = $value;
83 }
84 else
85 {
86 &printUsage('Error! Unknown option: ' . $option);
87 }
88}
89print "Chart Width: " . $chart_width . "px\n";
90print "Grayscale? " . ($debug ? 'Yes' : 'No') . "\n";
91print "Debug? " . ($debug ? 'Yes' : 'No') . "\n";
92print "===================================\n\n";
93
94# 2. Search for valid directories (containing timing.csv)
95while (defined $ARGV[0])
96{
97 my $dir = shift(@ARGV);
98 if (!-d $dir)
99 {
100 &printUsage('Error! Not a directory: ' . $dir);
101 }
102 if ($dir =~ /(.*)[\\\/]$/)
103 {
104 $dir = $1;
105 }
106 &searchForTimingCSV($dir);
107}
108
109# 3. Done
110print "Complete!\n\n";
111print "===================================\n";
112print 'Generated ' . $chart_count . " charts\n";
113print "===================================\n\n";
114exit;
115## main() ##
116
117
118## @function searchForTimingCSV()
119#
120sub searchForTimingCSV
121{
122 my $dir = shift(@_);
123 # For every directory where we find a timing.csv we generate a gantt chart
124 my $timing_path = &filenameCat($dir, 'timing.csv');
125 if (-e $timing_path)
126 {
127 &generateChart($dir, $timing_path);
128 }
129 # We also recursively search for other directories containing timing.csv's
130 opendir(my $dh, $dir) or &printError('Failed to open directory for reading: ' . $dir);
131 my @files = readdir($dh);
132 foreach my $file (@files)
133 {
134 if ($file !~ /^\./)
135 {
136 my $path = &filenameCat($dir, $file);
137 if (-d $path)
138 {
139 &searchForTimingCSV($path);
140 }
141 }
142 }
143}
144## searchForTimingCSV() ##
145
146
147## @function generateChart()
148#
149sub generateChart
150{
151 my $dir = shift(@_);
152 my $timing_csv_path = shift(@_);
153 my $import_dir;
154 my ($epoc) = $dir =~ /(\d+)$/;
155 my $gantt_path = $dir . '/' . $epoc . '-gantt.html';
156
157 print ' * Generating chart for: ' . $dir . "\n";
158 print ' - timing file: ' . $timing_csv_path . "\n";
159 print ' - gantt chart: ' . $gantt_path . "\n";
160
161 # Read in timing.csv and parse information into data structure
162 print ' - parsing timing.csv... ';
163 my $timing_data = {};
164 my $id_2_worker_id = {};
165 if (open(TIN, '<:utf8', $timing_csv_path))
166 {
167 my $line;
168 while ($line = <TIN>)
169 {
170 my @parts = split(/,/, $line);
171 if ($parts[1] eq 'M0')
172 {
173 $timing_data->{'M'} = {'N'=>$parts[2], 'S'=>$parts[3], 'E'=>$parts[4]};
174 }
175 elsif ($parts[1] =~ /W\d+/)
176 {
177 my $worker_id = $parts[1];
178 my $hostname = $parts[2];
179 # Alter the worker name for compute nodes so they can be naturally
180 # sorted
181 if ($hostname =~ /compute-0-(\d+)/)
182 {
183 $worker_id = 'W' . $1;
184 }
185 $timing_data->{$worker_id} = {'N'=>$hostname, 'S'=>$parts[3], 'E'=>$parts[4], 'F'=>{}};
186 $id_2_worker_id->{$parts[0]} = $worker_id;
187 }
188 elsif ($parts[1] =~ /T\d+/)
189 {
190 my $worker_id = $id_2_worker_id->{$parts[7]};
191 my $stop = $parts[4];
192 my $filepath = $parts[8];
193 $filepath =~ s/^\s+|\s+$//g;
194 my $percent_complete = 'NA';
195 if (defined($parts[9]))
196 {
197 $percent_complete = $parts[9];
198 chomp($percent_complete);
199 if ($percent_complete >= $complete_threshold)
200 {
201 $percent_complete = 'NA';
202 }
203 }
204 $import_dir = &longestCommonPath($filepath, $import_dir);
205 my $start_time = $parts[3];
206 while (defined $timing_data->{$worker_id}->{'F'}->{$start_time})
207 {
208 $start_time += 0.000001;
209 }
210 $timing_data->{$worker_id}->{'F'}->{$start_time} = {'FN'=>$filepath, 'S'=>$parts[3], 'PS'=>($stop - $parts[5]), 'PE'=>$stop, 'E'=>$stop, 'DL'=>$parts[6], 'PC'=>$percent_complete};
211 }
212 }
213 close(TIN);
214 }
215 else
216 {
217 die('Error! Failed to open file for reading: ' . $timing_csv_path);
218 }
219 my $number_of_workers = scalar(keys(%{$id_2_worker_id}));;
220 print "Done\n";
221
222 # 3. Produce pretty HTML chart of timing information including jobs
223 print " - generating timing information as chart in HTML... ";
224 open(HTMLOUT, '>:utf8', $gantt_path) or die('Error! Failed to open file for writing: gantt.html');
225 print HTMLOUT "<html>\n";
226 print HTMLOUT '<head>' . "\n";
227 print HTMLOUT '<style type="text/css">' . "\n";
228 print HTMLOUT "body {margin:0px;padding:4px}\n";
229 print HTMLOUT 'div.thread {position:relative}' . "\n";
230 print HTMLOUT 'div.master {border:1px solid gray;color:white;font-weight:bold}' . "\n";
231 print HTMLOUT 'div.worker {border:1px solid black;background-color:' . $color_worker . ';color:white;font-weight:bold;margin-bottom:1px;}' . "\n";
232 print HTMLOUT 'div.time {font-size:smaller;font-weight:normal}' . "\n";
233 print HTMLOUT 'div.job {background-color:transparent;color:black;border:1px solid black;display:block;font-size:smaller;font-weight:normal;position:relative;text-align:left;margin-bottom:1px;}' . "\n";
234 print HTMLOUT 'span.process {z-index:-1;background-color:#C7C7C7;position:absolute}' . "\n";
235 print HTMLOUT 'div.label {z-index:1;background-color:transparent;white-space:nowrap;text-align:center}' . "\n";
236 print HTMLOUT "th {text-align:left}\n";
237 print HTMLOUT "tr.toprule th,tr.toprule td {border-top:2px solid black;width:17%}\n";
238 print HTMLOUT "tr.bottomrule th,tr.bottomrule td {border-bottom:2px solid black}\n";
239 print HTMLOUT '</style>' . "\n";
240 print HTMLOUT '</head>' . "\n";
241 print HTMLOUT "<body>\n";
242 ##print HTMLOUT "<h2>Parallel Import Timing Chart</h2>\n";
243 print HTMLOUT "<table style=\"border-collapse:collapse;width:" . $chart_width . "px;";
244 if ($disable_header)
245 {
246 print HTMLOUT "display:none;";
247 }
248 print HTMLOUT "\">\n";
249
250 my $total_duration = $timing_data->{'M'}->{'E'} - $timing_data->{'M'}->{'S'};
251 my $file_count = 0;
252 my $data_locality = 0;
253 my $total_io_time = 0;
254 my $total_process_time = 0;
255 my $fastest_file = 0;
256 my $slowest_file = 0;
257 my $problem_files = 0;
258 foreach my $worker_id (keys %{$timing_data})
259 {
260 if ($worker_id ne 'M')
261 {
262 foreach my $job_start ( keys %{$timing_data->{$worker_id}->{'F'}} )
263 {
264 my $process_start = $timing_data->{$worker_id}->{'F'}->{$job_start}->{'PS'};
265 my $process_end = $timing_data->{$worker_id}->{'F'}->{$job_start}->{'PE'};
266 my $job_end = $timing_data->{$worker_id}->{'F'}->{$job_start}->{'E'};
267 my $percent_complete = $timing_data->{$worker_id}->{'F'}->{$job_start}->{'PC'};
268 if ($process_start == 0 || $process_end == 0 || $job_end == 0 || ($percent_complete =~ /^\d+$/ && $percent_complete < $complete_threshold))
269 {
270 $problem_files++;
271 }
272 else
273 {
274 my $io_duration = ($process_start - $job_start) + ($job_end - $process_end);
275 my $process_duration = $process_end - $process_start;
276 my $total_duration = $io_duration + $process_duration;
277 &debugPrint("filename: " . $timing_data->{$worker_id}->{'F'}->{$job_start}->{'FN'});
278 &debugPrint("start: $job_start ps: $process_start pe: $process_end end: $job_end");
279 &debugPrint("io: $io_duration process: $process_duration duration: $total_duration");
280 # Running stats
281 $total_io_time += $io_duration;
282 $total_process_time += $process_duration;
283 if ($fastest_file == 0 || $total_duration < $fastest_file)
284 {
285 $fastest_file = $total_duration;
286 }
287 if ($slowest_file == 0 || $total_duration > $slowest_file)
288 {
289 $slowest_file = $total_duration;
290 }
291 }
292 # Shorten filename
293 if (defined $timing_data->{$worker_id}->{'F'}->{$job_start}->{'FN'} && $timing_data->{$worker_id}->{'F'}->{$job_start}->{'FN'} ne '')
294 {
295 $timing_data->{$worker_id}->{'F'}->{$job_start}->{'FN'} = substr($timing_data->{$worker_id}->{'F'}->{$job_start}->{'FN'}, length($import_dir) + 1);
296 }
297 $file_count++;
298 if ($timing_data->{$worker_id}->{'F'}->{$job_start}->{'DL'} == 1)
299 {
300 $data_locality++;
301 }
302 }
303 }
304 }
305 if ($file_count <= 0)
306 {
307 $file_count = 1;
308 }
309 if ($total_process_time <= 0)
310 {
311 $total_process_time = 1;
312 }
313 my $avg_processing_time = floor(($total_io_time + $total_process_time) / $file_count);
314 my $avg_io_time = int(($total_io_time / $file_count) + 0.5);
315 my $avg_cpu_time = int(($total_process_time / $file_count) + 0.5);
316
317 print HTMLOUT "<tr class=\"toprule\">\n";
318 print HTMLOUT ' <th style="width:17%;">Import Directory:</th><td style="width:83%;" colspan="5">' . $import_dir . "</td>\n";
319 print HTMLOUT "</tr>\n";
320
321 print HTMLOUT "<tr>\n";
322 my ($sec, $min, $hour, $day, $month, $year) = (localtime($timing_data->{'M'}->{'S'}))[0,1,2,3,4,5];
323 print HTMLOUT ' <th>Start Time:</th><td>' . sprintf('%04d%s%02d %02d:%02d:%02d', ($year+1900), $months[$month], $day, $hour, $min, $sec) . "</td>\n";
324 ($sec, $min, $hour, $day, $month, $year) = (localtime($timing_data->{'M'}->{'E'}))[0,1,2,3,4,5];
325 print HTMLOUT ' <th>End Time:</th><td>' . sprintf('%04d%s%02d %02d:%02d:%02d', ($year+1900), $months[$month], $day, $hour, $min, $sec) . "</td>\n";
326 print HTMLOUT " <th>Processing Time:</th><td>" . &renderTime($total_duration) . "</td>\n";
327 print HTMLOUT "</tr>\n";
328
329 print HTMLOUT "<tr>\n";
330 print HTMLOUT " <th>Processing Threads:</th><td>" . $number_of_workers . "</td>\n";
331 print HTMLOUT " <th>Files Processed:</th><td>" . $file_count . "</td>\n";
332 print HTMLOUT " <th>Problem Files:</th><td>" . $problem_files . "</td>\n";
333 print HTMLOUT "</tr>\n";
334
335 print HTMLOUT "<tr>\n";
336 print HTMLOUT ' <th>Serial Processing Time:</th><td>' . &renderTime($total_process_time) . "</td>\n";
337 print HTMLOUT ' <th>Serial IO Time:</th><td>' . &renderTime($total_io_time) . "</td>\n";
338 print HTMLOUT ' <th>IO Percentage:</th><td>' . sprintf('%d%%', (($total_io_time / $total_process_time) * 100)) . "</td>\n";
339 print HTMLOUT "</tr>\n";
340
341 print HTMLOUT "<tr>\n";
342 print HTMLOUT " <th>Avg Processing Time:</th><td>" . &renderTime($avg_processing_time) . "</td>\n";
343 print HTMLOUT " <th>Avg File IO Time:</th><td>" . &renderTime($avg_io_time) . "</td>\n";
344 print HTMLOUT " <th>Avg File CPU Time:</th><td>" . &renderTime($avg_cpu_time) . "</td>\n";
345 print HTMLOUT "</tr>\n";
346
347 print HTMLOUT "<tr class=\"bottomrule\">\n";
348 print HTMLOUT " <th>Fastest File:</th><td>" . &renderTime($fastest_file) . "</td>\n";
349 print HTMLOUT " <th>Slowest File:</th><td>" . &renderTime($slowest_file) . "</td>\n";
350 #if ($data_locality > 0)
351 #{
352 print HTMLOUT " <th>Data Locality:</th><td>" . sprintf('%d%% [%d out of %d]', (($data_locality / $file_count) * 100), $data_locality, $file_count) . "</td>\n";
353 #}
354 #else
355 #{
356 # print HTMLOUT " <th>Data Locality:</th><td><i>Not Applicable</i></td>\n";
357 #}
358 print HTMLOUT "</tr>\n";
359
360 print HTMLOUT "</table>\n";
361 print HTMLOUT renderLine($chart_width, $timing_data->{'M'}->{'S'}, $timing_data->{'M'}->{'E'}, 'master', $timing_data->{'M'}->{'N'}, $timing_data->{'M'}->{'S'}, $timing_data->{'M'}->{'E'}, {}, $data_locality);
362 my $worker_count = 0;
363 foreach my $worker_id (nsort keys %{$timing_data})
364 {
365 if ($max_worker_count < 1 || $worker_count <= $max_worker_count)
366 {
367 if ($worker_id ne 'M')
368 {
369 my $data = $timing_data->{$worker_id};
370 print HTMLOUT renderLine($chart_width, $timing_data->{'M'}->{'S'}, $timing_data->{'M'}->{'E'}, 'worker', $worker_id . ' [' . $data->{'N'} . ']', $data->{'S'}, $data->{'E'}, $data->{'F'}, 2); #$data_locality);
371 $worker_count++;
372 }
373 }
374 }
375 print HTMLOUT '</div>' . "\n";
376 print HTMLOUT "</body>\n";
377 print HTMLOUT "</html>";
378 close(HTMLOUT);
379 print "Done!\n\n";
380 $chart_count++;
381}
382## generateChart() ##
383
384
385## @function debugPrint()
386#
387sub debugPrint
388{
389 my $msg = shift(@_);
390 if ($debug)
391 {
392 print STDERR '[DEBUG] ' . $msg . "\n";
393 }
394}
395## debugPrint() ##
396
397
398## @function filenameCat
399#
400sub filenameCat
401{
402 my $path = join('/', @_);
403 $path =~ s/[\/\\]+/\//g;
404 return $path;
405}
406## filenameCat() ##
407
408
409## @function printError()
410#
411sub printError
412{
413 my $msg = shift(@_);
414 die('Error! ' . $msg . "\n\n");
415}
416## printError() ##
417
418
419## @function printUsage()
420#
421sub printUsage
422{
423 my $msg = shift(@_);
424 if (defined $msg)
425 {
426 print 'Error! ' . $msg . "\n";
427 }
428 die("Usage: generate_gantt.pl [-width <width in pixels>] <dir> [<dir> ...]\n\n");
429}
430## printUsage() ##
431
432
433## @function longestCommonPath
434#
435sub longestCommonPath
436{
437 my ($path_new, $path_current) = @_;
438 my $result = '';
439 if (defined $path_current)
440 {
441 # Hide protocol before we split by slash
442 $path_new =~ s/:\/\//:/;
443 $path_current =~ s/:\/\//:/;
444 my @path_new_parts = split(/\//, $path_new);
445 my @path_current_parts = split(/\//, $path_current);
446 my @path_parts;
447 for (my $i = 0; $i < scalar(@path_current_parts); $i++)
448 {
449 if ($path_current_parts[$i] eq $path_new_parts[$i])
450 {
451 push(@path_parts, $path_new_parts[$i]);
452 }
453 else
454 {
455 last;
456 }
457 }
458 $result = &filenameCat(@path_parts);
459 # Restore protocol
460 $result =~ s/:/:\/\//;
461 }
462 else
463 {
464 $result = $path_new;
465 }
466 return $result;
467}
468## longestCommonPath() ##
469
470
471## @function renderLine()
472#
473sub renderLine
474{
475 my ($table_width, $start, $end, $class, $tname, $tstart, $tend, $jobs, $data_locality) = @_;
476 &debugPrint("renderLine($table_width, $start, $end, $class, $tname, $tstart, $tend, <jobs>, $data_locality)");
477 # All timings need to be relative to 0 (relative start)
478 my $duration = $end - $start;
479 my $rtstart = $tstart - $start;
480 my $rtend = $tend - $start;
481 # We need to scale these depending on the timing of this thread relative to
482 # the master thread
483 my $width = $chart_width;
484 my $left = 0;
485 if ($start != $tstart)
486 {
487 my $left_offset_percent = $rtstart / $duration;
488 $left = $left_offset_percent * $table_width;
489 }
490 # - subtract any left offset from width
491 $width = $width - $left;
492 # - right offset directly subtracted from width
493 if ($end != $tend)
494 {
495 my $right_offset_percent = ($duration - $rtend) / $duration;
496 my $right = $right_offset_percent * $table_width;
497 $width = $width - $right;
498 }
499 # Round things off (simple dutch rounding)
500 $left = int($left + 0.5);
501 $width = int($width + 0.5);
502 # Output the bar for this master/worker
503 my $html = '<div class="thread ' . $class . '" style="left:' . $left . 'px;width:' . $width . 'px;">';
504 if ($class eq 'master')
505 {
506 $html .= '<div style="background-color:' . $color_master . ';margin-bottom:1px">';
507 }
508 $html .= '<div class="time" style="display:table-cell">' . &renderTime($rtstart) . '</div><div style="display:table-cell;padding-left:20px;width:100%;">' . ucfirst($class) . ': ' . $tname . '</div><div class="time" style="display:table-cell">' . renderTime($rtend) . '</div></div>';
509 my $previous_jright = 0;
510 foreach my $jstart (sort keys %{$jobs})
511 {
512 my $rjstart = $jstart - $start;
513 my $rpstart = $jobs->{$jstart}->{'PS'} - $start;
514 my $rpend = $jobs->{$jstart}->{'PE'} - $start;
515 my $rjend = $jobs->{$jstart}->{'E'} - $start;
516 my $jduration = $jobs->{$jstart}->{'E'} - $jstart;
517 my $io_duration = $rpstart - $rjstart;
518 my $cpu_duration = $rpend - $rpstart;
519 # Scale Job co-ordinates
520 my $jleft_percent = $rjstart / $duration;
521 my $jleft = int(($jleft_percent * $table_width) + 0.5);
522 my $jwidth_percent = $jduration / $duration;
523 # -2 for left and right 1 pixel border
524 my $jwidth = int(($jwidth_percent * $table_width) + 0.5) - 2;
525 if ($jleft + $jwidth > $left + $width)
526 {
527 $jwidth = ($left + $width) - $jleft;
528 }
529 # Then scale process timings within that!
530 my $rpleft_percent = ($rpstart - $rjstart) / $duration;
531 my $rpleft = int(($rpleft_percent * $table_width) + 0.5);
532 my $rpwidth = $jwidth - $rpleft;
533 my $cpu_percent = int((($rpwidth / $jwidth) * 100) + 0.5);
534 $html .= '<div class="job" style="left:' . $jleft . 'px;width:' . $jwidth . 'px;';
535 ###rint "Data Locality? " . $data_locality . " DL? " . $jobs->{$jstart}->{'DL'} . "\n";
536 if ($data_locality > 1 && $jobs->{$jstart}->{'DL'} != 1)
537 {
538 $html .= 'border:1px solid #C7C7C7;';
539 }
540 $html .= '" title="FN:' . $jobs->{$jstart}->{'FN'} . ', S:' . &renderTime($rjstart) . ', E:' . &renderTime($rjend) . ', CPU: ' . $cpu_percent . '% [' . &renderTime($io_duration) . ', ' . &renderTime($cpu_duration) . ', PC: ' . $jobs->{$jstart}->{'PC'} . '%]"><span class="process" style="left:' . $rpleft . 'px;width:' . $rpwidth . 'px">&nbsp;</span><div class="label" style="width:' . $jwidth;
541 if ($data_locality > 1 && $jobs->{$jstart}->{'DL'} != 1)
542 {
543 $html .= ';color:' . $color_nlocal;
544 }
545 $html .= '">' . $jobs->{$jstart}->{'FN'};
546 if ($jobs->{$jstart}->{'PC'} ne 'NA')
547 {
548 $html .= ' <b>[Incomplete! ' . $jobs->{$jstart}->{'PC'} . '%]</b>';
549 }
550 if ($data_locality > 1 && $jobs->{$jstart}->{'DL'} != 1)
551 {
552 $html .= ' [NL]';
553 }
554 $html .= '</div></div>';
555 }
556 return $html;
557}
558## renderLine() ##
559
560
561## @function renderTime()
562#
563sub renderTime
564{
565 my ($seconds) = @_;
566 my $time_str = '';
567 # determine how many hours
568 my $an_hour = 60 * 60;
569 my $hours = floor($seconds / $an_hour);
570 $seconds = $seconds - ($hours * $an_hour);
571 my $a_minute = 60;
572 my $minutes = floor($seconds / $a_minute);
573 $seconds = $seconds - ($minutes * $a_minute);
574 if ($hours > 0)
575 {
576 $time_str = sprintf('%dh%02dm%02ds', $hours, $minutes, $seconds);
577 }
578 else
579 {
580 $time_str = sprintf('%dm%02ds', $minutes, $seconds);
581 }
582 return $time_str;
583}
Note: See TracBrowser for help on using the repository browser.