File Coverage

blib/lib/Net/Traces/SSFNet.pm
Criterion Covered Total %
statement 122 225 54.2
branch 40 120 33.3
condition 3 9 33.3
subroutine 9 10 90.0
pod 4 7 57.1
total 178 371 47.9


line stmt bran cond sub pod time code
1             package Net::Traces::SSFNet;
2              
3 1     1   26244 use strict;
  1         2  
  1         40  
4 1     1   5 use warnings;
  1         1  
  1         25  
5 1     1   5 use Carp;
  1         6  
  1         3350  
6              
7             =head1 NAME
8              
9             Net::Traces::SSFNet - Analyze traces generated by SSFNet
10              
11             =head1 SYNOPSIS
12              
13             use Net::Traces::SSFNet qw( droptail_record_player droptail_record_plotter );
14              
15             $Net::Traces::SSFNet::PRINT_EXACT_DECIMAL_DIGITS = 0;
16             $Net::Traces::SSFNet::SHOW_SOURCES = 1;
17             $Net::Traces::SSFNet::SHOW_STATS = 0;
18              
19             # Use with traces created by either
20             # SSF.Net.droptailQueueMonitor_1 or SSF.Net.droptailQueueMonitor_2
21             #
22             droptail_record_player('q.trace', 'text.output', 'some_stream_id.0');
23              
24             # Use with traces created by SSF.Net.droptailQueueMonitor_1
25             #
26             droptail_record_plotter('q.trace', 'some_stream_id.0', 'drops', 'pkts', 'av_qlen');
27              
28             # Use with traces created by SSF.Net.droptailQueueMonitor_2
29             #
30             droptail_record_plotter('q.trace', 'some_stream_id.0', 'drops', 'pkts', 'sumpkts', 'sumdrops');
31              
32             =cut
33              
34             require Exporter;
35              
36             our @ISA = qw( Exporter );
37              
38             our @EXPORT = qw( );
39              
40             our @EXPORT_OK = qw(
41             droptail_assert_input
42             droptail_assert_output
43             droptail_record_player
44             droptail_record_plotter
45             );
46              
47             our $VERSION = '0.02';
48              
49             my %supported_record_types =
50             (
51             'SSF.Net.QueueRecord_1' => 'SSF.Net.droptailQueueMonitor_1',
52             'SSF.Net.QueueProbeIntRecord' => 'SSF.Net.droptailQueueMonitor_1',
53             'SSF.Net.QueueRecord_2' => 'SSF.Net.droptailQueueMonitor_2',
54             );
55              
56             =head1 ABSTRACT
57              
58             Net::Traces::SSFNet can analyze traces created by L
59             Framework Network Models|"SEE ALSO">. It efficiently emulates in Perl
60             the functionality provided by Java-based, SSFNet-bundled trace
61             analyzers, and adds new features, including allowing for finer
62             granularity in the processed output.
63              
64             =head1 INSTALLATION
65              
66             See perlmodinstall.
67              
68             =head1 DESCRIPTION
69              
70             SSF, the Scalable Simulation Framework, is a public-domain standard
71             for discrete-event simulation of large, complex systems in Java and
72             C++. SSFNet is a collection of models used for simulating
73             telecommunication networks. The main goal of this module to ease the
74             analysis of traces produced by L.
75              
76             Net::Traces::SSFNet version 0.02 can analyze traces generated by
77             SSF.Net.droptailQueueMonitor_1 and SSF.Net.droptailQueueMonitor_2.
78              
79             =head2 Analyzing SSF.Net.droptailQueueMonitor traces
80              
81             Net::Traces::SSFNet can analyze traces created by
82             SSF.Net.droptailQueueMonitor_1 and SSF.Net.droptailQueueMonitor_2,
83             effectively L the functionality
84             of SSF.Net.droptailRecordPlayer_1 and SSF.Net.droptailRecordPlayer_2.
85              
86             To replicate the functionality of either
87              
88             java SSF.Net.droptailRecordPlayer_1 qlog.0 some_stream_id.0
89              
90             or
91              
92             java SSF.Net.droptailRecordPlayer_2 qlog.0 some_stream_id.0
93              
94              
95             use the following code:
96              
97             use Net::Traces::SSFNet qw( droptail_record_player );
98              
99             $Net::Traces::SSFNet::PRINT_EXACT_DECIMAL_DIGITS = 0;
100             droptail_record_player( 'qlog.0', *STDOUT, 'some_stream_id.0');
101              
102             Notice that you do not have to specify what kind of records are
103             contained in the trace. In fact, a trace may contain records created
104             from both SSF.Net.droptailQueueMonitor's.
105              
106             =head2 Finer granularity
107              
108             Although both SSF.Net.droptailQueueMonitor's capture simulation events
109             using 64-bit Cs, the SSFNet-bundled trace processing utilities
110             (SSF.Net.droptailRecordPlayer_1 and SSF.Net.droptailRecordPlayer_2)
111             use 3 decimal digits when generating the processed
112             output. Consequently, the text output is limited to millisecond
113             granularity.
114              
115             This can be an issue when events occur in sub-millisecond intervals:
116             the original SSFNet record players do not carry this information in
117             the text output. The following example might make the issue more
118             clear. Remember that each node in a network graph is uniquely
119             identified via a I (NHI) string (see
120             http://www.ssfnet.org/InternetDocs/ssfnetDMLReference.html#addresses). Suppose
121             that NHI 4(0) is sampled every 0.1 ms and NHI 4(1) every 1 ms. When
122             SSF.Net.droptailRecordPlayer_2 processes the trace file, it will
123             generate something like this
124              
125             50.805 4(0) sumpkts 186 sumdrops 0 pkts 0 drops 0
126             50.805 4(0) sumpkts 187 sumdrops 0 pkts 1 drops 0
127             50.805 4(0) sumpkts 187 sumdrops 0 pkts 0 drops 0
128             50.805 4(0) sumpkts 187 sumdrops 0 pkts 0 drops 0
129             50.806 4(0) sumpkts 188 sumdrops 0 pkts 1 drops 0
130             50.806 4(0) sumpkts 188 sumdrops 0 pkts 0 drops 0
131             50.806 4(0) sumpkts 189 sumdrops 0 pkts 1 drops 0
132             50.806 4(0) sumpkts 189 sumdrops 0 pkts 0 drops 0
133             50.806 4(0) sumpkts 190 sumdrops 0 pkts 1 drops 0
134             50.806 4(1) sumpkts 51 sumdrops 0 pkts 0 drops 0
135              
136             while L will
137             generate
138              
139             50.8051 4(0) sumpkts 186 sumdrops 0 pkts 0 drops 0
140             50.8052 4(0) sumpkts 187 sumdrops 0 pkts 1 drops 0
141             50.8053 4(0) sumpkts 187 sumdrops 0 pkts 0 drops 0
142             50.8054 4(0) sumpkts 187 sumdrops 0 pkts 0 drops 0
143             50.8055 4(0) sumpkts 188 sumdrops 0 pkts 1 drops 0
144             50.8056 4(0) sumpkts 188 sumdrops 0 pkts 0 drops 0
145             50.8057 4(0) sumpkts 189 sumdrops 0 pkts 1 drops 0
146             50.8058 4(0) sumpkts 189 sumdrops 0 pkts 0 drops 0
147             50.8059 4(0) sumpkts 190 sumdrops 0 pkts 1 drops 0
148             50.806 4(1) sumpkts 51 sumdrops 0 pkts 0 drops 0
149              
150             provided that L<"$PRINT_EXACT_DECIMAL_DIGITS"> is set.
151              
152             =head2 Improved Performance
153              
154             L processes a queue
155             trace generated by either SSF.Net.droptailQueueMonitor_1 or
156             SSF.Net.droptailQueueMonitor_2 significantly faster that
157             SSF.Net.droptailRecordPlayer_1 or SSF.Net.droptailRecordPlayer_2,
158             respectively.
159              
160             =head2 Additional functionality
161              
162             Use L to process
163             a queue trace and generate text files ready for plotting using
164             I, I, or even a spreadsheet application.
165              
166             =head1 VARIABLES
167              
168             Net::Traces::SSFNet uses the following variables to control
169             information generation. None is exported.
170              
171             =head2 $PRINT_EXACT_DECIMAL_DIGITS
172              
173             This variable is set by default in order to achieve L
174             granularity| "Finer granularity"> in the processed output. If you want
175             to mimic the behavior of SSF.Net.droptailRecordPlayer_1 and
176             SSF.Net.droptailRecordPlayer_2 use
177              
178             $Net::Traces::SSFNet::PRINT_EXACT_DECIMAL_DIGITS = 0;
179              
180             =head2 $SHOW_SOURCES
181              
182             If $SHOW_SOURCES is set, droptail_record_player() and
183             droptail_record_plotter() print to STDERR the types of records and
184             traffic sources (NHI) found in the trace. For example, you may see
185             something like this:
186              
187             Trace contains records from NHI 4(2)
188             Trace contains records of type "SSF.Net.QueueRecord_2"
189             Trace contains records from NHI 4(1)
190             Trace contains records from NHI 4(0)
191              
192             By default, no such information is sent to STDERR.
193              
194             =head2 $SHOW_STATS
195              
196             If $SHOW_STATS is set, droptail_record_player() and
197             droptail_record_plotter() display trace processing statistics on
198             STDERR. For example, you may see something like this:
199              
200             {Player processed 113776 records, 1820393 bytes in 7.05 seconds (252 KB/s)}
201              
202             This variable is set by default. If you want to suppress displaying
203             the statistics use
204              
205             $Net::Traces::SSFNet::SHOW_STATS = 0;
206              
207             =cut
208              
209             our $PRINT_EXACT_DECIMAL_DIGITS = 1;
210             our $SHOW_SOURCES = 0;
211             our $SHOW_STATS = 1;
212              
213             =head1 FUNCTIONS
214              
215             =head2 droptail_assert_input
216              
217             droptail_assert_input LIST
218              
219             This function asserts that the input FILEHANDLE is valid and open
220             before the real trace processing begins processing. LIST is expected
221             to have up to two elements: IN and STREAM_ID. The queue trace IN may
222             be either an open FILEHANDLE or a I. STREAM_ID, if
223             specified, must match the stream ID encoded in the queue trace file.
224              
225             droptail_assert_input() verifies that IN is open for reading and
226             includes a valid preamble. If IN is not specified, it defaults to
227             STDIN.
228              
229             droptail_assert_input() returns a list containing the input
230             FILEHANDLE, and the actual stream ID found in the queue trace file.
231              
232             =cut
233              
234             sub droptail_assert_input {
235              
236 3     3 1 12 my ( $in, $stream_id ) = @_;
237              
238             # The following makes sure that $in is an open FILEHANDLE: If $in
239             # was not provided, use STDIN. If $in is a filename, attempt to open
240             # it for input. Otherwise, use $in as-is.
241             #
242 3 50       27 if ( not defined $in ) {
    50          
243 0         0 $in = \*STDIN;
244 0 0       0 carp 'No input FILEHANDLE or filename provided, using STDIN'
245             if wantarray;
246             }
247             elsif ( not defined fileno $in ) {
248 3 50       148 open(IN_FH, '<', $in)
249             or croak "Cannot open $in ($!)";
250              
251 3         6 binmode IN_FH; # Needed for Windows; no harm on Unix
252              
253 3         8 $in = \*IN_FH;
254             }
255              
256             # Each valid queue trace file starts with a preamble. This preamble
257             # is actually inserted by SSF.Util.Streams, upon which both
258             # SSF.Net.droptailQueueMonitor_1 and SSF.Net.droptailQueueMonitor_2
259             # are based. Below we assert that this preamble indeed exists,
260             # before continuing to process the rest of the trace.
261             #
262             # Note that SSF.Util.Streams assumes that only a single stream ID is
263             # present in a given trace.
264             #
265 3         9 my $utf_string = readUTF($in);
266              
267 3 50       9 croak "Bad header command: \"$utf_string\" (expected \"record\")"
268             unless $utf_string eq 'record';
269              
270 3         7 $utf_string = readUTF($in);
271              
272 3 100       9 if ( defined $stream_id ) {
273 2 50       8 croak "Stream ID mismatch \"$utf_string\" (expected \"$stream_id\")"
274             unless $utf_string eq $stream_id;
275             }
276             else {
277 1         3 $stream_id = $utf_string;
278 1 50       4 carp
279             "No stream ID provided; trace contains stream ID: \"",
280             $utf_string, "\"" if wantarray ;
281             }
282              
283 3         16 return ( $in, $stream_id );
284              
285             } # End assert_input()
286              
287             =head2 droptail_assert_output
288              
289             droptail_assert_output FILEHANDLE
290             droptail_assert_output filename
291             droptail_assert_output
292              
293             This function returns a valid and open output FILEHANDLE. If
294             FILEHANDLE is open, it is returned as-is. If a I is provided
295             instead, this function attempts to open and return a filehandle to it.
296             If neither a FILEHANDLE nor a I is provided, the returned
297             FILEHANDLE defaults to STDOUT.
298              
299             =cut
300              
301             sub droptail_assert_output {
302              
303 1     1 1 3 my $out = shift;
304              
305             # If no FILEHANDLE or filename is provided, use STDOUT.
306             #
307 1 50       11 if ( not defined $out ) {
    50          
308 0         0 $out = \*STDOUT;
309 0 0       0 carp 'No output FILEHANDLE or filename provided, using STDOUT'
310             if defined wantarray;
311             }
312             elsif ( not defined fileno $out ) {
313 1 50       46 open(OUT_FH, '>', $out)
314             or croak "Cannot open $out ($!)";
315              
316 1         4 $out = \*OUT_FH;
317             }
318              
319 1         3 return $out;
320              
321             } # End assert_output()
322              
323              
324             =head2 droptail_record_player
325              
326             droptail_record_player LIST
327              
328             This function processes binary traces generated by
329             SSF.Net.droptailQueueMonitor_1 and SSF.Net.droptailQueueMonitor_2,
330             generates text output based on the contents of the binary trace, and
331             returns the number of records processed. In addition to seamlessly
332             emulating the functionality of SSF.Net.droptailRecordPlayer_1 and
333             SSF.Net.droptailRecordPlayer_2, droptail_record_player() can deal with
334             traces that contain a mix of records from both types of
335             droptailQueueMonitor's.
336              
337             LIST is expected to have up to three elements: IN, OUT, and
338             STREAM_ID. IN and OUT may be either an open FILEHANDLE or a
339             I. STREAM_ID, if specified, must match the stream ID
340             encoded in the queue trace file. LIST is asserted via
341             L and
342             L.
343              
344             A record created from SSF.Net.droptailQueueMonitor_1 will generate a
345             line like this in OUT
346              
347             6.01 4(1) pkts 7 drops 0 av_qlen 5.73492479324341
348              
349             where "6.01" is the simulation time when the queue at NHI "4(1)" was
350             sampled. Since the last time the queue was sampled, 7 packets were
351             enqueued, 0 were dropped, and the average number of bytes buffered
352             during this interval was 5.73492479324341.
353              
354             Similarly, a record created from SSF.Net.droptailQueueMonitor_2 will
355             generate a line like this
356              
357             99.1 4(2) sumpkts 55 sumdrops 0 pkts 0 drops 0
358              
359             where "99.1" is the simulation time when the queue at NHI "4(2)" was
360             sampled. Since the beginning of the simulation, this interface has
361             enqueued a total of 55 packets and has dropped none. Since the last
362             time the queue was sampled, 0 packets were enqueued and 0 were
363             dropped.
364              
365             =cut
366              
367             sub droptail_record_player {
368              
369 1     1 1 3 my ( $in_fh, $out_fh, $stream_id ) = @_;
370              
371             # Assert input and output FILEHANLDEs
372             #
373 1         5 ( $in_fh, $stream_id ) = droptail_assert_input( $in_fh, $stream_id );
374              
375 1         4 $out_fh = droptail_assert_output( $out_fh );
376              
377 1         2 my %types; # of trace records
378              
379             my %sources; # in the trace (interfaces - NHIs)
380              
381 0         0 my %times; # current simulation time for a given source
382              
383 0         0 my %time_decimals; # number of decimal digits used when printing the
384             # simulation times for each source
385              
386 0         0 my %sampling_intervals; # for each source
387              
388             # Used for gathering statistics to measure processing performance:
389             # Number of $records and $bytes processed in $seconds
390             #
391 1         22 my ( $records, $bytes, $seconds ) = ( 0, 0, times );
392              
393 1         2 my $trace_record;
394              
395             # Each record starts with a 20-byte "header-like" part containing
396             # the record $type, a unique $stream identifier, the simulation
397             # $time in seconds, and the number of bytes ($length) left to be
398             # read in the current record.
399             #
400 1         2 my ( $type, $stream, $time, $length );
401              
402 1         8 while( read($in_fh, $trace_record, 20) ) {
403              
404 9         33 ( $type, $stream, $time, $length ) = unpack("N N B64 N", $trace_record);
405              
406             # A $type code of 0 indicates that the current record
407             # defines/contains a type code. See %supported_record_types above.
408             #
409 9 100       25 $type == 0 && do {
410              
411 3         4 $records++;
412              
413 3         7 read( $in_fh, $trace_record, $length );
414 3         4 $bytes += $length;
415              
416 3         32 my ( $t_id, $t_name ) = split ' ', $trace_record;
417              
418 3 50       12 croak "Unsupported record type \"$t_name\"\n"
419             unless $supported_record_types{$t_name};
420              
421 3 50       7 warn "Trace contains records of type \"$t_name\"\n" if $SHOW_SOURCES;
422              
423 3         6 $types{$t_id} = $t_name;
424              
425 3         10 next;
426             };
427              
428             # A type code of 1 indicates that the current record contains a
429             # stream id and the stream name, which is the NHI corresponding to
430             # the interface being monitored.
431             #
432 6 100       15 $type == 1 && do {
433              
434 3         4 $records++;
435              
436 3         6 read($in_fh, $trace_record, $length);
437 3         4 $bytes += $length;
438              
439 3         9 my ( $s_id, $s_name ) = split ' ', $trace_record;
440 3 50       10 warn "Trace contains records from NHI $s_name\n" if $SHOW_SOURCES;
441              
442 3         9 $sources{$s_id} = $s_name;
443              
444 3         4 $times{$s_id} = 0;
445 3         7 $time_decimals{$s_id} = '%.3f';
446              
447 3         11 next;
448             };
449              
450             # The following type name (SSF.Net.QueueProbeIntRecord) and
451             # associated $type code is used by SSF.Net.droptailQueueMonitor_1
452             # to store the sampling interval. SSF.Net.droptailQueueMonitor_1
453             # samples the queue using this interval, but stores a record only
454             # when there is a change in the queue. On the other hand,
455             # SSF.Net.droptailQueueMonitor_2 stores a record regardless of
456             # whether any packets were enqueued since the last sample was
457             # made.
458             #
459 3 100       9 $types{$type} eq 'SSF.Net.QueueProbeIntRecord' && do {
460              
461 2         4 $records++;
462              
463             # From the next 8 bytes only the first 4 are useful: A float
464             # carrying the interval used by SSF.Net.droptailQueueMonitor_1
465             # to sample the queue size. This is due to a hack in the
466             # original Java code.
467             #
468 2         4 read($in_fh, $trace_record, 8);
469 2         3 $bytes += 8;
470              
471 2         9 $sampling_intervals{$stream} =
472             int_bits_to_float( unpack("V", $trace_record) );
473              
474 2         3 print { $out_fh }
  2         8  
475             sprintf($time_decimals{$stream}, long_bits_to_double($time)),
476             " $sources{$stream} probe_interval $sampling_intervals{$stream}\n";
477              
478 2 50       8 if ( $PRINT_EXACT_DECIMAL_DIGITS ) {
479             # Extract the exact sampling interval from the trace, and
480             # present the actual time in the generated output
481             #
482 2         16 my $d = log( $sampling_intervals{$stream} ) / log(10);
483 2 50       7 $d = $d < 0 ? sprintf("%.0f",-$d) : 0;
484              
485 2         9 $time_decimals{$stream} =~ s/3/$d/;
486             }
487              
488 2         8 next;
489             };
490              
491             # This must be the first actual queue record. We're past the trace
492             # preamble, so exit this loop.
493             #
494 1         2 last;
495             }
496              
497 1         2 do {{
498              
499 13         13 ( $type, $stream, $time, $length ) = unpack("N N B64 N", $trace_record);
  13         40  
500              
501 13         17 $records++;
502              
503 13 100       30 $types{$type} eq 'SSF.Net.QueueRecord_1' && do {
504              
505             # Read the next 12 bytes, which correspond to one float
506             # carrying the average queue length in bytes (over the
507             # sampling interval), and two 32-bit integers:
508             #
509             # * the number of packets enqueued during the current sampling
510             # interval ($pkts)
511             #
512             # * the number of dropped packets during the current sampling
513             # interval ($drops)
514             #
515 3         6 read($in_fh, $trace_record, 12);
516              
517 3         3 $bytes += 12;
518              
519             # You may notice that SSF.OS.NetFlow.BytesUtil uses a custom way
520             # for storing 32-bit integers. Integers are actually stored in
521             # little-endian binary format, contrary to standard Java, which
522             # is big-endian.
523             #
524 3         6 my ($q_length, $pkts, $drops) = unpack("V V V", $trace_record);
525              
526 3         4 print { $out_fh }
  3         7  
527             sprintf($time_decimals{$stream}, long_bits_to_double($time)),
528             " $sources{$stream} pkts $pkts drops $drops av_qlen ",
529             int_bits_to_float($q_length), "\n";
530              
531 3         16 next;
532             };
533              
534 10 50       23 $types{$type} eq 'SSF.Net.QueueRecord_2' && do {
535              
536             # First make sure that we got the sampling interval for this
537             # $stream
538             #
539 10 100       24 if ( not defined $sampling_intervals{$stream} ) {
540 1         3 $sampling_intervals{$stream} = long_bits_to_double($time);
541              
542 1 50       3 if ( $PRINT_EXACT_DECIMAL_DIGITS ) {
543             # Extract the exact sampling interval from the trace, and
544             # present the actual time in the generated output
545             #
546 1         4 my $d = log( $sampling_intervals{$stream} ) / log(10);
547 1 50       3 $d = $d < 0 ? sprintf("%.0f",-$d) : 0;
548              
549 1         4 $time_decimals{$stream} =~ s/3/$d/;
550             }
551             }
552              
553 10         24 $times{$stream} += $sampling_intervals{$stream};
554              
555             # Read the next 16 bytes, which correspond to four 32-bit
556             # integers:
557             #
558             # * the total number of packets enqueued at the interface from
559             # the beginning of the simulation ($sumpkts)
560             #
561             # * the total number of packets dropped at the interface from
562             # the beginning of the simulation ($sumdrops)
563             #
564             # * the number of packets enqueued during the current sampling
565             # interval ($pkts)
566             #
567             # * the number of dropped packets during the current sampling
568             # interval ($drops)
569             #
570 10         14 read($in_fh, $trace_record, 16);
571              
572 10         11 $bytes += 16;
573              
574             # SSF.OS.NetFlow.BytesUtil uses a custom way for storing 32-bit
575             # integers. Integers are actually stored in little-endian binary
576             # format, contrary to standard Java, which is big-endian.
577             #
578 10         24 my ( $sumpkts, $sumdrops, $pkts, $drops ) =
579             unpack("V V V V", $trace_record);
580              
581 10         12 print { $out_fh }
  10         55  
582             sprintf($time_decimals{$stream}, $times{$stream}),
583             " $sources{$stream} sumpkts $sumpkts sumdrops $sumdrops pkts ",
584             $pkts, " drops $drops\n";
585              
586 10         30 next;
587             };
588              
589             }} while( read($in_fh, $trace_record, 20) );
590              
591             # Display processing stats
592             #
593 1 50       3 if ( $SHOW_STATS ) {
594 0         0 $seconds = times - $seconds ;
595 0         0 my $rate = 'Inf';
596 0 0       0 if ( $seconds > 0 ) {
597 0         0 $rate = sprintf("%.0f", $bytes / (1024 * $seconds));
598             }
599             warn
600 0         0 "{Player processed $records records, ",
601             $bytes, " bytes in $seconds seconds ($rate KB/s)}\n";
602             }
603              
604 1         8 return $records;
605              
606             } # end of droptail_record_player()
607              
608              
609             =head2 droptail_record_plotter
610              
611             droptail_record_plotter LIST
612              
613             This function processes binary traces generated by
614             SSF.Net.droptailQueueMonitor_1 and SSF.Net.droptailQueueMonitor_2, and
615             generates text files suitable for plotting using, for example,
616             I. It returns the number of records processed.
617              
618             LIST should start with an open FILEHANDLE or a I, followed
619             by a STREAM_ID, which must match the stream ID encoded in the queue
620             trace file. This part of the LIST is asserted via
621             L.
622              
623             Following these two elements droptail_record_plotter() expects to see
624             at least one of the following strings: 'pkts, 'drops', 'sumpkts',
625             'sumdrops', and 'av_qlen'. For each of these strings and each source
626             NHI found in the trace, droptail_record_plotter() creates a text file
627             in the I. For example, if a trace file
628             includes records from two NHIs, 2(0) and 2(1), the following call
629              
630             droptail_record_plotter( 'qlog.0', 'some_stream_id.0', 'drops', 'pkts');
631              
632             will create 4 files: "2(0).pkts", "2(0).drops", "2(1).pkts", and
633             "2(1).drops". Each of these files has two columns: the first one is
634             the simulation time; the second is the value of the respective metric.
635              
636             Notice that you do not have to specify what kind of records are
637             contained in the trace. In fact, a trace may contain records created
638             from both SSF.Net.droptailQueueMonitor's.
639              
640             =cut
641              
642             sub droptail_record_plotter {
643              
644 0     0 1 0 my ( $in_fh, $stream_id ) = droptail_assert_input( shift, shift );
645              
646 0         0 my %can_plot = (
647             sumpkts => 0,
648             sumdrops => 0,
649             pkts => 0,
650             drops => 0,
651             av_qlen => 0,
652             );
653              
654 0         0 my ( %plot, %plot_fh );
655              
656 0         0 foreach my $p ( @_ ) {
657 0 0       0 if ( defined $can_plot{$p} ) {
658 0         0 $plot{$p} = $p;
659             }
660             else {
661 0         0 croak "Do not know how to plot \"$p\"";
662             }
663             }
664              
665 0         0 my %types; # of trace records
666              
667             my %sources; # in the trace (interfaces - NHI's)
668              
669 0         0 my %times; # current simulation time for a given source
670              
671 0         0 my %time_decimals; # number of decimal digits used when printing the
672             # simulation times for each source
673              
674 0         0 my %sampling_intervals; # for each source
675              
676             # Used for gathering statistics to measure processing performance:
677             # Number of $records and $bytes processed in $seconds
678             #
679 0         0 my ( $records, $bytes, $seconds ) = ( 0, 0, times );
680              
681 0         0 my $trace_record;
682              
683             # Each record starts with a 20-byte "header-like" part containing
684             # the record $type, a unique $stream identifier, the simulation
685             # $time in seconds, and the number of bytes ($length) left to be
686             # read in the current record.
687             #
688 0         0 my ( $type, $stream, $time, $length );
689              
690 0         0 while( read($in_fh, $trace_record, 20) ) {
691              
692 0         0 ( $type, $stream, $time, $length ) = unpack("N N B64 N", $trace_record);
693              
694             # A $type code of 0 indicates that the current record contains a
695             # type code. See %supported_record_types above.
696             #
697 0 0       0 $type == 0 && do {
698              
699 0         0 $records++;
700              
701 0         0 read($in_fh, $trace_record, $length);
702 0         0 $bytes += $length;
703              
704 0         0 my ( $t_id, $t_name ) = split ' ', $trace_record;
705              
706 0 0       0 croak "Unsupported record type \"$t_name\"\n"
707             unless $supported_record_types{$t_name};
708              
709 0 0       0 warn "Trace contains records of type \"$t_name\"\n" if $SHOW_SOURCES;
710              
711 0         0 $types{$t_id} = $t_name;
712              
713 0         0 next;
714             };
715              
716             # A type code of 1 indicates that the current record contains a
717             # stream id and the stream name, which is the NHI corresponding to
718             # the interface being monitored.
719             #
720 0 0       0 $type == 1 && do {
721              
722 0         0 $records++;
723              
724 0         0 read($in_fh, $trace_record, $length);
725 0         0 $bytes += $length;
726              
727 0         0 my ( $s_id, $s_name ) = split ' ', $trace_record;
728 0 0       0 warn "Trace contains records from NHI $s_name\n" if $SHOW_SOURCES;
729              
730 0         0 $sources{$s_id} = $s_name;
731              
732 0         0 foreach my $k ( keys %plot ) {
733 0 0       0 open( $plot_fh{$s_id}{$k}, '>', "$s_name.$plot{$k}" )
734             or croak "Cannot open $s_name.$plot{$k} ($!)";
735             }
736              
737 0         0 $times{$s_id} = 0;
738 0         0 $time_decimals{$s_id} = '%.3f';
739              
740 0         0 next;
741             };
742              
743 0 0       0 $types{$type} eq 'SSF.Net.QueueProbeIntRecord' && do {
744              
745 0         0 $records++;
746              
747             # From the next 8 bytes only the first 4 are useful: A float
748             # carrying the interval used by SSF.Net.droptailQueueMonitor_1
749             # to sample the queue size. This is due to hack in the original
750             # Java code.
751             #
752 0         0 read($in_fh, $trace_record, 8);
753 0         0 $bytes += 8;
754              
755 0         0 $sampling_intervals{$stream} =
756             int_bits_to_float(unpack("V", $trace_record));
757              
758 0 0       0 if ( $PRINT_EXACT_DECIMAL_DIGITS ) {
759             # Extract the exact sampling interval from the trace, and
760             # present the actual time in the generated output
761             #
762 0         0 my $d = log( $sampling_intervals{$stream} ) / log(10);
763 0 0       0 $d = $d < 0 ? sprintf("%.0f",-$d) : 0;
764              
765 0         0 $time_decimals{$stream} =~ s/3/$d/;
766             }
767              
768 0         0 next;
769             };
770              
771             # This must be the first actual queue record. We're past the trace
772             # preamble, so exit this loop.
773             #
774 0         0 last;
775             }
776              
777 0         0 do {{
778              
779 0         0 ( $type, $stream, $time, $length ) = unpack("N N B64 N", $trace_record);
  0         0  
780              
781 0         0 $records++;
782              
783 0 0       0 $types{$type} eq 'SSF.Net.QueueRecord_1' && do {
784             # Read the next 12 bytes, which correspond to one float
785             # carrying the average queue length in bytes (over the
786             # sampling interval), and two 32-bit integers:
787             #
788             # * the number of packets enqueued during the current sampling
789             # interval ($pkts)
790             #
791             # * the number of dropped packets during the current sampling
792             # interval ($drops)
793             #
794 0         0 read($in_fh, $trace_record, 12);
795              
796 0         0 $bytes += 12;
797              
798             # You may notice that SSF.OS.NetFlow.BytesUtil uses a custom way
799             # for storing 32-bit integers. Integers are actually stored in
800             # little-endian binary format, contrary to standard Java, which
801             # is big-endian.
802             #
803 0         0 my ($q_length, $pkts, $drops) = unpack("V V V", $trace_record);
804              
805 0         0 my $t = sprintf( $time_decimals{$stream}, long_bits_to_double($time) );
806              
807 0 0       0 print { $plot_fh{$stream}{pkts} } "$t $pkts\n"
  0         0  
808             if ( $plot{pkts} );
809              
810 0 0       0 print { $plot_fh{$stream}{drops} } "$t $drops\n"
  0         0  
811             if ( $plot{drops} );
812              
813 0 0       0 print { $plot_fh{$stream}{av_qlen} }
  0         0  
814             "$t ",int_bits_to_float($q_length), "\n"
815             if ( $plot{av_qlen} );
816              
817 0         0 next;
818             };
819              
820 0 0       0 $types{$type} eq 'SSF.Net.QueueRecord_2' && do {
821 0 0       0 if ( not defined $sampling_intervals{$stream} ) {
822 0         0 $sampling_intervals{$stream} = long_bits_to_double($time);
823              
824 0 0       0 if ( $PRINT_EXACT_DECIMAL_DIGITS ) {
825             # Extract the exact sampling interval from the trace, and
826             # present the actual time in the generated output
827             #
828 0         0 my $d = log( $sampling_intervals{$stream} ) / log(10);
829 0 0       0 $d = $d < 0 ? sprintf("%.0f",-$d) : 0;
830              
831 0         0 $time_decimals{$stream} =~ s/3/$d/;
832             }
833             }
834              
835 0         0 $times{$stream} += $sampling_intervals{$stream};
836              
837             # Read the next 16 bytes, which correspond to four 32-bit
838             # integers:
839             #
840             # * the total number of packets enqueued at the interface from
841             # the beginning of the simulation ($sumpkts)
842             #
843             # * the total number of packets dropped at the interface from
844             # the beginning of the simulation ($sumdrops)
845             #
846             # * the number of packets enqueued during the current sampling
847             # interval ($pkts)
848             #
849             # * the number of dropped packets during the current sampling
850             # interval ($drops)
851             #
852 0         0 read($in_fh, $trace_record, 16);
853              
854 0         0 $bytes += 16;
855              
856             # SSF.OS.NetFlow.BytesUtil uses a custom way for storing
857             # 32-bit integers. This results in integers being stored in
858             # binary little-endian format, contrary to standard Java,
859             # which is big-endian.
860             #
861 0         0 my ( $sumpkts, $sumdrops, $pkts, $drops )
862             = unpack("V V V V", $trace_record);
863              
864 0 0       0 print { $plot_fh{$stream}{pkts} } "$times{$stream} $pkts\n"
  0         0  
865             if ( $plot{pkts} );
866              
867 0 0       0 print { $plot_fh{$stream}{drops} } "$times{$stream} $drops\n"
  0         0  
868             if ( $plot{drops} );
869              
870 0 0       0 print { $plot_fh{$stream}{sumpkts} } "$times{$stream} $sumpkts\n"
  0         0  
871             if ( $plot{sumpkts} );
872              
873 0 0       0 print { $plot_fh{$stream}{sumdrops} } "$times{$stream} $sumdrops\n"
  0         0  
874             if ( $plot{sumdrops} ) ;
875              
876 0         0 next;
877             };
878              
879             }} while( read($in_fh, $trace_record, 20) );
880              
881             # Display processing stats
882             #
883 0 0       0 if ($SHOW_STATS) {
884 0         0 $seconds = times - $seconds ;
885 0         0 my $rate = 'Inf';
886 0 0       0 if ($seconds > 0) {
887 0         0 $rate = sprintf("%.0f", $bytes / (1024 * $seconds));
888             }
889             warn
890 0         0 "{Player processed $records records, ",
891             $bytes, " bytes in $seconds seconds ($rate KB/s)}\n";
892             }
893              
894 0         0 return $records;
895              
896             } # end of droptail_record_plotter()
897              
898             ####################################################################
899             # Utility functions
900             ####################################################################
901             #
902             # int_bits_to_float() 'returns the float value corresponding to a
903             # given bit represention. The argument is considered to be a
904             # representation of a floating-point value according to the IEEE 754
905             # floating-point "single format" bit layout.' -- from
906             # http://java.sun.com/j2se/1.4.1/docs/api/java/lang/Float.html#intBitsToFloat(int).
907             # int_bits_to_float() is used to read a float stored in binary format
908             # by a Java program.
909             #
910             sub int_bits_to_float ($) {
911 5     5 0 8 my $i = shift;
912              
913 5 50       9 return '+Inf' if ( $i == 0x7f800000 );
914 5 50       11 return '-Inf' if ( $i == 0xff800000 );
915              
916 5 50 33     36 return 'NaN' if ( $i >= 0x7f800001 and $i <= 0x7fffffff or
      33        
      33        
917             $i >= 0xffffffff and $i <= 0xff800001 );
918              
919 5 50       11 my $s = ( ( $i >> 31 ) == 0 ) ? 1 : -1;
920 5         9 my $e = ( $i >> 23 ) & 0xff;
921 5 50       13 my $m = ( $e == 0 ) ? ( $i & 0xfffff ) << 1
922             : ( $i & 0x7fffff ) | 0x800000;
923 5         6 $e -= 150;
924              
925 5 50       29 return ( $e >= 0 ) ? $s * $m * 2**$e
926             : $s * $m / (2**(-$e));
927             } # end of int_bits_to_float()
928              
929             # long_bits_to_double() 'returns the double value corresponding to a
930             # given bit representation. The argument is considered to be a
931             # representation of a floating-point value according to the IEEE 754
932             # floating-point "double format" bit layout.' -- from
933             # http://java.sun.com/j2se/1.4.1/docs/api/java/lang/Double.html#longBitsToDouble(long)
934             # long_bits_to_double() is used to read a double stored binary format
935             # by a Java program.
936             #
937             sub long_bits_to_double ($) {
938 6     6 0 10 my $i = shift;
939              
940 6 50       19 my $s = substr($i, 0, 1) eq '0' ? 1 : -1;
941 6         12 my $e = oct('0b' . substr($i, 1, 11));
942 6         609 $e &= 0x7ff;
943              
944 6         14 my $m1 = oct('0b' . substr($i, 12, 20));
945 6         11 my $m2 = oct('0b' . substr($i, 21, 32));
946              
947 6         8 my $m;
948              
949 6 100       13 if ( $e == 0 ) {
950 2         6 $m = 0.0 + ($m1 * 2**32 + $m2) * 2;
951             }
952             else {
953 4         5 $m1 |= 0x100000;
954 4         5 $m = 0.0 + $m1 * 2**32 + $m2;
955             }
956              
957 6         8 $e -= 1075;
958              
959 6 50       68 return ( $e >= 0 ) ? $s * $m * 2**$e : $s * $m / (2**(-$e));
960             } # end of long_bits_to_double()
961              
962             # readUTF FILEHANDLE
963             #
964             # This function emulates the functionality of the Java method
965             # java.io.DataInputStream.readUTF(). It reads and returns a single
966             # Java-UTF-8 string from FILEHANDLE.
967             #
968             # For more details see
969             # http://java.sun.com/j2se/1.4.1/docs/api/java/io/DataInputStream.html#readUTF()
970              
971             sub readUTF( * ) {
972 6     6 0 8 my $fh = shift;
973 6         6 my ( $string_length, $utf_string );
974              
975 6         56 read($fh, $string_length, 2);
976 6         18 read($fh, $utf_string, unpack("n", $string_length));
977              
978 6         15 return $utf_string;
979             } # end of readUTF()
980              
981              
982             1;
983              
984             __END__