package Net::Traces::SSFNet;

use strict;
use warnings;
use Carp;

=head1 NAME

Net::Traces::SSFNet - Analyze traces generated by SSFNet

=head1 SYNOPSIS

 use Net::Traces::SSFNet qw( droptail_record_player droptail_record_plotter );

 $Net::Traces::SSFNet::PRINT_EXACT_DECIMAL_DIGITS = 0;
 $Net::Traces::SSFNet::SHOW_SOURCES = 1;
 $Net::Traces::SSFNet::SHOW_STATS = 0;

 # Use with traces created by either
 # SSF.Net.droptailQueueMonitor_1 or SSF.Net.droptailQueueMonitor_2
 #
 droptail_record_player('q.trace', 'text.output', 'some_stream_id.0');

 # Use with traces created by SSF.Net.droptailQueueMonitor_1
 #
 droptail_record_plotter('q.trace', 'some_stream_id.0', 'drops', 'pkts', 'av_qlen');

 # Use with traces created by SSF.Net.droptailQueueMonitor_2
 #
 droptail_record_plotter('q.trace', 'some_stream_id.0', 'drops', 'pkts', 'sumpkts', 'sumdrops');

=cut

require Exporter;

our @ISA = qw( Exporter );

our @EXPORT = qw( );

our @EXPORT_OK = qw(
		    droptail_assert_input
		    droptail_assert_output
		    droptail_record_player
		    droptail_record_plotter
		   );

our $VERSION = '0.02';

my %supported_record_types =
  (
   'SSF.Net.QueueRecord_1'       => 'SSF.Net.droptailQueueMonitor_1',
   'SSF.Net.QueueProbeIntRecord' => 'SSF.Net.droptailQueueMonitor_1',
   'SSF.Net.QueueRecord_2'       => 'SSF.Net.droptailQueueMonitor_2',
  );

=head1 ABSTRACT

Net::Traces::SSFNet can analyze traces created by L<Scalable Simulator
Framework Network Models|"SEE ALSO">. It efficiently emulates in Perl
the functionality provided by Java-based, SSFNet-bundled trace
analyzers, and adds new features, including allowing for finer
granularity in the processed output.

=head1 INSTALLATION

See perlmodinstall.

=head1 DESCRIPTION

SSF, the Scalable Simulation Framework, is a public-domain standard
for discrete-event simulation of large, complex systems in Java and
C++. SSFNet is a collection of models used for simulating
telecommunication networks. The main goal of this module to ease the
analysis of traces produced by L<SSFNet|"SEE ALSO">.

Net::Traces::SSFNet version 0.02 can analyze traces generated by
SSF.Net.droptailQueueMonitor_1 and SSF.Net.droptailQueueMonitor_2.

=head2 Analyzing SSF.Net.droptailQueueMonitor traces

Net::Traces::SSFNet can analyze traces created by 
SSF.Net.droptailQueueMonitor_1 and SSF.Net.droptailQueueMonitor_2,
effectively L<replicating|"droptail_record_player"> the functionality
of SSF.Net.droptailRecordPlayer_1 and SSF.Net.droptailRecordPlayer_2.

To replicate the functionality of either

 java SSF.Net.droptailRecordPlayer_1 qlog.0 some_stream_id.0

or

 java SSF.Net.droptailRecordPlayer_2 qlog.0 some_stream_id.0


use the following code:

 use Net::Traces::SSFNet qw( droptail_record_player );

 $Net::Traces::SSFNet::PRINT_EXACT_DECIMAL_DIGITS = 0;
 droptail_record_player( 'qlog.0', *STDOUT, 'some_stream_id.0');

Notice that you do not have to specify what kind of records are
contained in the trace. In fact, a trace may contain records created
from both SSF.Net.droptailQueueMonitor's.

=head2 Finer granularity

Although both SSF.Net.droptailQueueMonitor's capture simulation events
using 64-bit C<double>s, the SSFNet-bundled trace processing utilities
(SSF.Net.droptailRecordPlayer_1 and SSF.Net.droptailRecordPlayer_2)
use 3 decimal digits when generating the processed
output. Consequently, the text output is limited to millisecond
granularity.

