Cyclic dependencies in Enswitch Perl backend

From Integrics Wiki
Revision as of 06:14, 24 March 2024 by Rodolfojcj (talk | contribs) (A way to identify cyclic dependencies in Perl modules of Enswitch)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Introduction

Given that cyclic dependencies between modules may bring runtime errors, the following is recommended:

  • Identify where they happen.
  • Refactor the source code to break them.

The details in this page are related with the first point, specifically with the Perl based Enswitch backend.

Automatically finding them

  • Use a machine where a copy of the Enswitch source code exists and take note of its directory path, for example:
    • /opt/enswitch
    • /opt/enswitch/4.3
    • /home/user/copy_of_enswitch_4.2
  • Install the packages that will be needed. For Debian or Ubuntu, run these commands:
 cpanm App::PrereqGrapher
 apt-get install graphviz
 apt-get install python3-networkx python3-graphviz python3-pydot python3-pydotplus python-is-python3
  • Save the following contents to a file (e.g /tmp/enswitch-cyclic-dependencies-finder.pl), which analyses the files inside the bin and lib subdirectories of Enswitch, assuming by default that they are under the /opt/enswitch directory.
 use App::PrereqGrapher;
 use Getopt::Long;
 use Module::Metadata;
 use strict;
 use warnings;
 
 my $source_dir = '/opt/enswitch';
 my $out_dir;
 my $depth = 2;
 
 GetOptions( 'source-dir=s' => \$source_dir, 'out-dir=s' => \$out_dir, 'depth:i' => \$depth, );
 
 $source_dir =~ s|/$||;
 $out_dir or die "Output directory is required.\n";
 $out_dir =~ s|/$||;
 
 for my $d ( 'bin', 'lib' ) {
         opendir( DIR, $source_dir . "/" . $d ) or die "Could not open $d\n";
         while ( my $f = readdir( DIR ) ) {
                 my $info = Module::Metadata->new_from_file( "$source_dir/$d/$f" );
                 if ( $info->{ 'module' } && $info->{ 'module' } ne 'main' )  {
                         print "$source_dir/$d/$f => " . $info->{ 'module' } . "\n";
                         generate_dot_file( $info->{ 'module' } );
                         transform_dot_file( $info->{ 'module' }, 'pdf' );
                         transform_dot_file( $info->{ 'module' }, 'svg' );
                         find_dependencies_cycles( $info->{ 'module' } );
                 }
         }
         filter_bidirectional_dependencies_cycles();
 }
 
 sub generate_dot_file {
         my $module = shift;
         my %options = (
                 format => 'dot',
                 no_core => 1,
                 no_recurse_core => 1,
                 depth => $depth,
                 output_file => $out_dir . "/" . $module =~ s/::/_/gr . ".dot",
                 verbose => 0,
         );
         my $grapher = App::PrereqGrapher->new( %options );
         $grapher->generate_graph( $module );
 }
 
 sub transform_dot_file {
         my $module = shift;
         my $format = shift;
         my $dot_file = $out_dir . "/" . $module =~ s/::/_/gr . ".dot";
         print "Converting $dot_file to $format.\n";
         system( "dot -T$format -O $dot_file" );
 }
 
 sub find_dependencies_cycles {
         my $module = shift;
         my $finder_path = '/tmp/dot_find_cycles.py';
         if ( ! -f $finder_path ) {
                 system( "wget --output-document=$finder_path https://github.com/jantman/misc-scripts/raw/master/dot_find_cycles.py" );
                 system( "chmod +x $finder_path" );
         }
         my $dot_file = $out_dir . "/" . $module =~ s/::/_/gr . ".dot";
         my $txt_file = $out_dir . "/" . $module =~ s/::/_/gr . ".cycles.txt";
         print "Finding dependencies cycles in $dot_file.\n";
         system( "$finder_path $dot_file > $txt_file" );
 }
 
 sub filter_bidirectional_dependencies_cycles {
         my $txts = $out_dir . "/" . "*.cycles.txt";
         my $filtered_file = $out_dir . "/" . "bidirectional.cycles.txt";
         print "Filtering bi-directional dependencies cycles in file patterns $txts.\n";
         system( "cat $txts | sort | uniq | " . 'egrep "^(.+) -> ([^->]+) -> \1$"' . " > $filtered_file" );
 }
  • Create a directory to store the results, for example /tmp/enswitch-cyclic-dependencies-results.
  • Run the previously saved script like this:
 perl /tmp/enswitch-cyclic-dependencies-finder.pl --out-dir=/tmp/enswitch-cyclic-dependencies-results --source-dir=/home/myuser/enswitch_4.4_copy
  • The results include these:
    • DOT files for the found Enswitch packages.
    • PDF and SVG files for visual representations of those DOT files.
    • Text files listing the found cyclic dependencies.
    • A text file named bidirectional.cycles.txt that lists the cyclic dependencies more likely to raise runtime errors and that are recommended to be refactored.

References