portstats.in 9.41 KB
Newer Older
1
#!/usr/bin/perl -T
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2
3
4

#
# EMULAB-COPYRIGHT
5
# Copyright (c) 2000-2004 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
6
7
8
# All rights reserved.
#

9
#
10
# portstats - Get port statistics for nodes in an experiment
11
#
12
13
14
15
16
#
# NOTE: no -w, because $::line below is used in the eval, which perl
# can't pick up on, so it warns about this variable being only used once
#

17
18
19
20
21
22
23
24
25
26
27
28
29
30

#
# Configure variables
#
use lib '@prefix@/lib';

use libdb;
use snmpit_lib;

use English;
use Getopt::Long;
use strict;

sub usage {
31
    print << "END";
32
Usage: $0 [-h] <-p | <pid> <eid> > [vname ...] [vname:port ...]
33
34
35
-h    This message
-e    Show only error counters
-a    Show all stats
36
37
38
-z    Zero out counts for selected counters, after printing
-q    Quiet: don't actually print counts - useful with -z
-c    Print absolute, rather than relative, counts
39
40
-p    The machines given are physical, not virtual, node IDs. No pid and
      eid should be given when using this option.
41
42
43
44
45
46
47
48
49

If only pid and eid are given, prints out information about all ports in the
experiment. Otherwise, output is limited to the nodes and/or ports given.

NOTE: Statistics are reported from the switch's perspective. This means that
'In' packets are those sent FROM the node, and 'Out' packets are those
sent TO the node.

In the output, packets described as 'NUnicast' or 'NUcast' are non-unicast
50
(broadcast or multicast) packets.'
51
52
END

53
54
55
    return 1;
}

56
57
58
59
#
# Process command-line arguments
#
my %opt = ();
60
GetOptions(\%opt,'h','a','e','p','b','z','q','c');
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

if ($opt{h}) {
    exit &usage;
}

my @oids = (); # The set of OIDs we care about

if ($opt{a}) {
    @oids = ('ifInOctets', 'ifInUcastPkts', 'ifInNUcastPkts', 'ifInDiscards',
	     'ifInErrors', 'ifInUnknownProtos', 'ifOutOctets', 'ifOutUcastPkts',
	     'ifOutNUcastPkts', 'ifOutDiscards', 'ifOutErrors', 'ifOutQLen');
} elsif ($opt{e}) {
    @oids = ('ifInDiscards', 'ifInErrors', 'ifInUnknownProtos', 'ifOutDiscards',
	     'ifOutErrors');
} else {
    @oids = ('ifInOctets', 'ifInUcastPkts', 'ifInNUcastPkts',
	     'ifOutOctets', 'ifOutUcastPkts', 'ifOutNUcastPkts');
}

80
81
82
83
84
85
my ($pid, $eid);
if (!$opt{p}) {
    if (@ARGV < 2) {
	exit &usage;
    }
    ($pid,$eid) = (shift,shift);
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

    #
    # Untaint args.
    #
    if ($pid =~ /^([-\w]+)$/) {
        $pid = $1;
    }
    else {
        die("*** Bad data in pid: $pid.\n");
    }
    if ($eid =~ /^([-\w]+)$/) {
        $eid = $1;
    }
    else {
        die("*** Bad data in eid: $eid.\n");
    }
102
103
}

104
105
106
107
108
109
#
# Scan the rest of the arguments, doing a generic taint check. More
# specific patterns are below.
# 
my @passedPorts = ();
foreach my $arg (@ARGV) {
110
    if ($arg =~ /^([-\w\.:]+)$/) {
111
112
113
114
115
116
117
        $arg = $1;
    }
    else {
        die("*** Bad data in arg: $arg.\n");
    }
    push(@passedPorts, $arg);
}
118

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#
# This hash is used to create colmn headers and the format string
#
my %oids = (
    'ifInOctets'        => [['        In','    Octets'], '@>>>>>>>>>'],
    'ifInUcastPkts'     => [[' InUnicast','   Packets'], '@>>>>>>>>>'],
    'ifInNUcastPkts'    => [['InNUnicast','   Packets'], '@>>>>>>>>>'],
    'ifInDiscards'      => [['        In','  Discards'], '@>>>>>>>>>'],
    'ifInErrors'        => [['        In','    Errors'], '@>>>>>>>>>'],
    'ifInUnknownProtos' => [['   Unknown','  Protocol'], '@>>>>>>>>>'],
    'ifOutOctets'       => [['       Out','    Octets'], '@>>>>>>>>>'],
    'ifOutUcastPkts'    => [['OutUnicast','   Pakcets'], '@>>>>>>>>>'],
    'ifOutNUcastPkts'   => [[' OutNUcast','   Packets'], '@>>>>>>>>>'],
    'ifOutDiscards'     => [['       Out','  Discards'], '@>>>>>>>>>'],
    'ifOutErrors'       => [['       Out','    Errors'], '@>>>>>>>>>'],
    'ifOutQLen'         => [['  OutQueue','    Length'], '@>>>>>>>>>']
);
136
137
138
139

