#! /usr/bin/perl use strict; use warnings; use File::Basename qw(&dirname); use File::Find; use Data::Dumper; use Statistics::Basic::StdDev; use List::Util; $|=1; sub readfile { my($fname)=@_; local *F; open F,"$fname" or die $fname; local $/=undef(); defined(my $r=) or die $fname; close F or die $fname; return $r; } my %R; if (($ARGV[0]||"") eq "-r") { shift; my $r=readfile shift; $R{$1}=1 while $r=~s/^(.*?)\n//; die $r if $r; } my %D; # ==> build/Cadence-1.0.0-0.12.20200504git5787908.fc33.src.rpm.dtneeded <== # /usr/lib/debug/.dwz/Cadence isdwzcommon 479079 NA # /usr/bin/cadence-jackmeter /usr/lib/debug/.dwz/Cadence 717792 1329040 # /usr/bin/cadence-xycontroller /usr/lib/debug/.dwz/Cadence 1763616 2369832 # ==> build/CVector-1.0.3.1-21.fc33.src.rpm.dtneeded <== # /usr/lib64/libCVector-1.0.3.so.2.0.0 nodwzcommon 28088 28896 for my $dtneededfn (glob "build/*.dtneeded") { next if %R && !$R{($dtneededfn=~m{^build/(.*?)-[^-]*-[^-]*$})[0]."-debuginfo"}; local *F; open F,$dtneededfn or die "$dtneededfn: $1"; local $_; while () { chomp; my($fn,$dwzcommon,$dwzsize,$dtsize)=/^(\S+) (\S+) (\S+) (\S+)$/ or die "$dtneededfn: $_"; die if $dwzcommon eq "isdwzcommon" && $dtsize ne "NA"; my $ref=\$D{$fn}; if ($$ref) { die if $dwzcommon eq "isdwzcommon"; $$ref=[]; } else { $$ref=[$dwzcommon,$dwzsize,$dtsize]; } } close F or die "close $dtneededfn: $1"; } chdir "dtneeded.out" or die "dtneeded.out: $!"; my %F; my %SONAME; my @DEBUG; my %SYMLINK; find { "no_chdir"=>1, "wanted"=>sub { die $File::Find::dir if $File::Find::dir=~m{/$}; my $binfn=$File::Find::name; if (-l $binfn) { my $target=readlink $binfn or die $binfn; $binfn=~s{^[.]}{}; my $final; if ($target=~m{^/}) { $final=$target; #warn "$binfn,target=rel=$$ref\n"; } else { my $base=dirname(".$binfn"); my $abs=File::Spec->rel2abs($target,$base); $final="/".File::Spec->abs2rel($abs); #warn "$binfn,base=$base,target=$target,abs=$abs,rel=$$ref\n"; } 1 while $final=~s{/[^/]+/[.][.]/}{/}; 1 while $final=~s{/[^/]+/[.][.]$}{}; if ($final=~m{/[.][.]}) { warn "$binfn,target=$target,final=$final\n"; return; } my $ref=\$SYMLINK{$binfn}; die if $$ref; $$ref=$final; return; } return if !-f $binfn; die $binfn if $binfn!~m{^[.]/}; my $bin=readfile $binfn; $binfn=~s{^[.]}{} or die; my $rpath=($bin=~m{^.*\Q(R\E(?:UN)?\QPATH)\E\s*Library r(?:un)?path: \Q[\E(.*)\Q]\E$}m)[0]; if ($rpath) { my $dirname=$File::Find::dir; die if $dirname!~s{^[.]/}{/}; $rpath=~s{\$ORIGIN}{$dirname}g; } my $soname=($bin=~m{^.*\Q(SONAME)\E\s*Library soname: \Q[\E(.*)\Q]\E$}m)[0]; my $h={ "binfn"=>$binfn, "needed"=>[$bin=~m{^\s*0x0000000000000001\s*\Q(NEEDED)\E\s*\QShared library: [\E(.*)\Q]\E$}gm], }; $h->{"rpath"}=$rpath if $rpath; $F{$binfn}=$h; #warn "$binfn=".Dumper($h)."\n" if $binfn=~/libc.so.6/; # my $total=keys %F; warn "$total...\n" if 0==$total%1000; if ($soname) { my $sonamefn=$File::Find::dir."/".$soname; $sonamefn=~s{^[.]/}{/} or die $sonamefn; my $ref=\$SONAME{$sonamefn}; # warn "soname=<$sonamefn> <$binfn> vs. <".$$ref->{"binfn"}.">\n" if $$ref; $$ref=$h; } push @DEBUG,$binfn if $bin=~m{ \Q(DEBUG) \E }; }, },"."; #while (my($src,$target)=each %SYMLINK) { # die "$src->$target" if exists $SYMLINK{$target}; #} my $dwzsizeall=0; my $dwzsizeduplall=0; my $dtsizeall=0; my @ratioall; my @ratioduplall; my $computed=0; BINFN: for my $binfn (@DEBUG) { # warn "$binfn...\n".Dumper([sort @{$F{$binfn}{"needed"}}]); die $binfn if exists $SYMLINK{$binfn}; my @l=$binfn; my %l=($binfn=>1); while (@l) { my $l=shift @l; my $h=$F{$l}; for my $needed (@{$h->{"needed"}}) { my $found; if ($needed=~m{^/}) { $needed=$SYMLINK{$needed} while exists $SYMLINK{$needed}; $found=$needed; } else { # die "$binfn: $l: $needed" if $needed=~m{/}; my @rpath; my $rpath=$h->{"rpath"}; push @rpath,split /:/,$rpath if $rpath; push @rpath,qw(/lib64 /usr/lib64); for my $rpath (@rpath) { if ($rpath!~m{^/}) { warn "$binfn: $l: $rpath"; next; } my $fn="$rpath/$needed"; $fn=$SYMLINK{$fn} while exists $SYMLINK{$fn}; next if !$SONAME{$fn}; $found=$fn; last; } warn "$binfn: $l: $needed not found; rpath=".join(":",@rpath)."\n" if !$found; } push @l,$found if $found&&!$l{$found}++; } } #warn Dumper $binfn,\%l; my $dwzsizetot=0; my $dwzsizedupltot=0; my $dtsizetot=0; my %dwzcommons; for my $l (keys(%l)) { my $ref=$D{$l}; if (!defined $ref) { warn "$binfn: $l: missing\n"; next BINFN; } if (0==@$ref) { warn "$binfn: $l: ambiguous\n"; next BINFN; } my $dtsize=$ref->[2]; die if !defined $dtsize; $dtsizetot+=$dtsize; my $dwzsize=$ref->[1]; die if !defined $dwzsize; $dwzsizetot+=$dwzsize; $dwzsizedupltot+=$dwzsize; $computed++; my $dwzcommon=$ref->[0]; next if $dwzcommon eq "nodwzcommon"; die if $dwzcommon eq "isdwzcommon"; my $duplicate=$dwzcommons{$dwzcommon}++; my $dwzcommonref=$D{$dwzcommon}; die if !$dwzcommonref; die if $dwzcommonref->[0] ne "isdwzcommon"; die if $dwzcommonref->[2] ne "NA"; my $dwzcommonsize=$dwzcommonref->[1]; $dwzsizetot+=$dwzcommonsize if !$duplicate; $dwzsizedupltot+=$dwzcommonsize; } print "$binfn: dwzsizetot=$dwzsizetot dtsizetot=$dtsizetot\n"; warn "$binfn: ".Dumper(\%dwzcommons); $dwzsizeall+=$dwzsizetot; $dwzsizeduplall+=$dwzsizedupltot; $dtsizeall+=$dtsizetot; push @ratioall ,$dwzsizetot /$dtsizetot; push @ratioduplall,$dwzsizedupltot/$dtsizetot; # warn "$binfn done\n".Dumper([sort keys(%l)]); } print "dwzsizeall =$dwzsizeall" ." dtsizeall=$dtsizeall =".$dwzsizeall /$dtsizeall." avg=".List::Util::sum(@ratioall )/@ratioall ." stddev=".Statistics::Basic::stddev(\@ratioall )."\n"; print "dwzsizeduplall=$dwzsizeduplall"." dtsizeall=$dtsizeall =".$dwzsizeduplall/$dtsizeall." avg=".List::Util::sum(@ratioduplall)/@ratioduplall." stddev=".Statistics::Basic::stddev(\@ratioduplall)."\n"; print "computed=$computed of DEBUG=".(0+@DEBUG)." =".$computed/@DEBUG."\n"; #print Dumper \@ratioall;