#!/usr/bin/perl -w ########################################### # Nettop - Top-like TCP Monitor # Mike Schilli, 2007 (m@perlmeister.com) ########################################### use strict; use Curses::UI::POE; use List::Util qw(max); my ($STATS, $CONNS); my $netstat = "netstat"; my $REFRESH_RATE = 1; use PoCoRunner; PoCoRunner->new( command => $netstat, args => "-s", data => \my $stats_data, interval => 1, ); PoCoRunner->new( command => $netstat, args => "-put", data => \my $conns_data, interval => 1, ); my $CUI = Curses::UI::POE->new( -color_support => 1, inline_states => { _start => sub { $poe_kernel->delay('wake_up', $REFRESH_RATE)}, wake_up => \&wake_up_handler, }); my $WIN = $CUI->add(qw( win_id Window )); my $TOP = $WIN->add(qw( top Label -y 0 -width -1 -paddingspaces 1 -fg white -bg blue ), -text => top_text()); my $LBOX = $WIN->add(qw( lb Listbox -padtop 1 -padbottom 1 -border 1 ), ); my $BOTTOM = $WIN->add(qw( bottom Label -y -1 -width -1 -paddingspaces 1 -fg white -bg blue ), -text => "TCP Watcher v1.0" ); $CUI->set_binding(sub { exit 0; }, "q"); $CUI->mainloop; ########################################### sub wake_up_handler { ########################################### # Re-enable timer $poe_kernel->delay('wake_up', $REFRESH_RATE); data_refresh(); $TOP->text(top_text()); $TOP->draw(); my $state_fmt = col_fmt([map $_->{state}, @$CONNS], 8); my $prog_fmt = col_fmt([map $_->{prog}, @$CONNS], 20); my $rem_fmt = col_fmt([map $_->{remote}, @$CONNS], 32); my $loc_fmt = col_fmt([map $_->{local}, @$CONNS], 20); my @lines = map { $state_fmt->($_->{state}) . " " . $prog_fmt->($_->{prog}) . " " . $rem_fmt->($_->{remote}) . " " . $loc_fmt->($_->{local}) . " " . ""; } sort conn_sort @$CONNS; $LBOX->{-values} = [@lines]; $LBOX->{-labels} = { map { $_ => $_ } @lines }; $LBOX->draw(1); } ########################################### sub top_text { ########################################### my $ip = $STATS->{Ip}; my $tcp = $STATS->{Tcp}; return sprintf "Packets rcvd:%s sent:%s TCPopen " . "active:%s passive:%s", $ip->{'total packets received'}, $ip->{'requests sent out'}, $tcp->{'active connections openings'}, $tcp->{'passive connection openings'}; } ########################################### sub data_refresh { ########################################### $STATS = stats_parse($stats_data); $CONNS = conns_parse($conns_data); } ########################################### sub stats_parse { ########################################### my($output) = @_; my $section; my $data = {}; my $key = qr/\w[\w\s]+/; for (split /\n/, $output) { if( /($key):$/ ) { $section = $1; next; } elsif( /($key): (\d+)/ ) { $data->{$section}->{$1} = $2; } elsif( /(\d+)\s+($key)/ ) { $data->{$section}->{$2} = $1; } else { die "Cannot parse stats line '$_'"; } } return $data; } ########################################### sub conn_sort { ########################################### return -1 if $a->{state} eq "ESTABLISHED"; return 1 if $b->{state} eq "ESTABLISHED"; return 0; } ########################################### sub col_fmt { ########################################### my($cols, $max_space) = @_; my $max_len = max map { length $_ } @$cols; $max_len = $max_space if $max_len > $max_space; return sub { return sprintf("%${max_len}s", substr(shift, 0, $max_len)); }; } ########################################### sub conns_parse { ########################################### my($output) = @_; my $data = []; for (split /\n/, $output) { my($proto, $rec, $snd, $local, $remote, $state, $prog) = split ' ', $_; next if $proto ne "tcp"; push @$data, { local => $local, remote => $remote, state => $state, prog => $prog }; } return $data; }