#
# First, make sure the experiment exists
#
140
141
142
143
if (!$opt{p}) {
    if (!ExpState($pid,$eid)) {
	die "There is no experiment $eid in project $pid\n";
    }
144
145
146
147
148
}

#
# Make sure they have access to it
#
149
if ($opt{p}) {
150
    my @nodes = map { /^([^:]+)(:(\d+))?$/; $1; } @passedPorts;
151
    if (!TBNodeAccessCheck($UID,TB_NODEACCESS_READINFO,@nodes)) {
152
	die "You do not have permission to view one or more of @nodes\n";
153
154
155
156
157
    }
} else {
    if (!TBExptAccessCheck($UID,$pid,$eid,TB_EXPT_READINFO)) {
	die "You do not have permission to view experiment $pid/$eid\n";
    }
158
159
160
161
162
}

snmpit_lib::init(0);

#
163
164
# If using physical port IDs, just use the list passed on the command line -
# For an experiment, figure out which one(s) they wanted.
165
166
#
my @ports;
167
if ($opt{p}) {
168
    #
169
170
    # If they gave a node:port form, use just that port. Otherwise, try to find
    # all the node's ports
171
172
173
174
    #
    foreach my $port (@passedPorts) {
	$port =~ /^([^:]+)(:(\d+))?$/;
	my ($hostname,$portnumber) = ($1,$3);
175
176
	if (defined $portnumber) {
	    push @ports, $port;
177
	} else {
178
179
180
181
182
	    my $interfaces = DBQueryFatal("select card from interfaces " .
		"where node_id = '$hostname'");
	    while (my ($card) = $interfaces->fetchrow()) {
		push @ports, "$port:$card";
	    }
183
184
185
	}
    }
} else {
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
    my @experimentPorts = getExperimentPorts($pid,$eid);
    if (@passedPorts) {
	#
	# Handle a set of passed-in ports
	#
	foreach my $port (@passedPorts) {
	    $port =~ /^([^:]+)(:(\d+))?$/;
	    my ($hostname,$portnumber) = ($1,$3);
	    my $nodeid;
	    if (!VnameToNodeid($pid,$eid,$hostname,\$nodeid)) {
		die "There is no node $hostname in $pid/$eid\n";
	    }

	    if (defined($portnumber)) {
		# They gave us a specific interface
		push @ports, "$nodeid:$portnumber";
	    } else {
		# We need to find all experimental ports for this node
		push @ports, grep(/^$nodeid:(\d+)$/,@experimentPorts);
	    }

	}
    } else {
	#
	# They didn't ask for specific ports, so we'll use 'em all
	#
	@ports = @experimentPorts;
    }
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
}

#
# List of OIDs we want to look at for each port
#

#
# Find out which devices these ports are on
#
my %portMap = mapPortsToDevices(@ports);

