portstats.in 11 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, 2006 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

#
# Configure variables
#
21
22
my $ELABINELAB = @ELABINELAB@;

23
24
25
26
use lib '@prefix@/lib';

use libdb;
use snmpit_lib;
Kevin Atkinson's avatar
Kevin Atkinson committed
27
use libtblog;
28
29
30
31
32
33

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

sub usage {
34
    print << "END";
35
Usage: $0 [-h] <-p | <pid> <eid> > [vname ...] [vname:port ...]
36
37
38
-h    This message
-e    Show only error counters
-a    Show all stats
39
40
41
-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
42
43
-p    The machines given are physical, not virtual, node IDs. No pid and
      eid should be given when using this option.
44
-s    Ports are specified in switch.port syntax
45
-C    List control net, rather than experimental net, ports
46
47
48
49
50
51
52
53
54

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
55
(broadcast or multicast) packets.'
56
57
END

58
59
60
    return 1;
}

61
62
63
64
#
# Process command-line arguments
#
my %opt = ();
65
66
Getopt::Long::Configure("no_ignore_case");
GetOptions(\%opt,'h','a','e','p','b','z','q','c','s','C');
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

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');
}

86
my ($pid, $eid);
87
if (!$opt{p} && !$opt{s}) {
88
89
90
91
    if (@ARGV < 2) {
	exit &usage;
    }
    ($pid,$eid) = (shift,shift);
92
93
94
95
96

    #
    # Untaint args.
    #
    if ($pid =~ /^([-\w]+)$/) {
97
	$pid = $1;
98
99
    }
    else {
Kevin Atkinson's avatar
   
Kevin Atkinson committed
100
        tbdie("Bad data in pid: $pid.");
101
102
103
104
105
    }
    if ($eid =~ /^([-\w]+)$/) {
        $eid = $1;
    }
    else {
Kevin Atkinson's avatar
   
Kevin Atkinson committed
106
        tbdie("Bad data in eid: $eid.");
107
    }
108
109
}

110
111
112
113
114
115
#
# Scan the rest of the arguments, doing a generic taint check. More
# specific patterns are below.
# 
my @passedPorts = ();
foreach my $arg (@ARGV) {
116
117
118
119
120
121
122
123
124
125
126
127
128
129
    if ($opt{s}) {
        if ($arg =~ /^([-\w\.\/]+)$/) {
            $arg = $1;
        }
        else {
            tbdie("Bad data in arg: $arg.");
        }
    } else {
        if ($arg =~ /^([-\w\.:]+)$/) {
            $arg = $1;
        }
        else {
            tbdie("Bad data in arg: $arg.");
        }
130
131
132
    }
    push(@passedPorts, $arg);
}
133

134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#
# 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'], '@>>>>>>>>>']
);
151
152
153
154

#
# First, make sure the experiment exists
#
155
if (!$opt{p} && !$opt{s}) {
156
157
158
    if (!ExpState($pid,$eid)) {
	die "There is no experiment $eid in project $pid\n";
    }
159
160
161
162
163
}

#
# Make sure they have access to it
#
164
165
166
167
168
if ($opt{s}) {
    if ($UID && !TBAdmin($UID)) {
        die("Only root or TB admins can use -s.");
    }
} elsif ($opt{p}) {
169
    my @nodes = map { /^([^:]+)(:(\d+))?$/; $1; } @passedPorts;
170
    if (!TBNodeAccessCheck($UID,TB_NODEACCESS_READINFO,@nodes)) {
171
	die "You do not have permission to view one or more of @nodes\n";
172
173
174
175
176
    }
} else {
    if (!TBExptAccessCheck($UID,$pid,$eid,TB_EXPT_READINFO)) {
	die "You do not have permission to view experiment $pid/$eid\n";
    }
177
178
}

179
180
181
182
183
184
185
186
#
# Eventually, we can pass this out via XMLRPC. For now just exit.
# 
if ($ELABINELAB) {
    print "Not bothering with portstats inside an elabinelab.\n";
    exit(0);
}

187
188
snmpit_lib::init(0);

