Back to home page

Project CMSSW displayed by LXR

 
 

    


Warning, /Utilities/ReleaseScripts/ignominy/deps2metrics is written in an unsupported language. File is not indexed.

0001 #!/usr/bin/env perl
0002 use strict;
0003 no warnings qw(recursion);
0004 $|=1;
0005 my $me = $0;
0006 $me =~ s|.*/||;
0007 
0008 ######################################################################
0009 my $SUM_INC_OUT_EDGES   = 0;
0010 my $SUM_INC_OUT_COUNT   = 1;
0011 my $SUM_INC_IN_EDGES    = 2;
0012 my $SUM_INC_IN_COUNT    = 3;
0013 my $SUM_LIB_OUT_EDGES   = 4;
0014 my $SUM_LIB_OUT_COUNT   = 5;
0015 my $SUM_LIB_IN_EDGES    = 6;
0016 my $SUM_LIB_IN_COUNT    = 7;
0017 my $SUM_LOG_OUT_EDGES   = 8;
0018 my $SUM_LOG_IN_EDGES    = 9;
0019 my $SUM_ALL_IN_EDGES    = 10;
0020 my $SUM_ALL_OUT_EDGES   = 11;
0021 my $SUM_N               = 12;
0022 
0023 my $CELL_INC_OUT_COUNT  = 0;
0024 my $CELL_INC_IN_COUNT   = 1;
0025 my $CELL_LIB_OUT_COUNT  = 2;
0026 my $CELL_LIB_IN_COUNT   = 3;
0027 my $CELL_LOG_OUT_EDGES  = 4;
0028 my $CELL_LOG_IN_EDGES   = 5;
0029 my $CELL_N              = 6;
0030 
0031 my @SUM_LABELS = ("Number of outgoing edges from includes",
0032                   "Total count of outgoing includes",
0033                   "Number of incoming edges from includes",
0034                   "Total count of incoming includes",
0035                   "Number of outgoing edges from symbols",
0036                   "Total count of outgoing symbols",
0037                   "Number of incoming edges from symbols",
0038                   "Total count of incoming symbols",
0039                   "Number of (possibly overlapping) outgoing logical edges",
0040                   "Number of (possibly overlapping) incoming logical edges",
0041                   "Total count of incoming edges",
0042                   "Total count of outgoing edges");
0043 
0044 sub min {
0045   my $min = shift;
0046   foreach my $val (@_) {
0047     $min = $min > $val ? $val : $min;
0048   }
0049   return $min;
0050 }
0051 
0052 sub max {
0053   my $max = shift;
0054   foreach my $val (@_) {
0055     $max = $max > $val ? $max : $val;
0056   }
0057   return $max;
0058 }
0059 
0060 sub rgb2hsv {
0061   my ($r, $g, $b) = @_;
0062   my ($h, $s, $v);
0063   my $max = &max($r, $g, $b);
0064   my $min = &min($r, $g, $b);
0065 
0066   $v = $max;
0067   $s = ($max ? ($max - $min)/$max : 0.0);
0068   if (! $s) {
0069     $h = 0;
0070   } else {
0071     my $d = $max - $min;
0072     if ($r == $max) {
0073       $h = 0. + ($g - $b)/$d;
0074     } elsif ($g == $max) {
0075       $h = 2. + ($b - $r)/$d;
0076     } elsif ($b == $max) {
0077       $h = 4. + ($r - $g)/$d;
0078     }
0079     $h /= 6.;
0080     $h += 1. if ($h < 0.);
0081   }
0082 
0083   $h = int($h * 1000 + .5)/1000;
0084   $s = int($s * 1000 + .5)/1000;
0085   $v = int($v * 1000 + .5)/1000;
0086 
0087   return ($h, $s, $v);
0088 }
0089 
0090 sub rgb2yiq {
0091   my ($r, $g, $b) = @_;
0092   return (.299 * $r + .587 * $g + .115 * $b,
0093           .596 * $r - .275 * $g - .321 * $b,
0094           .212 * $r - .523 * $g + .311 * $b);
0095 }
0096 
0097 sub hsv2rgb {
0098   my ($h, $s, $v) = @_;
0099   if ($s eq 0.) {
0100     die "bad HSV H value: $h -- should be 0\n" if $h;
0101     return ($v, $v, $v);
0102   } else {
0103     $h = 0. if $h eq 1.;
0104     $h *= 6.;
0105     my $i = int($h);
0106     my $f = $h - $i;
0107     my $p = $v * (1. - $s);
0108     my $q = $v * (1. - $s * $f);
0109     my $t = $v * (1. - $s * (1. - $f));
0110 
0111     return ($v, $t, $p) if $i == 0;
0112     return ($q, $v, $p) if $i == 1;
0113     return ($p, $v, $t) if $i == 2;
0114     return ($p, $q, $v) if $i == 3;
0115     return ($t, $p, $v) if $i == 4;
0116     return ($v, $p, $q) if $i == 5;
0117     die;
0118   }
0119 }
0120 
0121 sub validate_flag {
0122   my ($value, $opt) = @_;
0123 
0124   return 1 if (grep(lc($value) eq $_, qw(1 y yes)));
0125   return 0 if (grep(lc($value) eq $_, qw(0 n no)));
0126   die "$opt must be (1, y, yes) or (0, n, no)\n";
0127 }
0128 
0129 sub file_part {
0130   my $name = shift;
0131   $name =~ s|.*/||;
0132   return $name;
0133 }
0134 
0135 sub dir_part {
0136   my $name = shift;
0137   return '' if $name !~ m|/|;
0138   $name =~ s|/[^/]+$||;
0139   return $name;
0140 }
0141 
0142 sub check_xedge {
0143   my ($edges, $rinclusive, $rrx, $xinclusive, $xrx, $source, $target) = @_;
0144   return 0 if (grep($target =~ /$_/, @$rrx) && 1) ne $rinclusive;
0145 
0146   my $matches = 0;
0147   foreach my $xrx (@$xrx) {
0148     $matches += scalar grep ($_ ne ':hard'
0149                              && ($xinclusive ? $_ =~ /$xrx/ : $_ !~ /$xrx/),
0150                              @{$edges->{$source}->{$target}});
0151   }
0152   return $matches;
0153 }
0154 
0155 sub include_edge {
0156   my ($edges, $nodes, $rinclusive, $rrx, $xinclusive, $xrx, $source, $target) = @_;
0157   return 0 if !exists $nodes->{$target};
0158   return 0 if (grep($target =~ /$_/, @$rrx) && 1) ne $rinclusive;
0159   return 1 if grep($_ eq ':hard', @{$edges->{$source}->{$target}});
0160   return 2 if &check_xedge($edges,$rinclusive,$rrx,$xinclusive,$xrx,$source,$target) ne 0;
0161   return 0;
0162 }
0163 
0164 sub do_node {
0165   my ($gedges,$gnodes, $edges,$nodes, $rinclusive,$rrx, $xinclusive,$xrx, $from,$to,$include)=@_;
0166   return if ! $include;
0167 
0168   if (defined $from) {
0169     $gedges->{$from} ||= {};
0170     $gedges->{$from}->{$to} = $include
0171       if (! exists $gedges->{$from}->{$to}
0172           || $gedges->{$from}->{$to} > $include);
0173   }
0174 
0175   return if (exists $gnodes->{$to});
0176 
0177   $gnodes->{$to} = 1;
0178   foreach (keys %{$edges->{$to}}) {
0179     &do_node($gedges, $gnodes, $edges, $nodes, $rinclusive, $rrx, $xinclusive, $xrx, $to, $_,
0180              &include_edge($edges, $nodes, $rinclusive, $rrx, $xinclusive, $xrx, $to, $_));
0181   }
0182 }
0183 
0184 sub group_node {
0185   my ($grouping, $groups, $node, $default) = @_;
0186   foreach my $group (@$groups) {
0187     my $i = 0;
0188     foreach (@{$group->[2]}) {
0189       if ($node =~ /$_/) {
0190         $grouping->{$node} = [ $group, $i ];
0191         return;
0192       }
0193       $i++;
0194     }
0195   }
0196   $grouping->{$node} = [ $default, 0 ];
0197 }
0198 
0199 my %renames_results = ();
0200 sub rename_node {
0201   my ($renames, $node) = @_;
0202   if (exists $renames_results{$node}){return $renames_results{$node};}
0203   for (@$renames){
0204     if ($node =~ /$_->[0]/){
0205       my $tmp=eval $_->[1];
0206       $renames_results{$node}=$tmp;
0207       $renames_results{$tmp}=$tmp;
0208       return $tmp;
0209     }
0210   }
0211   $renames_results{$node}=$node;
0212   return $node;
0213 }
0214 
0215 sub dep_count {
0216   my ($table, $i, $j, $row) = @_;
0217   my $x = $table->[$i]->[$j];
0218   my $y = $table->[$j]->[$i];
0219 
0220   my ($libs, $incs);
0221   if ($row->[3] eq 'libraries') {
0222     $libs = $row->[2];
0223     $incs = $row->[5];
0224   } else {
0225     $libs = $row->[5];
0226     $incs = $row->[2];
0227   }
0228 
0229   $x->[$CELL_LIB_OUT_COUNT] += $libs;
0230   $y->[$CELL_LIB_IN_COUNT] += $libs;
0231 
0232   $x->[$CELL_INC_OUT_COUNT] += $incs;
0233   $y->[$CELL_INC_IN_COUNT] += $incs;
0234 }
0235 
0236 sub dep_xcount {
0237   my ($edges, $rinclusive, $rrx, $xinclusive, $xrx, $labels, $table, $i, $j) = @_;
0238   my $source = $labels->[$i];
0239   my $target = $labels->[$j];
0240   my $matches = &check_xedge($edges, $rinclusive, $rrx, $xinclusive, $xrx, $source, $target);
0241   if ($matches ne 0) {
0242     $table->[$i]->[$j]->[$CELL_LOG_OUT_EDGES] += $matches;
0243     $table->[$j]->[$i]->[$CELL_LOG_IN_EDGES]  += $matches;
0244   }
0245 }
0246 
0247 sub dep_summarise {
0248   my ($summary, $labels, $table, $i) = @_;
0249 
0250   foreach my $cell (@{$table->[$i]}) {
0251     $summary->[$i]->[$SUM_INC_OUT_EDGES]++ if ($cell->[$CELL_INC_OUT_COUNT]);
0252     $summary->[$i]->[$SUM_INC_OUT_COUNT]   += $cell->[$CELL_INC_OUT_COUNT];
0253     $summary->[$i]->[$SUM_INC_IN_EDGES]++  if ($cell->[$CELL_INC_IN_COUNT]);
0254     $summary->[$i]->[$SUM_INC_IN_COUNT]    += $cell->[$CELL_INC_IN_COUNT];
0255 
0256     $summary->[$i]->[$SUM_LIB_OUT_EDGES]++ if ($cell->[$CELL_LIB_OUT_COUNT]);
0257     $summary->[$i]->[$SUM_LIB_OUT_COUNT]   += $cell->[$CELL_LIB_OUT_COUNT];
0258     $summary->[$i]->[$SUM_LIB_IN_EDGES]++  if ($cell->[$CELL_LIB_IN_COUNT]);
0259     $summary->[$i]->[$SUM_LIB_IN_COUNT]    += $cell->[$CELL_LIB_IN_COUNT];
0260 
0261     $summary->[$i]->[$SUM_LOG_OUT_EDGES]   += $cell->[$CELL_LOG_OUT_EDGES];
0262     $summary->[$i]->[$SUM_LOG_IN_EDGES]    += $cell->[$CELL_LOG_IN_EDGES];
0263 
0264     $summary->[$i]->[$SUM_ALL_IN_EDGES]++
0265       if ($cell->[$CELL_INC_IN_COUNT]
0266           || $cell->[$CELL_LIB_IN_COUNT]
0267           || $cell->[$CELL_LOG_IN_EDGES]);
0268 
0269     $summary->[$i]->[$SUM_ALL_OUT_EDGES]++
0270       if ($cell->[$CELL_INC_OUT_COUNT]
0271           || $cell->[$CELL_LIB_OUT_COUNT]
0272           || $cell->[$CELL_LOG_OUT_EDGES]);
0273   }
0274 }
0275 
0276 sub dep_metrics_mark {
0277   my ($edges, $tedges, $n,$m) = @_;
0278   return if defined $m && exists $tedges->{$n}->{$m};
0279   if (defined $m) {
0280     $tedges->{$n}->{$m} = 1;
0281     map { &dep_metrics_mark($edges, $tedges, $n, $_) } keys %{$edges->{$m}};
0282   }
0283   map { &dep_metrics_mark($edges, $tedges, $n, $_) } keys %{$edges->{$n}};
0284 }
0285 
0286 sub dep_metrics {
0287   # see ftp://ftp.aw.com/cp/lakos/idep_ldep.c
0288   my ($edges, $nodes, $metrics) = @_;
0289 
0290   my @nodes     = keys %$nodes;
0291   my $n         = scalar @nodes;
0292   my @levels    = (0) x $n;
0293   my $nlevels   = 1;
0294   my @levelnums = (0) x $n;
0295   my @mapping   = (0) x $n;
0296   my $nmapping  = 0;
0297 
0298   my @lowerthan = map { [ (0) x $n ] } @nodes, 'foo', 'bar';
0299    
0300   # calculate transitive closure
0301   my %tedges = map { $_ => {} } keys %$edges;
0302   map { &dep_metrics_mark($edges, \%tedges, $_, undef) } keys %$edges;
0303 
0304   # determine and label all members of all cycles
0305   my @cycles    = (-1) x $n;
0306   my @weights   = (0) x $n;
0307   my @cindices  = (-1) x $n;
0308   my $ncycles   = 0;
0309   my $nmembers  = 0;
0310   for (my $i = 0; $i < $n; ++$i) {
0311     next if $cycles[$i] >= 0;
0312     my $found = 0;
0313     $cycles[$i] = $i;
0314     for (my $j = $i + 1; $j < $n; ++$j) {
0315       next if $cycles[$j] >= 0;
0316       if ($tedges{$nodes[$i]}->{$nodes[$j]} && $tedges{$nodes[$j]}->{$nodes[$i]}) {
0317         $found = 1;
0318         $cycles[$j] = $i;
0319       }
0320     }
0321     if ($found) {
0322       my $weight = 0;
0323       for (my $j = $i; $j < $n; ++$j) {
0324         ++$weight if $cycles[$j] == $i;
0325       }
0326       for (my $j = $i; $j < $n; ++$j) {
0327         $weights[$j] = $weight if $cycles[$j] == $i;
0328       }
0329       $nmembers += $weight;
0330       $ncycles++;
0331     } else {
0332       $cycles[$i] = -1;
0333     }
0334   }
0335 
0336   # sort packages into levelized order; strip principal cycle
0337   # members from their dependencies on other cycle members
0338   for (my $i = 0; $i < $n; ++$i) {
0339     next if $cycles[$i] != $i;
0340     for (my $j = $i + 1; $j < $n; ++$j) {
0341       next if $cycles[$j] != $i;
0342       delete $tedges{$nodes[$i]}->{$nodes[$j]};
0343       $lowerthan[1]->[$j] = 1;
0344     }
0345   }
0346 
0347   # construct levelized array of package indices
0348   while ($nmapping < $n) {
0349     my $count = 0;
0350     my @current = (0) x $n;
0351     for (my $i = 0; $i < $n; ++$i) {
0352       next if $cycles[$i] >= 0 && $cycles[$i] != $i;
0353       next if $lowerthan[$nlevels]->[$i];
0354 
0355       my $weight = 1;
0356       if ($cycles[$i] == $i) {
0357         next if $weights[$i] > $nlevels + 1;
0358         $weight = $weights[$i];
0359       }
0360 
0361       my $level = $nlevels + 1 - $weight;
0362       my $j;
0363       for ($j = 0; $j < $n; ++$j) {
0364         next if $i == $j;
0365         last if $tedges{$nodes[$i]}->{$nodes[$j]} && !$lowerthan[$level]->[$j];
0366       }
0367       next if $j < $n;
0368 
0369       $current[$i] = 1;
0370       $mapping[$nmapping++] = $i;
0371       $count++;
0372       if ($cycles[$i] == $i) {
0373         for ($j = $i + 1; $j < $n; ++$j) {
0374           next if $cycles[$j] != $i;
0375           $mapping[$nmapping++] = $j;
0376           $count++;
0377           $tedges{$nodes[$i]}->{$nodes[$j]} = 1;
0378         }
0379       }
0380     }
0381     for (my $i = 0; $i < $n; ++$i) {
0382       $current[$i] ||= $lowerthan[$nlevels][$i];
0383     }
0384     $levels[$nlevels++] = $count;
0385     @{$lowerthan[$nlevels]} = @current;
0386     @current = (0) x $n;
0387   }
0388   die "internal error" if $nmapping != $n;
0389 
0390   # start loading level number array
0391   my $start = 0;
0392   for (my $i = 1; $i < $nlevels; ++$i) {
0393     my $top = $start + $levels[$i];
0394     for (my $j = $start; $j < $top; ++$j) {
0395       $levelnums[$mapping[$j]] = $i;
0396     }
0397     $start = $top;
0398   }
0399 
0400   # sort packages on each level lexicographically
0401   $start = 0;
0402   for (my $k = 1; $k < $nlevels; ++$k) {
0403     my $top = $start + $levels[$k];
0404     for (my $i = $start + 1; $i < $top; ++$i) {
0405       for (my $j = $start; $j < $i; ++$j) {
0406         if (($nodes[$mapping[$i]] cmp $nodes[$mapping[$j]]) < 0) {
0407           my $tmp = $mapping[$i];
0408           $mapping[$i] = $mapping[$j];
0409           $mapping[$j] = $tmp;
0410         }
0411       }
0412     }
0413     $start = $top;
0414   }
0415 
0416   # create @cindices from cycles array and the level map
0417   my $ncycle = 0;
0418   for (my $i = 0; $i < $n; ++$i) {
0419     my $label = $cycles[$mapping[$i]];
0420     next if $label < 0;
0421     my $index = $cindices[$mapping[$i]];
0422     next if $index >= 0 && $index < $ncycle;
0423     for (my $j = $i; $j < $n; ++$j) {
0424       $cindices[$mapping[$j]] = $ncycle if $label == $cycles[$mapping[$j]];
0425     }
0426     $ncycle++;
0427   }
0428   die "internal error" if $ncycle != $ncycles;
0429 
0430   # sort packages on each level again but now grouping
0431   # cyclically dependent packages together
0432   $start = 0;
0433   for (my $k = 1; $k < $nlevels; ++$k) {
0434     my $top = $start + $levels[$k];
0435     for (my $i = $start + 1; $i < $top; ++$i) {
0436       for (my $j = $start; $j < $i; ++$j) {
0437         my $ci = $cindices[$mapping[$i]];
0438         my $cj = $cindices[$mapping[$j]];
0439         if ($ci < $cj || ($ci == $cj && ($nodes[$mapping[$i]] cmp $nodes[$mapping[$j]]) < 0)) {
0440           my $tmp = $mapping[$i];
0441           $mapping[$i] = $mapping[$j];
0442           $mapping[$j] = $tmp;
0443         }
0444       }
0445     }
0446     $start = $top;
0447   }
0448 
0449   # calculate ccd
0450   my %tmp = map { $_ => {} } keys %tedges;
0451   foreach my $n (keys %tedges) {
0452     map { $tmp{$n}->{$_} = 1 } keys %{$tedges{$n}};
0453   }
0454 
0455   for (my $i = 0; $i < $n; ++$i) {
0456     if ($levelnums[$i] == 0) {
0457       for (my $j = 0; $j < $n; ++$j) {
0458         delete $tmp{$nodes[$j]}->{$nodes[$i]};
0459       }
0460     } else {
0461       $tmp{$nodes[$i]}->{$nodes[$i]} = 1;
0462     }
0463   }
0464 
0465   my $ccd = 0;
0466   map { $ccd += scalar keys %{$tmp{$_}} } keys %tmp;
0467 
0468   if (0) {
0469     # make canonical representation: remove all redundant (transitive0edges
0470     my @tmp = map { [ (0) x $n ] } @nodes;
0471     for (my $i = 0; $i < $n; ++$i) {
0472       my $u = $mapping[$i];
0473       for (my $j = 0; $j < $n; ++$j) {
0474         my $v = $mapping[$j];
0475         my $bit = $tedges{$nodes[$u]}->{$nodes[$v]};
0476         $tmp[$i]->[$j] = $bit;
0477       }
0478     }
0479     # FIXME;
0480   }
0481 
0482   $metrics->{NODES}     = \@nodes;
0483   $metrics->{N}         = $n;
0484   $metrics->{LEVELS}    = \@levels;
0485   $metrics->{NLEVELS}   = $nlevels;
0486   $metrics->{LEVELNUMS} = \@levelnums;
0487   $metrics->{MAPPING}   = \@mapping;
0488   $metrics->{NMAPPINGS} = $nmapping;
0489    
0490   $metrics->{TEDGES}    = \%tedges;
0491 
0492   $metrics->{CYCLES}    = \@cycles;
0493   $metrics->{WEIGHTS}   = \@weights;
0494   $metrics->{CINDICES}  = \@cindices;
0495   $metrics->{NCYCLES}   = $ncycles;
0496   $metrics->{NMEMBERS}  = $nmembers;
0497 
0498   $metrics->{CCD}       = $ccd;
0499 
0500   return $nmembers;
0501 }
0502 
0503 my %url_results = ();
0504 sub node_url {
0505   my ($urls, $name) = @_;
0506   if (exists $url_results{$name}){return $url_results{$name};}
0507   foreach my $mapping (@$urls) {
0508     if ($name =~ /$mapping->[0]/) {
0509       my $N = $name;
0510       my $tmp = eval $mapping->[1];
0511       $url_results{$name} = $tmp;
0512       return $tmp;
0513     }
0514   }
0515   $url_results{$name} = undef;
0516   return undef;
0517 }
0518 
0519 sub read_report {
0520   my ($edges, $nodes, $file, $part) = @_;
0521   my $node = undef;
0522 
0523   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0524   while (<FILE>) {
0525     last if /^\# $part/;
0526   }
0527 
0528   while (<FILE>) {
0529     chomp;
0530     last if (/^\#\#\#\#\#/);
0531     next if (/^\s*$/);
0532 
0533     if (/^\S/) {
0534       chop;
0535       s% ([^/]+)$%%;
0536       $node = $_;
0537       $nodes->{$_} = 1;
0538       $edges->{$_} ||= {};
0539     } elsif (/^\s+\S/) {
0540       print STDERR "$file: warning: no current node for a dependency\n"
0541         if ! $node;
0542 
0543       s/\s//g;
0544       $nodes->{$_} = 1;
0545       $edges->{$_} ||= {}; 
0546       $edges->{$node}->{$_} ||= [];
0547       push(@{$edges->{$node}->{$_}}, ':hard');
0548     } else {
0549       print STDERR "$file: warning: unrecognized line: `$_'\n";
0550     }
0551   }
0552   close(FILE);
0553 }
0554 
0555 sub keep_subpartonly_nodes {
0556   my ($kept_nodes, $edges, $node, $spart) = @_;
0557   $kept_nodes->{$node} = 1;
0558   foreach my $edge (keys %{$edges->{$node}}) {
0559     if (!exists $kept_nodes->{$edge} && ($edge=~/$spart/)){
0560       &keep_subpartonly_nodes ($kept_nodes, $edges, $edge, $spart);
0561     }
0562   }
0563 }
0564 
0565 sub keep_nodes {
0566   my ($kept_nodes, $edges, $node) = @_;
0567   $kept_nodes->{$node} = 1;
0568   foreach my $edge (keys %{$edges->{$node}}) {
0569     if (!exists $kept_nodes->{$edge}){
0570       &keep_nodes ($kept_nodes, $edges, $edge);
0571     }
0572   }
0573 }
0574 
0575 sub remove_nodes {
0576   my ($edges, $nodes, $spart, $spartonly) = @_;
0577   my %kept_nodes = ();
0578   my $node;
0579   foreach $node (keys %$nodes) {
0580     if (($node=~/$spart/) && (!exists $kept_nodes{$node})){
0581       if ($spartonly){
0582         &keep_subpartonly_nodes (\%kept_nodes, $edges, $node, $spart);
0583       }
0584       else{
0585         &keep_nodes (\%kept_nodes, $edges, $node);
0586       }
0587     }
0588   }
0589   foreach $node (keys %$nodes) {
0590     if (!exists $kept_nodes{$node}){
0591       delete $nodes->{$node};
0592       delete $edges->{$node};
0593     }
0594   }
0595 }
0596 
0597 sub read_counts {
0598   my ($edges, $nodes, $vector, $note, $file) = @_;
0599   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0600   while (<FILE>) {
0601     chomp;
0602     if (! /^\s*(\d+)\s+(\S+)\s+(\S+(,\s+\S+)?)(\s+via\s+(\S+))?$/) {
0603       print STDERR "$file: error: bad line `$_'\n";
0604       next;
0605     }
0606     next if $2 eq $3;
0607     $nodes->{$2} = 1; $edges->{$2} ||= {};
0608     $nodes->{$3} = 1; $edges->{$3} ||= {};
0609     $edges->{$2}->{$3} ||= [];
0610     push(@{$edges->{$2}->{$3}}, ':hard');
0611     push(@$vector, [ $2, $3, $1, $note, $5 || '', -1]);
0612   }
0613   close(FILE);
0614 }
0615 
0616 sub read_loglinks {
0617   my ($edges, $nodes, $file) = @_;
0618   my @xtargets = ();
0619   my @xreasons = ();
0620 
0621   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0622   while (<FILE>) {
0623     chomp; s/\#.*//; s/\s*$//;
0624     next if (/^\s*$/);
0625 
0626     if (/^(\S+(\s*,\s*\S+)*)(\s*:\s*->\s*(\S+(\s*,\s*\S+)*))?$/) {
0627       @xreasons = split(/\s*,\s*/, $1);
0628       @xtargets = split(/\s*,\s*/, $4 || '');
0629     } elsif (/^\s+(\S+(\s*,\s*\S+)*)(\s*->\s*(\S+(\s*,\s*\S+)*))?$/) {
0630       my @sources = split(/\s*,\s*/, $1);
0631       my @targets = ($4 ? split(/\s*,\s*/, $4) : @xtargets);
0632       foreach my $source (grep(exists $nodes->{$_}, @sources)) {
0633         $edges->{$source} ||= {};
0634         map { $edges->{$source}->{$_} ||= [];
0635               push(@{$edges->{$source}->{$_}}, @xreasons); } @targets;
0636       }
0637     } else {
0638       print STDERR "$file: warning: unrecognized line: `$_'\n";
0639     }
0640   }
0641   close(FILE);
0642 }
0643 
0644 sub read_groups {
0645   my ($groups, $file) = @_;
0646   my $i = 0;
0647 
0648   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0649   while (<FILE>) {
0650     chomp;
0651     while (/\\$/ && !eof(FILE)) {
0652       chop; $_ .= <FILE>; chomp;
0653     }
0654     if (/\\$/) {
0655       print STDERR "$file: warning: incomplete last line\n";
0656       chop;
0657     }
0658     s/\#.*//; s/^\s*//; s/\s*$//;
0659     next if (/^$/);
0660 
0661     if (/^group\s+(\S+)\s+(.*)\s+--\s+(.*)$/) {
0662       push(@$groups, [ $i++, $1, [ split(/\s+/, $2) ], $3 ]);
0663     } else {
0664       print STDERR "$file: warning: unrecognized line: `$_'\n";
0665     }
0666   }
0667   close(FILE);
0668 }
0669 
0670 sub read_renames {
0671   my ($renames, $file) = @_;
0672   my $i = 0;
0673 
0674   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0675   while (<FILE>) {
0676     chomp;
0677     while (/\\$/ && !eof(FILE)) {
0678       chop; $_ .= <FILE>; chomp;
0679     }
0680     if (/\\$/) {
0681       print STDERR "$file: warning: incomplete last line\n";
0682       chop;
0683     }
0684     s/\#.*//; s/^\s*//; s/\s*$//;
0685     next if (/^$/);
0686 
0687     if (/^rename\s+([^:]+):(.*)$/) {
0688       push(@$renames, [ $1, $2 ]);
0689     } else {
0690       print STDERR "$file: warning: unrecognized line: `$_'\n";
0691     }
0692   }
0693   close(FILE);
0694 }
0695 
0696 sub read_urls {
0697   my ($urls, $file) = @_;
0698   my $i = 0;
0699 
0700   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0701   while (<FILE>) {
0702     chomp;
0703     while (/\\$/ && !eof(FILE)) {
0704       chop; $_ .= <FILE>; chomp;
0705     }
0706     if (/\\$/) {
0707       print STDERR "$file: warning: incomplete last line\n";
0708       chop;
0709     }
0710     s/^\s*//; s/\s*$//;
0711     next if (/^$/);
0712     next if (/^#/);
0713 
0714     if (/^([^:]+):(.*)$/) {
0715       push(@$urls, [ $1, $2 ]);
0716     } else {
0717       print STDERR "$file: warning: unrecognized line: `$_'\n";
0718     }
0719   }
0720   close(FILE);
0721 }
0722 
0723 ######################################################################
0724 my $usage =
0725     "usage: $me REPORT-FILE REPORT-PART REPORT-SUBPART SUBPARTONLY INCLUSIVE? [RE...]\n"
0726   . "               [! RESTRICT-INCLUSIVE? RESTRICT-RE...]\n"
0727   . "               [-- EXTRA EXTRA-INCLUSIVE? EXTRA-RE...]\n";
0728 
0729 die $usage if (scalar @ARGV < 3);
0730 my ($file, $part, $subpart, $subpartonly, $inclusive, @restargs) = @ARGV;
0731 my ($rinclusive, $xfile, $xinclusive, @rx, @rrx, @xrx) = (1, "/dev/null", 1);
0732 $inclusive = &validate_flag($inclusive, "INCLUSIVE?");
0733 
0734 while (scalar @restargs) {
0735   my $arg = shift(@restargs);
0736   if ($arg eq '!') {
0737     die $usage if (scalar @restargs < 2);
0738     $rinclusive = &validate_flag(shift(@restargs), "RESTRICT-INCLUSIVE?");
0739     while (scalar @restargs && $restargs[0] ne '--') {
0740       push(@rrx, shift(@restargs));
0741     }
0742   } elsif ($arg eq '--') {
0743     die $usage if (scalar @restargs < 2);
0744     $xfile = shift(@restargs);
0745     $xinclusive = &validate_flag(shift(@restargs), "EXTRA-INCLUSIVE?");
0746     @xrx = @restargs;
0747     $xrx[0] ||= '.*';
0748     last;
0749   } else {
0750     push(@rx, $arg);
0751   }
0752 }
0753 $rx[0] ||= '.*';
0754 $rrx[0] ||= '.*';
0755 
0756 ######################################################################
0757 my %nodes = ();
0758 my %gnodes = ();
0759 my %edges = ();
0760 my %gedges = ();
0761 my @roots = ();
0762 my %metrics = ();
0763 
0764 # Read in the report.
0765 &read_report(\%edges, \%nodes, $file, $part);
0766 
0767 # Read in the extra-deps file.
0768 &read_loglinks(\%edges, \%nodes, $xfile);
0769 
0770 # Remove the unwanted nodes
0771 &remove_nodes(\%edges, \%nodes, $subpart, $subpartonly) if ($subpart ne "");
0772 
0773 # Calculate which nodes can be reached.
0774 foreach my $node (keys %nodes) {
0775   push(@roots, $node) if (grep($node =~ /$_/, @rx) && 1) eq $inclusive;
0776 }
0777 
0778 foreach (@roots) {
0779   &do_node(\%gedges,\%gnodes, \%edges,\%nodes, $rinclusive,\@rrx, $xinclusive,\@xrx, undef,$_,1);
0780 }
0781 
0782 # Calculate metrics
0783 &dep_metrics(\%gedges,\%gnodes, \%metrics);
0784 
0785 ######################################################################
0786 # Print results
0787 sub plural {
0788   my $arg = shift;
0789   return $arg == 1 ? "" : "s";
0790 }
0791 sub ccdbalanced {
0792   my $n = shift;
0793   return ($n + 1) * (log($n + 1)/log(2) - 1) + 1;
0794 }
0795 
0796 sub ccd {
0797   return $metrics{CCD};
0798 }
0799 sub acd {
0800   return $metrics{N} ? &ccd / $metrics{N} : 0.0;
0801 }
0802 sub nccd {
0803   return $metrics{N} ? &ccd / &ccdbalanced($metrics{N}) : 0.0;
0804 }
0805 sub minnccd {
0806   return $metrics{N} ? $metrics{N}/&ccdbalanced($metrics{N}) : 0.0;
0807 }
0808 sub maxnccd {
0809   return $metrics{N} ? $metrics{N}*$metrics{N} / &ccdbalanced($metrics{N}) : 0.0;
0810 }
0811 
0812 print "# Summary\n";
0813 if ($metrics{NCYCLES} > 0) {
0814   print sprintf "%-11s %d\n", "Cycles", $metrics{NCYCLES};
0815   print sprintf "%-11s %d\n", "Members", $metrics{NMEMBERS};
0816 }
0817 
0818 print sprintf "%-11s %d\n", "Packages", $metrics{N};
0819 print sprintf "%-11s %d\n", "Levels", $metrics{NLEVELS}-1;
0820 
0821 print sprintf "%-11s %f\n", "CCD", &ccd;
0822 print sprintf "%-11s %f\n", "ACD", &acd;
0823 print sprintf "%-11s %f [%f, %f]\n", "NCCD", &nccd, &minnccd, &maxnccd;
0824 print "\n";
0825 print "* CCD:  Cumulative Component Dependency measures the cumulative testing cost\n";
0826 print "        across the system.\n";
0827 print "* ACD:  Average Component Dependency indicates the number of other packages\n";
0828 print "        an average package depends on.\n";
0829 print "* NCCD: Normalised Cumulative Component Dependency measures how the structure\n";
0830 print "        differs from a balanced binary tree of comparable size. If NCCD is one,\n";
0831 print "        the structure resembles a binary tree; if much less than one, the\n";
0832 print "        packages are mostly independent; if much greater than one, the system\n";
0833 print "        is fairly strongly coupled. The only universal NCCD target is to\n";
0834 print "        minimise for any given software system--a high value indicates a\n";
0835 print "        strongly coupled system and less coupling is better.\n";
0836 print "\n";
0837 
0838 for (my $i = 0; $i < $metrics{NCYCLES}; ++$i) {
0839   print "# Cycles\n" if $i == 0;
0840   print "Cycle ", $i + 1, "\n";
0841   my $j = -1;
0842   while ($j < $metrics{N}) {
0843     do { ++$j } while ($j < $metrics{N}
0844                        && $metrics{CINDICES}->[$metrics{MAPPING}->[$j]] != $i);
0845     last if $j >= $metrics{N};
0846     print "    " . $metrics{NODES}->[$metrics{MAPPING}->[$j]] . "\n";
0847   }
0848 }
0849 
0850 print "\n" if $metrics{NCYCLES};
0851 print "# Levels\n";
0852 
0853 my $start = 0;
0854 my $skiped_levels = 0;
0855 for (my ($i, $start) = (1, 0); $i < $metrics{NLEVELS}; $start += $metrics{LEVELS}->[$i++]) {
0856   my $first = 1;
0857   for (my ($j, $top) = ($start, $start + $metrics{LEVELS}->[$i]); $j < $top; ++$j) {
0858     my $name = $metrics{NODES}->[$metrics{MAPPING}->[$j]];
0859     my $cycle = $metrics{CINDICES}->[$metrics{MAPPING}->[$j]] + 1;
0860 
0861     if ($first) {
0862       $first = 0;
0863       print sprintf "%-4s ", $i-$skiped_levels.".";
0864     } else {
0865       print "     ";
0866     }
0867 
0868     print $name;
0869     if ($metrics{NCYCLES} > 0 && $cycle) {
0870       print "<$cycle>";
0871     }
0872     map { print "\n        " . $_ } keys %{$metrics{EDGES}->{$metrics{MAPPING}->[$i]}};
0873     print "\n";
0874   }
0875   if ($first){
0876     $skiped_levels++;
0877   }
0878   else{
0879     print "\n";
0880   }
0881 }