# Synopsis: Report an alternate FANIN/FANOUT metric for C++ functions. # # Categories: Project Metrics # # Languages: C # # Description: # Report an alternate FANIN/FANOUT metric for c/c++ functions. # # FANIN is the count of unique functions that call a given function, # either directly, or ultimately, via other functions. # FANOUT is the count of unique functions that are called by a # given function, either directly, or ultimately, via other # functions. # # The metrics may be calculated 'per file', which means that only # values are reported for files. The file values are based on # the all unique calls (or called by) of all the functions defined # in the file, and excludes calls (or called by) to functions within # the file. # # For the latest Understand perl API documentation, see # http://www.scitools.com/perl/ # sub usage($) { print shift,"\n"; print "Usage: c_fanin_fanout.pl\n"; print " -db database Specify the Understand database if run from uperl\n"; print " -csv Output in a format suitable for spredsheet import\n"; print " -fanin Report FANIN metric\n"; print " -fanout Report FANOUT metric\n"; print " -perfile Specify calculations and output to be file-based\n"; } use Understand; use Getopt::Long; use strict; my ($argCsv,$argFanin,$argFanout,$argVerbose,$argVerbose2,$db); report(); sub report() { # get options my ($argDb,$argHelp,$argPerfile); GetOptions ("db=s" => \$argDb, "help" => \$argHelp, "csv" => \$argCsv, "fanin" => \$argFanin, "fanout" => \$argFanout, "perfile" => \$argPerfile, "v|verbose" => \$argVerbose, "v2|verbose2" => \$argVerbose2, ) or return usage(""); return usage("") if ($argHelp); $argFanin = $argFanout = 1 unless ($argFanin || $argFanout); $argVerbose = 1 if $argVerbose2; # open the database $db = Understand::Gui::db(); if (!$db) { return usage("Error, database not specified") unless $argDb; my $status; ($db,$status) = Understand::open($argDb); return print("Error opening database: $status\n") if $status; } # check language my $language = $db->language(); return print("$language is currently unsupported") if ($language !=/c/i); if ($argPerfile) { per_file(); } else { per_function(); } } sub per_function { # calculate metrics for each function my (%fanin,%fanout); foreach my $func ($db->ents("function ~unknown ~unresolved")) { if ($argFanin) { my %used; print "Calculating FANIN for ". $func->longname(). "\n" if $argVerbose; $used{$func->id()} = 1; $fanin{$func->id()} = calc_fanin($func,\%used); } if ($argFanout) { my %used; print "Calculating FANOUT for ". $func->longname(). "\n" if $argVerbose; $used{$func->id()} = 1; $fanout{$func->id()} = calc_fanout($func,\%used); } } print "\n" if $argVerbose; # output metrics if ($argCsv) { print "Name"; print ",FANIN" if ($argFanin); print ",FANOUT" if ($argFanout); print "\n"; } foreach my $func (sort { lc($a->longname()) cmp lc($b->longname())} $db->ents("function ~unknown ~unresolved")) { if ($argCsv) { print $func->longname(); print ",". $fanin{$func->id()} if ($argFanin); print ",". $fanout{$func->id()} if ($argFanout); print "\n"; } elsif ($argFanin && !$argFanout) { print " ". $func->longname(). " = ". $fanin{$func->id()}. "\n"; } elsif ($argFanout && !$argFanin) { print " ". $func->longname(). " = ". $fanout{$func->id()}. "\n"; } else { print " ". $func->longname(). "\n"; print " FANIN = ". $fanin{$func->id()}. "\n"; print " FANOUT = ". $fanout{$func->id()}. "\n"; } } } sub per_file { # calculate metrics for each file my (%fanin,%fanout); foreach my $file ($db->ents("code file")) { # get list of functions defined in file my @funcs; foreach my $ref ($file->filerefs("define","function")) { push @funcs,$ref->ent(); } # calculate FANIN if ($argFanin) { print "Calculating FANIN for ". $file->longname(). "\n" if $argVerbose; my %used; foreach my $func (@funcs) { $used{$func->id()} = 1; } foreach my $func (@funcs) { print " Function: ". $func->longname(). "\n" if $argVerbose2; $fanin{$file->id()} += calc_fanin($func,\%used); } } # calculate FANOUT if ($argFanout) { print "Calculating FANOUT for ". $file->longname(). "\n" if $argVerbose; my %used; foreach my $func (@funcs) { $used{$func->id()} = 1; } foreach my $func (@funcs) { print " Function: ". $func->longname(). "\n" if $argVerbose2; $fanout{$file->id()} += calc_fanout($func,\%used); } } } print "\n" if $argVerbose; # output metrics if ($argCsv) { print "Name"; print ",FANIN" if ($argFanin); print ",FANOUT" if ($argFanout); print "\n"; } foreach my $file (sort { lc($a->longname()) cmp lc($b->longname())} $db->ents("code file")) { if ($argCsv) { print $file->longname(); print ",". $fanin{$file->id()} if ($argFanin); print ",". $fanout{$file->id()} if ($argFanout); print "\n"; } elsif ($argFanin && !$argFanout) { print " ". $file->longname(). " = ". $fanin{$file->id()}. "\n"; } elsif ($argFanout && !$argFanin) { print " ". $file->longname(). " = ". $fanout{$file->id()}. "\n"; } else { print " ". $file->longname(). "\n"; print " FANIN = ". $fanin{$file->id()}. "\n"; print " FANOUT = ". $fanout{$file->id()}. "\n"; } } } sub calc_fanin { my $func = shift; my $used = shift; my $count = 0; foreach my $ref ($func->refs("callby ~inactive,useby ~inactive","",1)) { if (!exists $$used{$ref->ent->id()}) { print " Calledby ". $ref->ent->longname(). "\n" if $argVerbose2; $$used{$ref->ent->id()} = 1; $count = $count + 1 + calc_fanin($ref->ent(),$used); } } return $count; } sub calc_fanout { my $func = shift; my $used = shift; my $count = 0; foreach my $ref ($func->refs("call ~inactive","",1)) { if (!exists $$used{$ref->ent->id()}) { print " Calls ". $ref->ent->longname(). "\n" if $argVerbose2; $$used{$ref->ent->id()} = 1; $count = $count + 1 + calc_fanout($ref->ent(),$used); } } return $count; }