189
190
191
192
193
194
195
196
197
198
199
200
201
202
my @ports;
#
# If using "switch" syntax, make sure all names are in the correct format
#
if ($opt{s}) {
    foreach my $port (@passedPorts) {
        if ($port =~ /^[^.]+\.\d+\/\d+$/) {
	    push @ports, $port;
	} else {
	    print "'$port' not in correct switch.port syntax, ignoring\n";
	}
    }

}
203
#
204
205
# If using physical port IDs, just use the list passed on the command line -
# For an experiment, figure out which one(s) they wanted.
206
#
207
elsif ($opt{p}) {
208
    #
209
210
    # If they gave a node:port form, use just that port. Otherwise, try to find
    # all the node's ports
211
212
213
214
    #
    foreach my $port (@passedPorts) {
	$port =~ /^([^:]+)(:(\d+))?$/;
	my ($hostname,$portnumber) = ($1,$3);
215
216
	if (defined $portnumber) {
	    push @ports, $port;
217
	} else  {
218
219
220
221
222
	    my $interfaces = DBQueryFatal("select card from interfaces " .
		"where node_id = '$hostname'");
	    while (my ($card) = $interfaces->fetchrow()) {
		push @ports, "$port:$card";
	    }
223
224
225
	}
    }
} else {
226
227
228
229
230
231
232
233
234
235
236
    my @experimentPorts;
    #
    # Get control net or experimental net ports, depending on what they
    # asked for
    #
    if ($opt{C}) {
        @experimentPorts = getExperimentControlPorts($pid,$eid);
    } else {
        @experimentPorts = getExperimentPorts($pid,$eid);
    }
    print "ep: " . join(";",@experimentPorts) . "\n";
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
    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;
    }
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
}

#
# 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
282
283
284
285
286
    my $type = getDeviceType($name);
    my $device;
    SWITCH: for ($type) {
	/cisco/ && do {
	    require snmpit_cisco;
287
	    $device = new snmpit_cisco($name,0);
Robert Ricci's avatar
Robert Ricci committed
288
289
290
291
292
293
294
	    last;
	};
	/intel/ && do {
	    require snmpit_intel;
	    $device = new snmpit_intel($name);
	    last;
	};
295
296
297
298
299
	/foundry/ && do {
	    require snmpit_foundry;
	    $device = new snmpit_foundry($name);
	    last;
	};
300
301
302
303
304
	/nortel/ && do {
	    require snmpit_nortel;
	    $device = new snmpit_nortel($name);
	    last;
	};
Robert Ricci's avatar
Robert Ricci committed
305
306
307
308

	# 'default' case
	die "Unknown switch type ($type) for $name\n";
    }
309
310
311
312
313
    my @results = $device->getFields(\@ports,\@oids);

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

314
315
316
	#
	# Figure out which port on which switch this corresponds to
	#
317
318
319
320
321
322
323
324
	my $switchport;
	if ($opt{s}) {
	    if ($port =~ /^([^.]+)\.(\d+)\/(\d+)$/) {
		$switchport = "$1:$2.$3";
	    }
	} else {
	    $switchport = portnum($port);
	}
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
	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]}) {
344
345
346
347
348
349
350
351
352
353
354
			    #
			    # 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]});
			    }
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
			}
		    }
		}
	    }

	    #
	    # 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);
	    }
	}

393
	if (!$opt{p} && !$opt{s}) {
394
395
396
397
398
399
400
401
402
403
	    #
	    # 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";
	    }
404
405
406
407
408
409
410
411
412
	}

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

413
414
415
416
417
418
419
#
# Exit now if they wanted quiet operation
#
if ($opt{q}) {
    exit(0);
}

420
#
421
# Build up the heading a format strings
422
#
423
424
425
426
427
428
429
430
431
432
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;
}
433

434
435
436
437
438
439
440
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;
441
442
$FORMAT_NAME = 'stats';

443
444
445
446
447
448
449
#
# Print out the heading
#
print "$heading1\n";
print "$heading2\n";
print "-" x length($heading1),"\n";

450
451
452
#
# Finally, print out the results
#
453
foreach $::line (sort {$$a[0] cmp $$b[0]} @stats) {
454
455
    write;
}