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__ |