my @stats;
foreach my $name (keys %portMap) {
    my @ports = @{$portMap{$name}};

    #
    # Connect to the switch and get the data we want off of it
    #
Robert Ricci's avatar
Robert Ricci committed
232
233
234
235
236
    my $type = getDeviceType($name);
    my $device;
    SWITCH: for ($type) {
	/cisco/ && do {
	    require snmpit_cisco;
237
	    $device = new snmpit_cisco($name,0);
Robert Ricci's avatar
Robert Ricci committed
238
239
240
241
242
243
244
245
246
247
248
	    last;
	};
	/intel/ && do {
	    require snmpit_intel;
	    $device = new snmpit_intel($name);
	    last;
	};

	# 'default' case
	die "Unknown switch type ($type) for $name\n";
    }
249
250
251
252
253
    my @results = $device->getFields(\@ports,\@oids);

    foreach my $result (@results) {
	my $port = shift @ports;

254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
	#
	# Figure out which port on which switch this corresponds to
	#
	my $switchport = portnum($port);
	if (!($switchport && ($switchport =~ /(.+):(\d+)\.(\d+)/))) {
	    warn "WARNING: No switch port found for $port\n";
	} else {
	    my ($switch_id,$switch_card,$switch_port) = ($1, $2, $3);
	    my $dbresult = DBQueryFatal("select * from port_counters where ".
	    		"node_id='$switch_id' and card=$switch_card and ".
			"port=$switch_port");

	    my $oldresult = [@$result];

	    #
	    # Unless they want absolute counters, go through and subtract
	    # out the values stored in the database
	    #
	    if (!$opt{c}) {
		if ($dbresult && $dbresult->num_rows()) {
		    my %oldvals = $dbresult->fetchhash();
		    for (my $i = 0; $i < @oids; $i++) {
			if ($oldvals{$oids[$i]}) {
277
278
279
280
281
282
283
284
285
286
287
			    #
			    # Check for wraparound - of course, we can't tell
			    # how many times it wrapped, but we can at least
			    # not print a negative number.
			    # XXX - we harcode the size of the counter here
			    #
			    if ($result->[$i] >= $oldvals{$oids[$i]}) {
				$result->[$i] -= $oldvals{$oids[$i]};
			    } else {
				$result->[$i] += (2**32 - $oldvals{$oids[$i]});
			    }
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
			}
		    }
		}
	    }

	    #
	    # If requested, update the counters in the database
	    #
	    if ($opt{z}) {
		#
		# What we do to the DB depends on whether or not there is
		# already an entry for this port
		#
		my $query;
		if ($dbresult->num_rows()) {
		    $query = "update port_counters set ";
		} else {
		    $query = "insert into port_counters set ";
		}

		my @query_terms = ();
		for (my $i = 0; $i < @oids; $i++) {
		    push @query_terms, " $oids[$i]=$oldresult->[$i] ";
		}
		$query .= join(",",@query_terms);

		if ($dbresult->num_rows()) {
		    $query .= " where node_id='$switch_id' and " .
			    "card=$switch_card and port=$switch_port";
		} else {
		    $query .= ", node_id='$switch_id', card=$switch_card, " .
			    "port=$switch_port ";
		}

		DBQueryFatal($query);
	    }
	}

326
327
328
329
330
331
332
333
334
335
336
	if (!$opt{p}) {
	    #
	    # Try to translate the port name to the node's vname
	    #
	    $port =~ /^(.+):(\d+)/;
	    if ($1) {
		my $portnum = $2;
		my ($junk, $vname);
		NodeidToExp($1,\$junk,\$junk,\$vname);
		$port = "$vname:$portnum";
	    }
337
338
339
340
341
342
343
344
345
	}

	#
	# Throw this onto a list, so that we can sort it
	#
	push @stats, [$port,@$result];
    }
}

346
347
348
349
350
351
352
#
# Exit now if they wanted quiet operation
#
if ($opt{q}) {
    exit(0);
}

353
#
354
# Build up the heading a format strings
355
#
356
357
358
359
360
361
362
363
364
365
my @heading1 = ('              ');
my @heading2 = ('Port          ');
my @format   = ('@<<<<<<<<<<<<<');

foreach my $line (@oids{@oids}) {
    my ($heading,$format) = @$line;
    push @heading1, $$heading[0];
    push @heading2, $$heading[1];
    push @format,   $format;
}
366

367
368
369
370
371
372
373
my $heading1 = join(" ",@heading1);
my $heading2 = join(" ",@heading2);

my $format = "format stats =\n" . join(" ",@format) . "\n";
$format .= join(",",map {"\$\$::line[$_]"} (0 .. @oids)) . "\n.\n";

eval $format;
374
375
$FORMAT_NAME = 'stats';

376
377
378
379
380
381
382
#
# Print out the heading
#
print "$heading1\n";
print "$heading2\n";
print "-" x length($heading1),"\n";

383
384
385
#
# Finally, print out the results
#
386
foreach $::line (sort {$$a[0] cmp $$b[0]} @stats) {
387
388
    write;
}