This can be an issue when events occur in sub-millisecond intervals:
the original SSFNet record players do not carry this information in
the text output. The following example might make the issue more
clear. Remember that each node in a network graph is uniquely
identified via a I<Network, Host, Interface> (NHI) string (see
http://www.ssfnet.org/InternetDocs/ssfnetDMLReference.html#addresses). Suppose
that NHI 4(0) is sampled every 0.1 ms and NHI 4(1) every 1 ms.  When
SSF.Net.droptailRecordPlayer_2 processes the trace file, it will
generate something like this

 50.805 4(0) sumpkts 186 sumdrops 0 pkts 0 drops 0
 50.805 4(0) sumpkts 187 sumdrops 0 pkts 1 drops 0
 50.805 4(0) sumpkts 187 sumdrops 0 pkts 0 drops 0
 50.805 4(0) sumpkts 187 sumdrops 0 pkts 0 drops 0
 50.806 4(0) sumpkts 188 sumdrops 0 pkts 1 drops 0
 50.806 4(0) sumpkts 188 sumdrops 0 pkts 0 drops 0
 50.806 4(0) sumpkts 189 sumdrops 0 pkts 1 drops 0
 50.806 4(0) sumpkts 189 sumdrops 0 pkts 0 drops 0
 50.806 4(0) sumpkts 190 sumdrops 0 pkts 1 drops 0
 50.806 4(1) sumpkts 51 sumdrops 0 pkts 0 drops 0

while L<droptail_record_player()|"droptail_record_player"> will
generate

 50.8051 4(0) sumpkts 186 sumdrops 0 pkts 0 drops 0
 50.8052 4(0) sumpkts 187 sumdrops 0 pkts 1 drops 0
 50.8053 4(0) sumpkts 187 sumdrops 0 pkts 0 drops 0
 50.8054 4(0) sumpkts 187 sumdrops 0 pkts 0 drops 0
 50.8055 4(0) sumpkts 188 sumdrops 0 pkts 1 drops 0
 50.8056 4(0) sumpkts 188 sumdrops 0 pkts 0 drops 0
 50.8057 4(0) sumpkts 189 sumdrops 0 pkts 1 drops 0
 50.8058 4(0) sumpkts 189 sumdrops 0 pkts 0 drops 0
 50.8059 4(0) sumpkts 190 sumdrops 0 pkts 1 drops 0
 50.806 4(1) sumpkts 51 sumdrops 0 pkts 0 drops 0

provided that L<"$PRINT_EXACT_DECIMAL_DIGITS"> is set.

=head2 Improved Performance

L<droptail_record_player()|"droptail_record_player"> processes a queue
trace generated by either SSF.Net.droptailQueueMonitor_1 or
SSF.Net.droptailQueueMonitor_2 significantly faster that
SSF.Net.droptailRecordPlayer_1 or SSF.Net.droptailRecordPlayer_2,
respectively.

=head2 Additional functionality

Use L<droptail_record_plotter()|"droptail_record_plotter"> to process
a queue trace and generate text files ready for plotting using
I<gnuplot>, I<xgraph>, or even a spreadsheet application.

=head1 VARIABLES

Net::Traces::SSFNet uses the following variables to control
information generation. None is exported.

=head2 $PRINT_EXACT_DECIMAL_DIGITS

This variable is set by default in order to achieve L<finer
granularity| "Finer granularity"> in the processed output. If you want
to mimic the behavior of SSF.Net.droptailRecordPlayer_1 and
SSF.Net.droptailRecordPlayer_2 use

 $Net::Traces::SSFNet::PRINT_EXACT_DECIMAL_DIGITS = 0;

=head2 $SHOW_SOURCES

If $SHOW_SOURCES is set, droptail_record_player() and
droptail_record_plotter() print to STDERR the types of records and
traffic sources (NHI) found in the trace. For example, you may see
something like this:

 Trace contains records from NHI 4(2)
 Trace contains records of type "SSF.Net.QueueRecord_2"
 Trace contains records from NHI 4(1)
 Trace contains records from NHI 4(0)

By default, no such information is sent to STDERR.

=head2 $SHOW_STATS

If $SHOW_STATS is set, droptail_record_player() and
droptail_record_plotter() display trace processing statistics on
STDERR. For example, you may see something like this:

 {Player processed 113776 records, 1820393 bytes in 7.05 seconds (252 KB/s)}

This variable is set by default. If you want to suppress displaying
the statistics use

 $Net::Traces::SSFNet::SHOW_STATS = 0;

=cut

our $PRINT_EXACT_DECIMAL_DIGITS = 1;
our $SHOW_SOURCES = 0;
our $SHOW_STATS = 1;

=head1 FUNCTIONS

=head2 droptail_assert_input

 droptail_assert_input LIST

This function asserts that the input FILEHANDLE is valid and open
before the real trace processing begins processing. LIST is expected
to have up to two elements: IN and STREAM_ID. The queue trace IN may
be either an open FILEHANDLE or a I<filename>. STREAM_ID, if
specified, must match the stream ID encoded in the queue trace file.

droptail_assert_input() verifies that IN is open for reading and
includes a valid preamble. If IN is not specified, it defaults to
STDIN.

droptail_assert_input() returns a list containing the input
FILEHANDLE, and the actual stream ID found in the queue trace file.

=cut

sub droptail_assert_input {

  my ( $in, $stream_id ) = @_;

  # The following makes sure that $in is an open FILEHANDLE: If $in
  # was not provided, use STDIN. If $in is a filename, attempt to open
  # it for input. Otherwise, use $in as-is.
  #
  if ( not defined $in ) {
    $in = \*STDIN;
    carp 'No input FILEHANDLE or filename provided, using STDIN'
      if wantarray;
  }
  elsif ( not defined fileno $in ) {
    open(IN_FH, '<', $in)
      or croak "Cannot open $in ($!)";

    binmode IN_FH; # Needed for Windows; no harm on Unix

    $in = \*IN_FH;
  }

  # Each valid queue trace file starts with a preamble. This preamble
  # is actually inserted by SSF.Util.Streams, upon which both
  # SSF.Net.droptailQueueMonitor_1 and SSF.Net.droptailQueueMonitor_2
  # are based. Below we assert that this preamble indeed exists,
  # before continuing to process the rest of the trace.
  #
  # Note that SSF.Util.Streams assumes that only a single stream ID is
  # present in a given trace.
  #
  my $utf_string = readUTF($in);

  croak "Bad header command: \"$utf_string\" (expected \"record\")"
    unless $utf_string eq 'record';

  $utf_string = readUTF($in);

  if ( defined $stream_id ) {
    croak "Stream ID mismatch \"$utf_string\" (expected \"$stream_id\")"
      unless $utf_string eq $stream_id;
  }
  else {
    $stream_id = $utf_string;
    carp
      "No stream ID provided; trace contains stream ID: \"",
      $utf_string, "\"" if wantarray ;
  }

  return ( $in, $stream_id );

} # End assert_input()

=head2 droptail_assert_output

 droptail_assert_output FILEHANDLE
 droptail_assert_output filename
 droptail_assert_output

This function returns a valid and open output FILEHANDLE. If
FILEHANDLE is open, it is returned as-is. If a I<filename> is provided
instead, this function attempts to open and return a filehandle to it.
If neither a FILEHANDLE nor a I<filename> is provided, the returned
FILEHANDLE defaults to STDOUT.

=cut

sub droptail_assert_output {

  my $out = shift;

  # If no FILEHANDLE or filename is provided, use STDOUT.
  #
  if ( not defined $out ) {
    $out = \*STDOUT;
    carp 'No output FILEHANDLE or filename provided, using STDOUT'
      if defined wantarray;
  }
  elsif ( not defined fileno $out ) {
    open(OUT_FH, '>', $out)
      or croak "Cannot open $out ($!)";

    $out = \*OUT_FH;
  }

  return $out;

} # End assert_output()


=head2 droptail_record_player

 droptail_record_player LIST

This function processes binary traces generated by
SSF.Net.droptailQueueMonitor_1 and SSF.Net.droptailQueueMonitor_2,
generates text output based on the contents of the binary trace, and
returns the number of records processed. In addition to seamlessly
emulating the functionality of SSF.Net.droptailRecordPlayer_1 and
SSF.Net.droptailRecordPlayer_2, droptail_record_player() can deal with
traces that contain a mix of records from both types of
droptailQueueMonitor's.

LIST is expected to have up to three elements: IN, OUT, and
STREAM_ID. IN and OUT may be either an open FILEHANDLE or a
I<filename>.  STREAM_ID, if specified, must match the stream ID
encoded in the queue trace file. LIST is asserted via
L<droptail_assert_input()|"droptail_assert_input"> and
L<droptail_assert_output()|"droptail_assert_output">.

A record created from SSF.Net.droptailQueueMonitor_1 will generate a
line like this in OUT

 6.01 4(1) pkts 7 drops 0 av_qlen 5.73492479324341

where "6.01" is the simulation time when the queue at NHI "4(1)" was
sampled. Since the last time the queue was sampled, 7 packets were
enqueued, 0 were dropped, and the average number of bytes buffered
during this interval was 5.73492479324341.

Similarly, a record created from SSF.Net.droptailQueueMonitor_2 will
generate a line like this

 99.1 4(2) sumpkts 55 sumdrops 0 pkts 0 drops 0

where "99.1" is the simulation time when the queue at NHI "4(2)" was
sampled. Since the beginning of the simulation, this interface has
enqueued a total of 55 packets and has dropped none. Since the last
time the queue was sampled, 0 packets were enqueued and 0 were
dropped.

=cut

sub droptail_record_player {

  my ( $in_fh, $out_fh, $stream_id ) = @_;

  # Assert input and output FILEHANLDEs
  #
  ( $in_fh, $stream_id ) =  droptail_assert_input( $in_fh, $stream_id );

  $out_fh = droptail_assert_output( $out_fh );

  my %types; # of trace records

  my %sources; # in the trace (interfaces - NHIs)

  my %times; # current simulation time for a given source

  my %time_decimals; # number of decimal digits used when printing the
                     # simulation times for each source

  my %sampling_intervals; # for each source

  # Used for gathering statistics to measure processing performance:
  # Number of $records and $bytes processed in $seconds
  #
  my ( $records, $bytes, $seconds ) = ( 0, 0, times );

  my $trace_record;

  # Each record starts with a 20-byte "header-like" part containing
  # the record $type, a unique $stream identifier, the simulation
  # $time in seconds, and the number of bytes ($length) left to be
  # read in the current record.
  #
  my ( $type, $stream, $time, $length );

  while( read($in_fh, $trace_record, 20) ) {

    ( $type, $stream, $time, $length ) = unpack("N N B64 N", $trace_record);

    # A $type code of 0 indicates that the current record
    # defines/contains a type code. See %supported_record_types above.
    #
    $type == 0 && do {

      $records++;

      read( $in_fh, $trace_record, $length );
      $bytes += $length;

      my ( $t_id, $t_name ) = split ' ', $trace_record;

      croak "Unsupported record type \"$t_name\"\n"
	unless $supported_record_types{$t_name};

      warn "Trace contains records of type \"$t_name\"\n" if $SHOW_SOURCES;

      $types{$t_id} = $t_name;

      next;
    };

    # A type code of 1 indicates that the current record contains a
    # stream id and the stream name, which is the NHI corresponding to
    # the interface being monitored.
    #
    $type == 1 && do {

      $records++;

      read($in_fh, $trace_record, $length);
      $bytes += $length;

      my ( $s_id, $s_name ) = split ' ', $trace_record;
      warn "Trace contains records from NHI $s_name\n" if $SHOW_SOURCES;

      $sources{$s_id} = $s_name;

      $times{$s_id} = 0;
      $time_decimals{$s_id} = '%.3f';

      next;
    };

    # The following type name (SSF.Net.QueueProbeIntRecord) and
    # associated $type code is used by SSF.Net.droptailQueueMonitor_1
    # to store the sampling interval. SSF.Net.droptailQueueMonitor_1
    # samples the queue using this interval, but stores a record only
    # when there is a change in the queue. On the other hand,
    # SSF.Net.droptailQueueMonitor_2 stores a record regardless of
    # whether any packets were enqueued since the last sample was
    # made.
    #
    $types{$type} eq 'SSF.Net.QueueProbeIntRecord' && do {

      $records++;

      # From the next 8 bytes only the first 4 are useful: A float
      # carrying the interval used by SSF.Net.droptailQueueMonitor_1
      # to sample the queue size. This is due to a hack in the
      # original Java code.
      #
      read($in_fh, $trace_record, 8);
      $bytes += 8;

      $sampling_intervals{$stream} =
	int_bits_to_float( unpack("V", $trace_record) );

      print { $out_fh }
	sprintf($time_decimals{$stream}, long_bits_to_double($time)),
        " $sources{$stream} probe_interval $sampling_intervals{$stream}\n";

      if ( $PRINT_EXACT_DECIMAL_DIGITS ) {
	# Extract the exact sampling interval from the trace, and
	# present the actual time in the generated output
	#
	my $d = log( $sampling_intervals{$stream} ) / log(10);
	$d = $d < 0 ? sprintf("%.0f",-$d) : 0;

	$time_decimals{$stream} =~ s/3/$d/;
      }

      next;
    };

    # This must be the first actual queue record. We're past the trace
    # preamble, so exit this loop.
    #
    last;
  }

  do {{

    ( $type, $stream, $time, $length ) = unpack("N N B64 N", $trace_record);

    $records++;

    $types{$type} eq 'SSF.Net.QueueRecord_1' && do {

      # Read the next 12 bytes, which correspond to one float
      # carrying the average queue length in bytes (over the
      # sampling interval), and two 32-bit integers:
      #
      # * the number of packets enqueued during the current sampling
      #   interval ($pkts)
      #
      # * the number of dropped packets during the current sampling
      #   interval ($drops)
      #
      read($in_fh, $trace_record, 12);

      $bytes += 12;

      # You may notice that SSF.OS.NetFlow.BytesUtil uses a custom way
      # for storing 32-bit integers.  Integers are actually stored in
      # little-endian binary format, contrary to standard Java, which
      # is big-endian.
      #
      my ($q_length, $pkts, $drops) = unpack("V V V", $trace_record);

      print { $out_fh }
	sprintf($time_decimals{$stream}, long_bits_to_double($time)),
	" $sources{$stream} pkts $pkts drops $drops av_qlen ",
	int_bits_to_float($q_length), "\n";

      next;
    };

    $types{$type} eq 'SSF.Net.QueueRecord_2' && do {

      # First make sure that we got the sampling interval for this
      # $stream
      #
      if ( not defined $sampling_intervals{$stream} ) {
	$sampling_intervals{$stream} = long_bits_to_double($time);

	if ( $PRINT_EXACT_DECIMAL_DIGITS ) {
	  # Extract the exact sampling interval from the trace, and
	  # present the actual time in the generated output
	  #
	  my $d = log( $sampling_intervals{$stream} ) / log(10);
	  $d = $d < 0 ? sprintf("%.0f",-$d) : 0;

	  $time_decimals{$stream} =~ s/3/$d/;
	}
      }

      $times{$stream} += $sampling_intervals{$stream};

      # Read the next 16 bytes, which correspond to four 32-bit
      # integers:
      #
      # * the total number of packets enqueued at the interface from
      #   the beginning of the simulation ($sumpkts)
      #
      # * the total number of packets dropped at the interface from
      #   the beginning of the simulation ($sumdrops)
      #
      # * the number of packets enqueued during the current sampling
      #   interval ($pkts)
      #
      # * the number of dropped packets during the current sampling
      #   interval ($drops)
      #
      read($in_fh, $trace_record, 16);

      $bytes += 16;

      # SSF.OS.NetFlow.BytesUtil uses a custom way for storing 32-bit
      # integers. Integers are actually stored in little-endian binary
      # format, contrary to standard Java, which is big-endian.
      #
      my ( $sumpkts, $sumdrops, $pkts, $drops ) =
	unpack("V V V V", $trace_record);

      print { $out_fh }
	sprintf($time_decimals{$stream}, $times{$stream}),
	" $sources{$stream} sumpkts $sumpkts sumdrops $sumdrops pkts ",
	$pkts, " drops $drops\n";

      next;
    };

  }} while( read($in_fh, $trace_record, 20) );

  # Display processing stats
  #
  if ( $SHOW_STATS ) {
    $seconds = times - $seconds ;
    my $rate = 'Inf';
    if ( $seconds > 0 ) {
      $rate = sprintf("%.0f", $bytes / (1024 * $seconds));
    }
    warn
      "{Player processed $records records, ",
      $bytes, " bytes in $seconds seconds ($rate KB/s)}\n";
  }

  return $records;

} # end of droptail_record_player()


=head2 droptail_record_plotter

 droptail_record_plotter LIST

This function processes binary traces generated by
SSF.Net.droptailQueueMonitor_1 and SSF.Net.droptailQueueMonitor_2, and
generates text files suitable for plotting using, for example,
I<gnuplot>.  It returns the number of records processed.

LIST should start with an open FILEHANDLE or a I<filename>, followed
by a STREAM_ID, which must match the stream ID encoded in the queue
trace file. This part of the LIST is asserted via
L<droptail_assert_input()|"droptail_assert_input">.

Following these two elements droptail_record_plotter() expects to see
at least one of the following strings: 'pkts, 'drops', 'sumpkts',
'sumdrops', and 'av_qlen'. For each of these strings and each source
NHI found in the trace, droptail_record_plotter() creates a text file
in the I<current working directory>. For example, if a trace file
includes records from two NHIs, 2(0) and 2(1), the following call

 droptail_record_plotter( 'qlog.0', 'some_stream_id.0', 'drops', 'pkts');

will create 4 files: "2(0).pkts", "2(0).drops", "2(1).pkts", and
"2(1).drops". Each of these files has two columns: the first one is
the simulation time; the second is the value of the respective metric.

Notice that you do not have to specify what kind of records are
contained in the trace. In fact, a trace may contain records created
from both SSF.Net.droptailQueueMonitor's.

=cut

sub droptail_record_plotter {

  my ( $in_fh, $stream_id ) = droptail_assert_input( shift, shift );

  my %can_plot = (
		  sumpkts  => 0,
		  sumdrops => 0,
		  pkts     => 0,
		  drops    => 0,
		  av_qlen  => 0,
		 );

  my ( %plot, %plot_fh );

  foreach my $p ( @_ ) {
    if ( defined $can_plot{$p} ) {
      $plot{$p} = $p;
    }
    else {
      croak "Do not know how to plot \"$p\"";
    }
  }

  my %types; # of trace records

  my %sources; # in the trace (interfaces - NHI's)

  my %times; # current simulation time for a given source

  my %time_decimals; # number of decimal digits used when printing the
                     # simulation times for each source

  my %sampling_intervals; # for each source

  # Used for gathering statistics to measure processing performance:
  # Number of $records and $bytes processed in $seconds
  #
  my ( $records, $bytes, $seconds ) = ( 0, 0, times );

  my $trace_record;

  # Each record starts with a 20-byte "header-like" part containing
  # the record $type, a unique $stream identifier, the simulation
  # $time in seconds, and the number of bytes ($length) left to be
  # read in the current record.
  #
  my ( $type, $stream, $time, $length );

  while( read($in_fh, $trace_record, 20) ) {

    ( $type, $stream, $time, $length ) = unpack("N N B64 N", $trace_record);

    # A $type code of 0 indicates that the current record contains a
    # type code. See %supported_record_types above.
    #
    $type == 0 && do {

      $records++;

      read($in_fh, $trace_record, $length);
      $bytes += $length;

      my ( $t_id, $t_name ) = split ' ', $trace_record;

      croak "Unsupported record type \"$t_name\"\n"
	unless $supported_record_types{$t_name};

      warn "Trace contains records of type \"$t_name\"\n" if $SHOW_SOURCES;

      $types{$t_id} = $t_name;

      next;
    };

    # A type code of 1 indicates that the current record contains a
    # stream id and the stream name, which is the NHI corresponding to
    # the interface being monitored.
    #
    $type == 1 && do {

      $records++;

      read($in_fh, $trace_record, $length);
      $bytes += $length;

      my ( $s_id, $s_name ) = split ' ', $trace_record;
      warn "Trace contains records from NHI $s_name\n" if $SHOW_SOURCES;

      $sources{$s_id} = $s_name;

      foreach my $k ( keys %plot ) {
	open( $plot_fh{$s_id}{$k}, '>', "$s_name.$plot{$k}" )
	  or croak "Cannot open $s_name.$plot{$k} ($!)";
      }

      $times{$s_id} = 0;
      $time_decimals{$s_id} = '%.3f';

      next;
    };

    $types{$type} eq 'SSF.Net.QueueProbeIntRecord' && do {

      $records++;

      # From the next 8 bytes only the first 4 are useful: A float
      # carrying the interval used by SSF.Net.droptailQueueMonitor_1
      # to sample the queue size. This is due to hack in the original
      # Java code.
      #
      read($in_fh, $trace_record, 8);
      $bytes += 8;

      $sampling_intervals{$stream} =
	int_bits_to_float(unpack("V", $trace_record));

      if ( $PRINT_EXACT_DECIMAL_DIGITS ) {
	# Extract the exact sampling interval from the trace, and
	# present the actual time in the generated output
	#
	my $d = log( $sampling_intervals{$stream} ) / log(10);
	$d = $d < 0 ? sprintf("%.0f",-$d) : 0;

	$time_decimals{$stream} =~ s/3/$d/;
      }

      next;
    };

    # This must be the first actual queue record. We're past the trace
    # preamble, so exit this loop.
    #
    last;
  }

  do {{

    ( $type, $stream, $time, $length ) = unpack("N N B64 N", $trace_record);

    $records++;

    $types{$type} eq 'SSF.Net.QueueRecord_1' && do {
      # Read the next 12 bytes, which correspond to one float
      # carrying the average queue length in bytes (over the
      # sampling interval), and two 32-bit integers:
      #
      # * the number of packets enqueued during the current sampling
      #   interval ($pkts)
      #
      # * the number of dropped packets during the current sampling
      #   interval ($drops)
      #
      read($in_fh, $trace_record, 12);

      $bytes += 12;

      # You may notice that SSF.OS.NetFlow.BytesUtil uses a custom way
      # for storing 32-bit integers. Integers are actually stored in
      # little-endian binary format, contrary to standard Java, which
      # is big-endian.
      #
      my ($q_length, $pkts, $drops) = unpack("V V V", $trace_record);

      my $t = sprintf( $time_decimals{$stream}, long_bits_to_double($time) );

      print { $plot_fh{$stream}{pkts} } "$t $pkts\n"
	if ( $plot{pkts} );

      print { $plot_fh{$stream}{drops} } "$t $drops\n"
	if ( $plot{drops} );

      print { $plot_fh{$stream}{av_qlen} }
	"$t ",int_bits_to_float($q_length), "\n"
      if ( $plot{av_qlen} );

      next;
    };

    $types{$type} eq 'SSF.Net.QueueRecord_2' && do {
      if ( not defined $sampling_intervals{$stream} ) {
	$sampling_intervals{$stream} = long_bits_to_double($time);

	if ( $PRINT_EXACT_DECIMAL_DIGITS ) {
	  # Extract the exact sampling interval from the trace, and
	  # present the actual time in the generated output
	  #
	  my $d = log( $sampling_intervals{$stream} ) / log(10);
	  $d = $d < 0 ? sprintf("%.0f",-$d) : 0;

	  $time_decimals{$stream} =~ s/3/$d/;
	}
      }

      $times{$stream} += $sampling_intervals{$stream};

      # Read the next 16 bytes, which correspond to four 32-bit
      # integers:
      #
      # * the total number of packets enqueued at the interface from
      #   the beginning of the simulation ($sumpkts)
      #
      # * the total number of packets dropped at the interface from
      #   the beginning of the simulation ($sumdrops)
      #
      # * the number of packets enqueued during the current sampling
      #   interval ($pkts)
      #
      # * the number of dropped packets during the current sampling
      #   interval ($drops)
      #
      read($in_fh, $trace_record, 16);

      $bytes += 16;

      # SSF.OS.NetFlow.BytesUtil uses a custom way for storing
      # 32-bit integers. This results in integers being stored in
      # binary little-endian format, contrary to standard Java,
      # which is big-endian.
      #
      my ( $sumpkts, $sumdrops, $pkts, $drops )
	= unpack("V V V V", $trace_record);

      print { $plot_fh{$stream}{pkts} } "$times{$stream} $pkts\n"
	if ( $plot{pkts} );

      print { $plot_fh{$stream}{drops} } "$times{$stream} $drops\n"
	if ( $plot{drops} );

      print { $plot_fh{$stream}{sumpkts} } "$times{$stream} $sumpkts\n"
	if ( $plot{sumpkts} );

      print { $plot_fh{$stream}{sumdrops} } "$times{$stream} $sumdrops\n"
	if ( $plot{sumdrops} ) ;

      next;
    };

  }} while( read($in_fh, $trace_record, 20) );

  # Display processing stats
  #
  if ($SHOW_STATS) {
    $seconds = times - $seconds ;
    my $rate = 'Inf';
    if ($seconds > 0) {
      $rate = sprintf("%.0f", $bytes / (1024 * $seconds));
    }
    warn
      "{Player processed $records records, ",
      $bytes, " bytes in $seconds seconds ($rate KB/s)}\n";
  }

  return $records;

} # end of droptail_record_plotter()

####################################################################
# Utility functions
####################################################################
#
# int_bits_to_float() 'returns the float value corresponding to a
# given bit represention. The argument is considered to be a
# representation of a floating-point value according to the IEEE 754
# floating-point "single format" bit layout.' -- from
# http://java.sun.com/j2se/1.4.1/docs/api/java/lang/Float.html#intBitsToFloat(int).
# int_bits_to_float() is used to read a float stored in binary format
# by a Java program.
#
sub int_bits_to_float ($) {
  my $i = shift;

  return '+Inf' if ( $i == 0x7f800000 );
  return '-Inf' if ( $i == 0xff800000 );

  return 'NaN'  if ( $i >= 0x7f800001 and $i <= 0x7fffffff  or
		     $i >= 0xffffffff and $i <= 0xff800001 );

  my $s = ( ( $i >> 31 ) == 0 ) ? 1 : -1;
  my $e = ( $i >> 23 ) & 0xff;
  my $m = ( $e == 0 ) ? ( $i & 0xfffff ) << 1
                      : ( $i & 0x7fffff ) | 0x800000;
  $e -= 150;

  return ( $e >= 0 ) ? $s * $m * 2**$e
                     : $s * $m / (2**(-$e));
} # end of int_bits_to_float()

# long_bits_to_double() 'returns the double value corresponding to a
# given bit representation. The argument is considered to be a
# representation of a floating-point value according to the IEEE 754
# floating-point "double format" bit layout.' -- from
# http://java.sun.com/j2se/1.4.1/docs/api/java/lang/Double.html#longBitsToDouble(long)
# long_bits_to_double() is used to read a double stored binary format
# by a Java program.
#
sub long_bits_to_double ($) {
  my $i = shift;

  my $s = substr($i, 0, 1) eq '0' ? 1 : -1;
  my $e =  oct('0b' . substr($i, 1, 11));
  $e &= 0x7ff;

  my $m1 = oct('0b' . substr($i, 12, 20));
  my $m2 = oct('0b' . substr($i, 21, 32));

  my $m;

  if ( $e == 0 ) {
    $m  = 0.0 + ($m1 * 2**32 + $m2) * 2;
  }
  else {
    $m1 |=  0x100000;
    $m  = 0.0 + $m1 * 2**32 + $m2;
  }

  $e -= 1075;

  return ( $e >= 0 ) ? $s * $m * 2**$e : $s * $m / (2**(-$e));
} # end of long_bits_to_double()

# readUTF FILEHANDLE
#
# This function emulates the functionality of the Java method
# java.io.DataInputStream.readUTF(). It reads and returns a single
# Java-UTF-8 string from FILEHANDLE.
#
# For more details see
# http://java.sun.com/j2se/1.4.1/docs/api/java/io/DataInputStream.html#readUTF()

sub readUTF( * ) {
  my $fh = shift;
  my ( $string_length, $utf_string );

  read($fh, $string_length, 2);
  read($fh, $utf_string, unpack("n", $string_length));

  return $utf_string;
} # end of readUTF()


1;

__END__

=head1 DEPENDENCIES

None.

=head1 EXPORTS

None by default.

=head2 Exportable

L<droptail_assert_input()|"droptail_assert_input">,
L<droptail_assert_output()|"droptail_assert_output">,
L<droptail_record_player()|droptail_record_player>,
L<droptail_record_plotter()|"droptail_record_plotter">.


=head1 CAVEATS

B<Binary Queue Traces> This module reads C<float>s, C<double>s,
C<int>s, and C<long>s generated by Java code and stored in a I<binary>
file. It works fine with binary files created with J2SE 1.4.1 on
GNU/Linux and Solaris 2.6.

=head1 VERSION

This is C<Net::Traces::SSFNet> version 0.02.

=head1 SEE ALSO

The Scalable Simulator Framework web site at http://www.ssfnet.org

=head1 AUTHOR

Kostas Pentikousis, kostas@cpan.org

=head1 COPYRIGHT AND LICENSE

Copyright 2003 by Kostas Pentikousis. All Rights Reserved.

This library is free software with ABSOLUTELY NO WARRANTY. You can
redistribute it and/or modify it under the same terms as Perl itself.

=cut
