Visualisation with GraphViz

From RackTables Wiki
Jump to: navigation, search

GraphViz is an ingenious package created by IBM to visualise lots of possible object topologies. The package makes it very easy to create all kinds of directed and undirected graphs from raw data.

Hereby I share with you some simple code to create a very basic network topology graph from RackTables database. All updates (expecially fine-tuning the .dot file) are much welcome, since there are hundreds of possible tweaks in dot (the submodule I'm using here) itself.

I use dot (which is the most general topology module) because my tests showed that default layouts of other modules resulted inferior outputs. Still, the .dot file is the same, and you're all free to tweak other settings and show us all. :-)

Code requires perl (DBI / DBD::mysql for database access) and graphviz. If you use accented characters for strings you have to recode the output to UTF-8 somehow, I'm using konwert for that, but recode or other tools would do just fine, too.

dottr.pl

#!/usr/bin/perl
##$Id: dottr.pl,v 5aa2d11fa948 2012/11/10 10:57:04 grin $
#
# (c) Peter 'grin' Gervai, 2012
# Released under CreativeCommons-Attribution-ShareAlike-3.0-Unported
#
# create .dot from racktables links
#

use DBI;
use Encode;

use DatabaseLoginData; # $db_user; $db_pw; $db_host

my ($db_name, $db_port) = ("racktables_db", 3306);

my $dbh = DBI->connect( "dbi:mysql:database=$db_name;host=$DatabaseLoginData::db_host;port=$db_port",
                           $DatabaseLoginData::db_user, $DatabaseLoginData::db_pw, 
                           { RaiseError =>1, AutoCommit => 1 } );

#########################################################################

my $sth_links = $dbh->prepare("
SELECT ro1.name AS obj1, p1.name AS port1, Link.cable, p2.name AS port2, ro2.name AS obj2, d.dict_value AS obj1type
 FROM RackObject AS ro1 
   JOIN Port AS p1 ON(ro1.id=p1.object_id)
   JOIN Link       ON(p1.id=Link.porta)
   JOIN Port AS p2 ON(Link.portb=p2.id)
   JOIN RackObject AS ro2 ON(p2.object_id=ro2.id)
   LEFT JOIN Dictionary AS d ON(ro1.objtype_id=d.dict_key)
 ORDER BY obj1, port1");

# I append random number to reservation comments to prevent same comments to
# be merged into one node.
my $sth_reservations = $dbh->prepare("
SELECT ro.name AS obj1, p.name AS port1, 'RESERVE' AS cable, '' AS port2, 
 CONCAT(reservation_comment,' #',ROUND(RAND()*1000))  AS obj2,  d.dict_value AS obj1type
 FROM RackObject AS ro
   JOIN Port AS p  ON(ro.id=p.object_id)
   LEFT JOIN Dictionary AS d ON(ro.objtype_id=d.dict_key)
     WHERE p.reservation_comment IS NOT NULL
 ORDER BY obj1, port1");

my $res = $sth_links->execute;
my $a = $sth_links->fetchall_arrayref;
my @data = @$a;

$res = $sth_reservations->execute;
$a = $sth_reservations->fetchall_arrayref;
@data = (@data, @$a);

# header
print '
graph rackspace_topo {

label = "Tarr Racktables topo";
rankdir = LR;

       edge [ color="#0000a0", decorate=true, fontsize=9, headclip=false ];
       node [ shape=box, headport=n, tailport=n ];
       
';


# generate edges
foreach $a (@data) {
  # node+interface 1
  $node{$a->[0]} = $a->[1];
  $nodetype{$a->[0]} = $a->[5];
  # node+interface 2
  $node{$a->[4]} = $a->[3];
  # 
  push @link, $a;
}

# output the nods first
foreach my $n (keys %node) {
  # nodes with empty or "space" names
  next if $n =~ /^\s*$/;

  $ntyp = $nodetype{$n};

  # color/style depends on node type
  my $color="";
  if( $ntyp eq "Server" ) {
    $color='color="black"';
    
  } elsif( $ntyp eq "Network switch" ) {
    $color='color="red" style="bold"';
    
  } elsif( $ntyp eq "Server chassis" ) {
    $color='color="blue" style="dotted"';
    
  } elsif( $ntyp eq "Network chassis" ) {
    $color='color="lightblue"';
    
  } elsif( $ntyp eq "MediaConverter" ) {
    $color='color="gold"';
    
  } elsif( $ntyp eq "Router" ) {
    $color='color="darkgreen"';
    
  } else {
    #$color='color="pink"';			# Barbie syndrome FTW
    
  }
  
  print '"' . $n . "\" [ $color ];\n";
}

# and print the edges
foreach my $l (@link) {
  # skip empty...
  next if $l->[0] =~ /^\s*$/;

  if( defined( $link{ $l->[0] }{ $l->[4] } ) && $link{ $l->[0] }{ $l->[4] } == $l->[2] ) {
    # skip duplicate
    # if you need warnings... here you can.
  } else {
    print '"', $l->[0], '" -- "', $l->[4], '" [ label="', $l->[2], "\" color=\"grey\"];\n";
    $link{ $l->[0] }{ $l->[4] } = $l->[2];
  }
}

print "\n};\n\n";


DatabaseLoginData.pm

# database login data
package DatabaseLoginData;

our $db_user = "racktables_user";
our $db_pw = "supersecretpassword";
our $db_host = "racktables.example.com";

1;

create_svg.sh

#!/bin/bash
#$Id: create_svg.sh,v 5aa2d11fa948 2012/11/10 10:57:04 grin $
#
# (c) Peter 'grin' Gervai, 2012
# Released under CreativeCommons-Attribution-ShareAlike-3.0-Unported
#
# create .svg from racktables links
# recode the file

# first create the .dot file, and recode it using konwert
# skip recoding if you're happy without accents, screws up svg otherwise
echo "Creating .dot file from the database..."
./dottr.pl | konwert isolatin2-utf8 > map.dot

# dot output is fine, others (like neato or circo) not that useful
echo "Rendering map.svg and map.png..."
dot -Tsvg -o map.svg map.dot
dot -Tpng -o map.png map.dot
 
echo "That's all, folks!"