#!/usr/bin/perl # compute packet stream quality statistics for UDP/RTP from network or captured files(pcap) # use Socket; use IO::Socket qw(:DEFAULT :crlf); use IO::Socket::INET; use IO::Socket::Multicast; use IO::Select; use Net::Pcap; use NetPacket::Ethernet qw(:strip); use NetPacket::IP; use NetPacket::IP qw(:protos); use NetPacket::UDP; use Net::RTP; use Net::RTP::Packet; use Time::HiRes qw/ sleep time tv_interval gettimeofday/; use Data::Dumper; use Carp; use constant MTU => 1580; $DEFAULT_PORT = 1955; my $timeout=1 ; # frac seconds to wait for receiving packets require "newgetopt.pl"; $usage="$0 [option]... [file...|ip/|:port]... -list list flows in files -dump dump data part to file -format [full|pretty] print full numbers foror short pretty numbers -net open network stream rather than file(s) -rtp look forinto rtp headers (default just udp) -mpeg decode mpeg-2 transport stream and look for sequence and mediatime -pcap listen to all multicast groups with pcap at the same time -period seconds - period in seconds between stats output -nperiod number of periodes -last seconds - exit after seconds -packets packets - exit after packets -src source - limit stats to given source -flow_key flow key - limit stats to this flow(src_ip:src_port->dest_ip:dest_port) -flow_no flow no - limit stats to this flow no from -list -flow_min numb - discard flows with less packets that numb -nohead - no headings for batch use -id name - report name instead of flow_key in flows -sum name - make a summary of all flows to name rather than per flow -verbose - more details packet distances and sizes -debug - print debugging info -help/h this message \n"; @opts=('list', 'fullformat', 'net', 'rtp', 'mpeg', 'pcap', 'crude', 'nperiod=s', 'period=s', 'last=s', 'packets=s', 'src=s', 'flow_key=s', 'flow_no=s', 'flow_min=s', 'nohead', 'dump=s', 'format=s', 'id=s', 'sum=s', 'verbose', 'v', 'help', 'h', 'debug'); &NGetOpt(@opts) || die $usage; die $usage if $opt_h or $opt_help; $continous= ! $opt_packets; $opt_v=$opt_v or $opt_verbose; my @streams=@ARGV; my $format = $opt_format || "pretty"; my %hdr=(); my $err=""; my $us=0; # current packet time in us my $nkill=0; my $nint=0; my $n_packets=0; my $endstream=0; $SIG{USR2} = sub {confess "Caught by SIGUSR2"; }; $SIG{INT} = sub { $uninterrupted=0; # return if $nint++ < 1; &display_stats(); die "End after interrupt.\n";exit(0) }; $SIG{KILL} = sub { $uninterrupted=0; # return if $nkill++ < 1; &display_stats(); die "End after kill.\n";exit(0) }; $SIG{ALRM} = sub { # die "No packets received" if $n_packets < 1; # $uninterrupted=0; &display_stats(); $endstream=1; print "endstream=1" if $opt_debug; }; if ($opt_dump){ open DUMP, ">$opt_dump"; } # $wanted_flow = $opt_flow_key; if ($opt_pcap){ # listen in parallell alarm($opt_last) if $opt_last; $uninterrupted=1; $endstream=0; eat_pcap_streams(@streams); &display_stats(); } else { # listen serially foreach $id (@streams) { alarm($opt_last) if $opt_last; $uninterrupted=1; $endstream=0; init_file($id); eat_stream($id); if (! ( $opt_list || $opt_sum)){ &display_stats() if !$endstream; } last if ! $uninterrupted; } } &list_flows if $opt_list; &display_stats() if $opt_sum; exit(0); #------------------------------------------------------------------------------ # unpack uri's to ip-address sub prepare_ip { my $uri=shift; $uri =~ s/^(udp|rtp)://; $uri =~ s#//##; die "Unsupported streaming $1" if $uri =~/^(\w:)/; my $multicast = $uri =~ s/^@//; my ($host, $port)=split(/[\/:]/, $uri); die $usage . "undefined address inn $host" unless (defined $host); $port = $DEFAULT_PORT unless (defined $port); my $address=$host; if ($host !~ /^\d+\.\d+\.\d+\.\d+$/){ # hostname my $padr=gethostbyname($host) || die "Gethostbyname - $host - $padr : $!"; $address=Socket::inet_ntoa($padr); } my @nib=split(/\./,$address); $multicast = $multicast || $nib[0] >= 224 && $nib[0] <= 240; return ($address, $port, $multicast); } sub eat_stream { my $id=shift; $tstart{$id}=[gettimeofday]; if ($opt_net && $opt_rtp){ # Create RTP socket my $elapsed=0; my $t0=0.0; $n_packets=0; my ($address, $port, $multicast) = prepare_ip($id); my $rtp = new Net::RTP( LocalPort=>$port, LocalAddr=>$address ) || die "Failed to create RTP socket: $!"; my $select=IO::Select->new($rtp->fileno); my $tjoined=[gettimeofday]; if ($opt_align){ # align to $opt_period } while ($uninterrupted && !($opt_packets && $n_packets > $opt_packets)) { my $packet; last if ! $uninterrupted || $endstream; if ($select->can_read($timeout) && ($packet = $rtp->recv())){ my $ssrc = $packet->ssrc(); $cur_id=$packet->source_ip . ", $address:$port"; if ( !$file{$cur_id}){ push(@myflows, $cur_id); $file{$cur_id}=$id; } $n_packets++; next if $opt_src and $opt_src ne $cur_id; $tc=[gettimeofday]; # current flow time if( $t0){ $elapsed=tv_interval ( $t0, $tc); } else { $t0 = $tc; $elapsed=0.0; &init_stats($cur_id); } $us=$elapsed*10**6; $tjoined{$cur_id} = $tjoined if !$tjoined{$cur_id}; # No stats for that SSRC yet? &pkt_stats($cur_id, $packet->encode, $elapsed * 10**6, $packet->size()); if ($opt_dump){ print DUMP $packet; } } else { # anticipate interrupted by alarm # warn "Failed to receive $n_packets packets from $address:$port : $!"; # next; #### exit } } # &display_stats(); # $mc->shutdown; # gives error message } elsif ($opt_net) { # udp stream # Create UDP socket my $elapsed=0; my $t0=0.0; $n_packets=0; my ($address, $port, $multicast) = prepare_ip($id); my $mc; if ( $multicast){ $mc = IO::Socket::Multicast->new(LocalPort=>$port, LocalAddr=>$address, ReuseAddr=>1) || die "Could not connect to port $port : $!"; $mc->mcast_add($address) || die "could not join $address:$port : $!"; } else { # unicast $mc = IO::Socket::INET->new( LocalAddr=> $address, LocalPort => $port); } my $tjoined=[gettimeofday]; my $select=IO::Select->new($mc->fileno); if ($opt_align){ # align to $opt_period } while ($uninterrupted && ! $endstream && !($opt_packets && $n_packets > $opt_packets)){ if ($select->can_read($timeout) && $mc->recv($packet, 1560)) { last if ! $uninterrupted || $endstream; $n_packets++; my $ssrc = $mc->peerhost; my $cur_id = $mc->peerhost . ":" . $mc->peerport . "->$address:$port"; if ( !$file{$cur_id}){ push(@myflows, $cur_id); $file{$cur_id}=$id; } next if $opt_src and $opt_src ne $cur_id; $tc=[gettimeofday]; # current flow time if( $t0){ $elapsed=tv_interval ( $t0, $tc); } else { $t0 = $tc; $elapsed=0.0; &init_stats($cur_id); } $us=$elapsed*10**6; $tjoined{$cur_id} = $tjoined if !$tjoined{$cur_id}; # No stats for that SSRC yet? &pkt_stats($cur_id, $packet, $elapsed * 10**6, length($packet)); if ($opt_dump){ print DUMP $packet; } } else { # assume that we got an interrupt # die "Failed to receive packet: $!"; } } # &display_stats(); # $mc->mcast_drop($address); } else { # files my $f=$id; my $pcap; my $tmp_file; my $pcap_file=$f; if ($f =~ /\.gz$|\.Z/){ # gzipped ? $tmp_file="/tmp/pcap$$"; $pcap_file=$tmp_file; system( "/bin/zcat $f > $tmp_file") ; # rc code ? || die "Could not unpack $f to $tmp_file : $!"; } if ($opt_crude){ my $flow_key; open CRUDE, "<", $pcap_file || die "Could not open $f : $err"; while(){ # crude.sourceforge.net if (($fid, $seq, $src, $dst, $tx, $rx, $size)= /ID=(\d+)\s+SEQ=(\d+)\s+SRC=([\d.:]+)\s+DST=([\d.:]+)\s+Tx=([\d.,]+)\s+Rx=([\d.,]+)\s+SIZE=(\d+)/){ $flow_key="$src->$dst"; $flow_pkts{$flow_key}++; if ($flow_pkts{$flow_key} <= 1) { # new flow push(@flows, $flow_key); # $flow_no++; $file{$flow_key}=$f; $source{$flow_key}=$src; } next if $opt_list; # just count packets per flow next if $opt_flow_key && $flow_key !~ $wanted_flow; next if $opt_flow_no && $opt_flow_no !~ /\b$flow_no\b/; if ($flow_pkts{$flow_key} <= 1) { # &init_stats($flow_key); push (@myflows, $flow_key); } $us=($rc-$tc)*10^6; # us &pkt_stats($flow_key, , $us, $size); } } } else { if ($pcap=Net::Pcap::open_offline($pcap_file, \$err)){ # my $select=IO::Select->new(Net::Pcap::fileno($pcap)); until ( ! $uninterrupted || $endstream || ($opt_packets && $n_packets > $opt_packets) || ! (my $pkt=Net::Pcap::next($pcap, \%hdr) ) ) { eat_pcap($f, $pcap, $pkt); $n_packets++; } } else { # open failed if ($format ne "full"){ # always put out data when format full die "Could not open $f : $err"; } } } undef $t0; unlink $tmpfile if $tmp_file; } # of files } # of eat_stream sub eat_pcap_streams{ # open groups and listen on pcap my @streams=@_; my $filter="( "; my $mc = IO::Socket::Multicast->new(LocalPort=>$DEFAULT_PORT, ReuseAddr=>1) || die "Could not connect to port $port : $!"; foreach $id (@streams){ # listen to all groups my ($host, $port) = split(/[\/:]/, $id); die $usage . "undefined address inn $id" unless (defined $host); $port = $DEFAULT_PORT unless (defined $port); my $address=$host; if ($host !~ /^\d+\.\d+\.\d+\.\d+$/){ # hostname my $padr=gethostbyname($host); $address=Socket::inet_ntoa($padr); } $mc->mcast_add($address) || die "could not join $address:$port : $!"; $filter .= " or " if $filter ne "( "; $filter .= "host $address"; } $tjoined{$f}=[gettimeofday]; $filter .= " )"; # $filter .= " and ip multicast"; # $filter ="ip"; my ($net, $netmask, $filter_t, $err, $dev); # printf $filter; $dev = Net::Pcap::lookupdev(\$err) || die "Could not lookupdev $dev : $err : $!"; my $r = Net::Pcap::lookupnet( $dev, \$net, \$netmask, \$err); die "Failed lookupnet for $dev : $r : $err : $!" if $r != 0; $pcap=Net::Pcap::open_live( $dev, 100, 1, 0,\$err) || die "Could not open $dev : $err"; $r=Net::Pcap::compile($pcap, \$filter_t, $filter, 0, $netmask); die "Could not compile filter : $r : $filter : $!)" if $r != 0; $r=Net::Pcap::setfilter($pcap, $filter_t); die "Could not set pcap filter : $r : $filter : $! " if $r != 0 ; my $select=IO::Select->new( Net::Pcap::fileno($pcap)); while ($select->can_read($timeout) && (my $pkt=Net::Pcap::next($pcap, \%hdr)) ) { # get all packets last if ! $uninterrupted || $endstream; eat_pcap("pcap", $pcap, $pkt); $n_packets++; last if $opt_packets && $n_packets > $opt_packets; } } sub eat_pcap { # process pcap packets my ($f, $pcap, $pkt)=@_; my $ip=null; my $rtp = new Net::RTP::Packet(); # spare rtp obj if (Net::Pcap::datalink($pcap) == 1){ # ethernet $ip = NetPacket::IP->decode(eth_strip($pkt)); } elsif (Net::Pcap::datalink($pcap) == 113) { # DLT_LINUX_SLL my ($head, $data)=unpack('a16a*', $pkt); $ip = NetPacket::IP->decode($data); } else { die "Invalid link layer type : ".Net::Pcap::datalink($pcap); } next if $ip->{proto} != IP_PROTO_UDP; my $udp = NetPacket::UDP->decode($ip->{data}); my $dlen = $ip->{len} - $ip->{hlen}*4; my $flow_key; if ($opt_sum){ $flow_key=$opt_sum; } else { $flow_key=sprintf "%s:%d->%s:%d", $ip->{src_ip}, $udp->{src_port}, $ip->{dest_ip}, $udp->{dest_port}; } $flow_pkts{$flow_key}++; if ($flow_pkts{$flow_key} <= 1) { # new flow push(@flows, $flow_key); # $flow_no++; $file{$flow_key}=$f; $source{$flow_key} =sprintf "%s:%d", $ip->{src_ip}, $udp->{src_port}; } next if $opt_list; # just count packets per flow next if $opt_flow_key && $flow_key !~ $wanted_flow; next if $opt_flow_no && $opt_flow_no !~ /\b$flow_no\b/; if ($flow_pkts{$flow_key} <= 1) { # &init_stats($flow_key); push (@myflows, $flow_key); # printf "$flow_key\n"; } $tc= [$hdr{tv_sec}, $hdr{tv_usec}]; if (! defined($t0)) { $t0=$tc; } $tjoined{$f}=$tc if !$tjoined{$f}; $tjoined{$flow_key} = $tjoined{$f} if !exists($tjoined{$flow_key}); $us = tv_interval ( $t0, $tc) * 10**6 ; # us relative packet time # fixate on first flow to appear # if ( $first_port) { # next if $udp->{dest_port} != $first_port; # } else { # $first_port = $udp->{dest_port}; # } &pkt_stats($flow_key, $udp->{data}, $us, $dlen); } sub init_stats { my $f=shift; $pinterval=0; $npkt{$f}=$sumbyte{$f}=$ssbyte{$f}=0; $first_sec=0; delete $pmtime{$f}; delete $pus{$f} ; delete $first_us{$f}; delete $last_us{$f}; delete $prev_1s{$f}; delete $prev_100ms{$f}; delete $per1s{$f}; delete $per100ms{$f}; delete $sum1s{$f}; delete $sum100ms{$f}; $n100ms{$f}=0; $sum100ms{$f}=0; $ss100bps{$f}; $akk100ms{$f}=0; $sumbyte{$f}=$sumps{$f}=0; # sum byte per second $maxbps{$f}=0; $max100ms{$f}=0; $dup{$f}=$lost{$f}=$late{$f}=0; $maxbyte{$f}=0; $minbyte{$f}=2**31; $sumgap{$f}=$ssgap{$f}=0; $ngap{$f}=0; $maxgap{$f}=0, $mingap{$f}=2**63-5; $njitter{$f}=$sumjitter{$f}=$ssjitter{$f}=0; $maxjitter{$f}=-2**63-5; $minjitter{$f}=2**63-5; delete $est_jitter{$f}; delete $pest_jitter{$f}; $first_port = 0; $n_seq=-1; delete $tstart{$f}; delete $tend{$f}; } sub init_file { undef $t0; my $f=shift; undef $tjoined; delete $tjoined{$f}; delete $setuptime{$f}; delete($joined{$file{$f}}); delete $file{$f}; @myflows=() if !$opt_sum; $flow_pkts{$f}=0; # new flow on new file undef $pus; undef $psec; delete($pmtime{$f}); delete($pseq_num{$f}); delete($pus{$f}); } sub pkt_stats { my $f = shift; # file or stream name my $packet = shift; my $us = shift; my $dlen = shift; my $rtp = new Net::RTP::Packet(); if (! exists($tstart{$f})){ $tstart{$f} = $tc; $first_us{$f}=$prev1s_us{$f}=$prevt100ms_us{$f}=$us; if (! exists($setuptime{$f}) ){ if ( $pinterval){ $setuptime{$f}=0; } else { $setuptime{$f}=tv_interval ( $tjoined{$f}, $tstart{$f}) * 10**3; } printf "setup $f : %.3f\n", $setuptime{$f} if $opt_debug; } } $tend{$f} = $tc; $last_us{$f}=$us; # next if $dlen < 20; # typically small background noice packets added $npkt{$f}++; $sumbyte{$f}+=$dlen; $sum1s{$f}+=$dlen; $sum100ms{$f}+=$dlen; $ssbyte{$f}+=$dlen**2; $maxbyte{$f}= $dlen if $dlen > $maxbyte{$f}; $minbyte{$f}= $dlen if $dlen < $minbyte{$f}; $sumps{$f}+=$dlen; # sum per second if ($opt_dump){ print DUMP $packet; } if ($opt_rtp){ if ( $rtp->decode($packet) ) { #assume RTP # printf "RTP: %10s %5d %5d\n", $rtp->ssrc(), $rtp->timestamp(), $rtp->size(); # $dlen = $rtp->size(); } else { print "??? $f # $npkt{$f} $dlen B\n"; if ($opt_debug){ my $n=0; foreach $w( unpack("C*", $packet)) { printf "%0X",$w; if ($n > 0){ print " " if $n % 4 == 0; print "\n" if $n % 32 == 0; } $n++; } printf "\n"; } next; } # Calculate next number in sequence $n_seq = $rtp->seq_num()+1; if ($n_seq > 65535) { $n_seq=0; } $mtime=$rtp->timestamp() * &time_unit($rtp->payload_type); # mediatime in us } if (defined($pus{$f})) { $gap = $us - $pus{$f}; # packet time gap $ngap{$f}++; $sumgap{$f} += $gap; $ssgap{$f} += $gap**2; $maxgap{$f}= $gap if $gap > $maxgap{$f}; $mingap{$f}= $gap if $gap < $mingap{$f}; if ($opt_rtp){ my $seq_diff= $rtp->seq_num - $pseq_num{$f}; # printf "%0d-%0d, ", $seq_diff, $n_seq - $rtp->seq_num; if ($seq_diff != 1) { # printf "seq %d,", $rtp->seq_num; if ($seq_diff == 0) { # Duplicated $dup{$f} ++; } elsif ($seq_diff < 0) { # Out Of Order $late{$f}++; # $lost{$f}--; } else { # Lost $lost{$f}+= $seq_diff - 1; $nloss{$f}++; $sumloss{$f} += $seq_diff; $ssumloss{$f} += $seq_diff**2; $maxloss{$f} = $seq_diff if $seq_diff > $maxloss{$f}; } } elsif ( $mtime <= $pmtime{$f}){ $ntimeerr{$f}++; } else { # the jitter can be measured # compute jitter relative to previous packet $mgap= ($mtime - $pmtime{$f} ); # media time gap in samples 8khz=us $jitter=abs($gap - $mgap); #ok compute jitter relative to start #ok $mgap= ($mtime - $first_mtime); # media time gap in us #ok $jitter=abs($us - $mtime - $first_jitter); $njitter{$f}++; $sumjitter{$f} += $jitter; $ssjitter{$f} += $jitter**2; $maxjitter{$f}= $jitter if $jitter > $maxjitter{$f}; $minjitter{$f}= $jitter if $jitter < $minjitter{$f}; # rfc 3550 if (defined($est_jitter{$f})){ $est_jitter{$f} = $est_jitter{$f} + ( $jitter - $est_jitter{$f} )/16; } else { # inital value $est_jitter{$f} = $jitter; } printf "%5d %6d %5d %5d %5d %5d %5d\n",$rtp->seq_num,$us,$rtp->timestamp(), $gap, $mgap, $jitter, $first_jitter if $opt_debug; } } elsif ($opt_mpeg){ # incomplete code under exploration # meg transport stream layout http://en.wikipedia.org/wiki/Transport_stream my ($flag, $b2, $b3, $rest)= unpack "H2n1C1C*", $packet; # my ($ts_err, $payload, $ts_pri)=split( //, unpack "B3", $b2); # does not work my ($ts_err, $payload, $ts_pri) = ( $b2 & 0x8000 , $b2 & 0x4000, $b2 & 0x2000); my $pid= $b2 & 0x1F00; # my ($scramble1, $scramble2, $adapt, $data) = split( //, unpack "B4", $b3); # not working my ($scramble1, $scramble2, $adapt, $data) = ( $b3 & 0x80, $b3 & 0x40, $b3 & 0x20, $b3 & 0x10); my $contin= $b3 & 0x0F; # printf "%5d %5s %5d %5X %5d %5d %s\n", $npkt{$f}, $flag, $pid, $b2, $data, $contin, unpack("H*",$packet) # if $payload > 0 ; # $contin == 13; #$ts_pri > 0 ; # $b2 > 256; if ($payload && $pid > 0){ # PES follows http://en.wikipedia.org/wiki/Packetized_Elementary_Stream my ($ts_prefix, $code, $id, $pes_lth, $pes_rest )= unpack("H8H6C1C2C*", $packet); # printf "%5d %5s %5d %5X %5d %5d %s\n", $npkt{$f}, $flag, $pid, $code, $data, $id, unpack("H*",$packet); if ($code == 0x000001){ if ( $id >= 0xE0 ){ # video PES-header my ($ts_prefix, $pes_head, $pes_opt, $head_lth)=unpack("H8H6C2C1", $packet); if ($pes_opt & 0x0080){ # PTS ind printf "%5d %5s %5d %5X %5d %5d %s\n", $npkt{$f}, $flag, $pid, $id, $pes_lth, $head_lth, unpack("H*",$packet); } } } } if ($adapt == 1 ) { # adaption field coming my ($length, $aflags, $rest2)=unpack "C1C1C*", $rest; my ($dif, $raif, $espif, $pcrf, $opcrf, $splicef, $tpdf, $afextf)=split(//, $aflags); if ($pcrf == 1){ ; } } } &burst($f); } if ($opt_period){ $interval=int($us/10**6 / $opt_period); if ($interval > $pinterval){ &display_stats() ; $pinterval=$interval; $pinterval{$f}=$interval; $endstream = $opt_nperiod && $interval >= $opt_nperiod; } } $pus=$us; $psec=$sec; $pmtime{$f}=$mtime if $opt_rtp; $pseq_num{$f}=$rtp->seq_num if $opt_rtp; $pus{$f}=$us; } sub burst { my $f=shift; my $span=$last_us{$f}-$first_us{$f}; my $per1s=int($span/10**6); my $per100ms=int($span/10**5); if ( $per1s > $per1s{$f} ){ # period expired my $per=($last_us{$f}-$prev_1s{$f})/10**6; if ($per>0){ my $bps = $sum1s{$f} * 8 / $per; $maxbps{$f} = $bps if $bps > $maxbps{$f}; $per1s{$f} = $per1s; $prev_1s{$f} = $last_us{$f}; $sum1s{$f}=0; } } if ( $per100ms > $per100ms{$f} ){ # period expired my $per=($last_us{$f}-$prev_100ms{$f})/10**6; if ($per>0){ my $bps = $sum100ms{$f} * 8 / $per; $max100ms{$f} = $bps if $bps > $max100ms{$f}; $n100ms{$f}++; $ss100bps{$f}+= $bps**2; $akk100ms{$f}+=$bps; $per100ms{$f} = $per100ms; $prev_100ms{$f} = $last_us{$f}; $sum100ms{$f}=0; # print "$bps "; } } } sub display_stats{ # my $f = shift; my $media="file"; # $media = "host, group" if $opt_net; $media = "host:port" if $opt_net; @myflows=@streams if ($#myflows < 0); # no response foreach $f (sort @myflows) { my $id=$f; next if $npkt{$f} < $opt_flow_min; my $source=$f; $source=$source{$f} if ! $opt_v && $source{$f} ne ""; $source=$opt_id if $opt_id; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($tstart{$f}->[0]); my $time=sprintf "%02d:%02d:%02d", $hour, $min, $sec; if ($format eq "full"){ $time.=sprintf ".%06d", $tstart{$f}->[1]; } else { my $time=sprintf "%02d:%02d:%02d", $hour, $min, $sec; } my $date=sprintf "%4d-%02d-%02d", $year+1900, $mon+1, $mday; my $span=&tv_interval($tstart{$f}, $tend{$f}); # second &burst($f); # in case a second is interrupted # printf "dus %d min %.2f\n", $us-$pus, $max100ms{$f}; # $max100ms{$f}=-1 if $max100ms{$f} > 2**61; # $maxbps{$f}=-1 if ! $maxbps{$f} > 0; if ($opt_v) { if ($date ne $pdate) { if (! $opt_nohead){ print "\nDate $date packet size(byte) packet distance(ms) "; print " time sequence loss packet jitter(ms) " if $opt_rtp; print " thrust(bps) source\n"; printf "%-8s %6s %6s %4s%4s %4s %4s %4s %6s %4s %4s %4s %4s", "time","span", "setup", "numb ", "avg", "sdv", "min", "max", "numb", "avg", "sdv", "min", "max"; printf " %5s %5s %5s %5s %5s %5s %5s", "stamp", "dup", "late", "lost", "gaps", "avg", "sdv", if $opt_rtp; printf " %6s %4s %4s %4s %4s %4s ", "numb", "avg", "sdv", "min", "max", "rfc" if $opt_rtp; printf " %5s %5s %5s %5s %-15s\n", "avg", "1s", "100ms", "sdv", $media; } } if ( $npkt{$f} >= 0 && $ngap{$f}>= 0) { if (! $opt_rtp) { $minjitter{$f}=0; $maxjitter{$f}=0;} if ($ngap{$f} < 1 ){ $mingap{$f}=0; $maxgap{$f}=0; $max100ms{$f}=0; $maxbps{$f}=0; } # my $dup=&lesbar($dup{$f}+$ntimeerr{$f}); # $dup .= "T", if $ntimeerr{$f} > 0; # $dup .= "S", if $dup{$f} > 0; printf "%8s %6s %6s %8s", $time, &lesbars($span,3), &lesbars($setuptime{$f},3), &lesbar($npkt{$f},8); my $n=$npkt{$f}; $n=1 if $npkt{$f} <1; # divide by zero protection printf " %4s %4s %4d %4d ", &lesbar($sumbyte{$f}/$n,4), &lesbar(sdv($npkt{$f}, $sumbyte{$f}, $ssbyte{$f})), $minbyte{$f}, $maxbyte{$f}; printf " %6s %4s %4s %4s %4s", &lesbar($ngap{$f},6), &lesbars( avg( $sumgap{$f}, $ngap{$f}) / 1000 ), &lesbars(sdv($ngap{$f}, $sumgap{$f},$ssgap{$f})/1000), &lesbars($mingap{$f}/1000), &lesbars($maxgap{$f}/1000); printf " %5s %5s %5s %5s %5s %5s %5s", &lesbar($ntimeerr{$f}), &lesbar($dup{$f}), &lesbar($late{$f}), &lesbar($lost{$f}), &lesbar($nloss{$f}), &lesbar( avg( $sumloss{$f}, $nloss{$f}) ), &lesbar( sdv($lost{$f}, $sumloss{$f}, $ssumloss{$f}) ) if $opt_rtp; printf " %6s %4.1f %4.1f %4.1f %4.1f %4.1f ", &lesbar($njitter{$f}), avg($sumjitter{$f}, $njitter{$f})/1000, sdv($njitter{$f}, $sumjitter{$f}, $ssjitter{$f})/1000, $minjitter{$f}/1000, $maxjitter{$f}/1000, $est_jitter{$f}/1000 if $opt_rtp; printf " %5s %5s %5s %5s %s\n", &lesbar( avg( $sumbyte{$f}*8, $span ) ), &lesbar($maxbps{$f}), &lesbar($max100ms{$f}), &lesbar(sdv($n100ms{$f},$akk100ms{$f}, $ss100bps{$f})), $source; } else { # not reached printf "%-8s %6s %6s %8s %4s %4d %4d %4d %6d %4s %4s %4s %4s %5d %5d %5d %5d %s\n", $time, 0, 0, &lesbar($npkt{$f},4), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, $source if $f ne "" && $npkt{$f}>=0; } } else { if ($date ne $pdate) { if (! $opt_nohead){ print "\nDate $date packet size(byte) "; if ($opt_rtp){ print "jitter(ms) " ; } else { print "gap(ms) " ; } print "thrust(bps) source\n"; printf "%-8s %8s %4s %4s %4s %4s %4s", "time","numb", "dup", "late", "lost", "avg", "sdv"; printf " %5s %5s %5s %5s", "avg", "sdv", "min", "max" ; # if $opt_rtp; printf " %5s %5s %5s %s\n", "avg", "1s", "100ms", $media; } } if ( $npkt{$f}>= 0 && $ngap{$f}>= 0) { my @jitterstat=(0,0,0,0); my $n=$npkt{$f}; $n=1 if $npkt{$f} <1; # divide by zero protection if ($opt_rtp){ @jitterstat= (&lesbars($sumjitter{$f}/$njitter{$f}/1000), &lesbars(sdv($njitter{$f}, $sumjitter{$f}, $ssjitter{$f})/1000), &lesbars($minjitter{$f}/1000), &lesbars($maxjitter{$f}/1000)); } else { @jitterstat=( &lesbars( avg($sumgap{$f}, $ngap{$f}) / 1000), &lesbars(sdv($ngap{$f}, $sumgap{$f},$ssgap{$f})/1000), &lesbars($mingap{$f}/1000), &lesbars($maxgap{$f}/1000) ); } printf "%8s %8d %4d %4d %4d %4d %4.1f", $time, $npkt{$f}, $dup{$f}+$ntimeerr{$f}, $late{$f}, $lost{$f}, $sumbyte{$f}/$n, sdv($n, $sumbyte{$f}, $ssbyte{$f}); printf " %5s %5s %5s %5s",@jitterstat; printf " %5s %5s %5s %s\n", &lesbar( avg($sumbyte{$f}*8, $span)), &lesbar($maxbps{$f}), &lesbar($max100ms{$f}), $source; } else { printf "%-8s %8s %68s %s\n",$time, lesbar($npkt{$f},4),"", $source if $f ne "" && $npkt{$f}>0; } } $opt_nohead=1; $pdate=$date; $flow_pkts{$f}=0; &init_stats($f); } } sub avg { my ($sum, $n)=@_; return 0 if $n <= 0; return $sum/$n; } sub sdv { my ($n, $sum, $sumsq)=@_; if ($n > 1) { return sqrt( abs($sumsq/$n - ($sum/$n)**2)); } else { return 0; } } sub lesbar{ my ($n, $figs)=@_; $figs = 3 if ! $figs; return sprintf ("%.1f", $n) if $format ne "pretty"; @dekade=("", "K", "M", "G", "T", "P"); for ($d=0; $d<=$#dekade; $d++){ $dekade=10**(3*$d); my $nd=$n / $dekade; if ( $nd < 10**$figs){ # 3 siffer OK if ( $nd > 100 || ($n < 1000 && $n == int($n))){ return (sprintf "%d", $nd) . $dekade[$d]; } else { return sprintf "%.1f%s", $nd, $dekade[$d]; } } } return sprintf "%E", $n; } sub lesbars { my ($n, $figs)=@_; return &lesbar($n, $figs) if $format ne "pretty"; if ($n > 1000){ my $num = &lesbar($n /1000, $figs) . "s"; $num=~s/ //; return $num; } else { return &lesbar($n, $figs); } } # compute timeunits in mediatime from rfc 3551 # limited to static payload-types sub time_unit{ my $payload_type=shift; $sample_freq{0}=8000; $sample_freq{3}=8000; $sample_freq{4}=8000; $sample_freq{5}=8000; $sample_freq{6}=16000; $sample_freq{7}=8000; $sample_freq{8}=8000; $sample_freq{9}=8000; $sample_freq{10}=44100; $sample_freq{11}=44100; $sample_freq{12}=8000; $sample_freq{13}=8000; $sample_freq{14}=90000; $sample_freq{15}=8000; $sample_freq{16}=11025; $sample_freq{17}=22050; $sample_freq{18}=8000; $sample_freq{726}=8000; $sample_freq{726}=8000; $sample_freq{726}=8000; $sample_freq{726}=8000; $sample_freq{729}=8000; $sample_freq{729}=8000; $sample_freq{25}=90000; $sample_freq{26}=90000; $sample_freq{28}=90000; $sample_freq{31}=90000; $sample_freq{32}=90000; $sample_freq{33}=90000; $sample_freq{34}=90000; return 10**6/($sample_freq{$payload_type}||8000); } sub list_flows { my $n=0; printf "\n\n%4s %-50s %10s %s\n", "No", " Flow key", "Packets", "File" if ! $opt_nohead;; foreach $f ( @flows){ printf "%4d %-50s %10d %s\n", $n+++1, $f, $flow_pkts{$f}, $file{$f}; } $opt_nohead=1; }