Commit 76163920 authored by Leigh B Stoller's avatar Leigh B Stoller

More improvements. Thanks to Gary for figuring out a great query; see

comments in the code.
parent 37b9bdd2
...@@ -8,7 +8,6 @@ use English; ...@@ -8,7 +8,6 @@ use English;
use Getopt::Std; use Getopt::Std;
use Date::Parse; use Date::Parse;
use Time::Local; use Time::Local;
use Math::BigInt;
# #
# Drill down the node history data in the DB # Drill down the node history data in the DB
...@@ -46,15 +45,18 @@ sub usage { ...@@ -46,15 +45,18 @@ sub usage {
" use negative value for last num records\n". " use negative value for last num records\n".
" -r reverse order (most recent first)\n". " -r reverse order (most recent first)\n".
" -s show a summary of node's past usage\n". " -s show a summary of node's past usage\n".
" -d date Start at YYYY-MM-DD HH:MM:SS\n".
" -c used with -d, show current status of all nodes\n".
" -v verbose output\n". " -v verbose output\n".
" -w print warnings about anomolous records\n"); " -w print warnings about anomolous records\n");
exit(1); exit(1);
} }
my $optlist = "ARS:aln:rswvd:i:"; my $optlist = "ARS:aln:rswvd:i:c";
my $warnme = 0; my $warnme = 0;
my $verbose = 0; my $verbose = 0;
my $showall = 0; my $showall = 0;
my $showcurrent = 0;
my $showalloconly = 0; my $showalloconly = 0;
my $list = 0; my $list = 0;
my $summary = 0; my $summary = 0;
...@@ -66,18 +68,12 @@ my $ip; ...@@ -66,18 +68,12 @@ my $ip;
# #
# Sort stuff. sortby value should correspond to record field format: # Sort stuff. sortby value should correspond to record field format:
# #
# sortby 0: node
# sortby 1: pideid
# sortby 2: uid
# sortby 3: date
# sortby 4: elapsed
#
my %sortmap = ( my %sortmap = (
"node" => 0, "node" => 0,
"pideid" => 1, "pideid" => 6,
"uid" => 2, "uid" => 3,
"date" => 3, "date" => 1,
"elapsed" => 4 "elapsed" => 2
); );
my $sortbydate = $sortmap{date}; my $sortbydate = $sortmap{date};
my $sortby = $sortbydate; my $sortby = $sortbydate;
...@@ -115,7 +111,7 @@ if (defined($options{"R"})) { ...@@ -115,7 +111,7 @@ if (defined($options{"R"})) {
$raw = 1; $raw = 1;
} }
if (defined($options{"S"})) { if (defined($options{"S"})) {
if (!defined($sortmap{$options{"S"}})) { if (!exists($sortmap{$options{"S"}})) {
print STDERR "invalid sort field '$options{S}'\n"; print STDERR "invalid sort field '$options{S}'\n";
usage(); usage();
} }
...@@ -140,6 +136,11 @@ if (defined($options{"d"})) { ...@@ -140,6 +136,11 @@ if (defined($options{"d"})) {
$datetime = timelocal(strptime($options{"d"})); $datetime = timelocal(strptime($options{"d"}));
$summary = 0; $summary = 0;
} }
if (defined($options{"c"})) {
$showcurrent = 1;
usage()
if (!defined($datetime));
}
if (defined($options{"i"})) { if (defined($options{"i"})) {
$ip = $options{"i"}; $ip = $options{"i"};
$summary = 0; $summary = 0;
...@@ -150,7 +151,7 @@ if (defined($options{"w"})) { ...@@ -150,7 +151,7 @@ if (defined($options{"w"})) {
if (defined($options{"v"})) { if (defined($options{"v"})) {
$verbose = 1; $verbose = 1;
} }
if (!$showall && @ARGV == 0) { if (!$showall && @ARGV == 0 && !defined($ip)) {
usage(); usage();
} }
my @nodes = @ARGV; my @nodes = @ARGV;
...@@ -159,13 +160,90 @@ if (!$list && !$summary) { ...@@ -159,13 +160,90 @@ if (!$list && !$summary) {
$list = 1; $list = 1;
} }
my %nodeinfo; # [ expt, starttime, uid ]
my @rawrecords;
my %startinfo;
# #
# Common case: a single node. # Common case: a single node.
# Just fetch data for it, otherwise fetch data for all. # Just fetch data for it, otherwise fetch data for all.
# #
#
# If we have a datetime, then we want to start with records greater
# then the stamp, but we will not know the state at that point, since
# it will be the most recent record *before* the requested stamp, that
# says what the node is doing at the time. So, this query does some
# amazing magic to find those records without an explosion of terms
# that takes forever. Kudos to Gary for figuring out this query.
#
if ($datetime) {
my $querymod = "";
if (@nodes) {
$querymod = " AND (" .
join(" or ", map {"h1.node_id='$_'"} @nodes) . ")";
}
elsif (defined($ip)) {
$querymod = " AND h1.cnet_ip='$ip'";
}
my $query_result =
DBQueryFatal("SELECT h1.node_id,h1.op,h1.stamp,h1.uid,h1.exptidx,".
" s.pid,s.eid ".
" FROM node_history as h1 ".
"JOIN (SELECT h3.node_id,MAX(h3.stamp) as stamp ".
" FROM node_history as h3 ".
" WHERE stamp < $datetime GROUP BY h3.node_id ".
" ) AS h2 ON h1.node_id=h2.node_id AND ".
" h1.stamp = h2.stamp ".
"left join nodes as n on n.node_id=h1.node_id ".
"left join node_types as t on t.type=n.type ".
"left join experiment_stats as s on s.exptidx=h1.exptidx ".
"where t.isplabdslice=0 and t.isplabphysnode=0 and ".
" (n.role='testnode' or n.role='virtnode') ".
"$querymod order by h1.node_id");
while (my %row = $query_result->fetchhash()) {
my $pid = $row{pid};
my $eid = $row{eid};
my $pideid = "$pid/$eid";
my $exptidx = $row{"exptidx"};
my $node = $row{node_id};
my $stamp = $row{stamp};
my $uid = $row{uid};
my $op = $row{op};
my $cstamp = ctime($stamp);
my $diff = $datetime - $stamp;
chomp($cstamp);
if ($op eq "alloc" || $op eq "move" || $op eq "create") {
if ($showcurrent) {
next
if ($pid eq TBOPSPID());
if ($verbose) {
print "$node '$stamp' $diff $uid $pid $eid\n";
}
else {
print "$node REC $stamp $diff $uid $pid $eid\n";
}
}
else {
$nodeinfo{$node} = [ $pideid, $exptidx, $stamp, $uid ];
}
}
elsif ($op eq "free" || $op eq "destroy") {
if (!$showcurrent) {
$nodeinfo{$node} = [ "", undef, $stamp, $uid ];
}
}
}
}
exit(0)
if ($showcurrent);
my $querymod = ""; my $querymod = "";
if (@nodes == 1) { if (@nodes) {
$querymod = " AND node_id='$nodes[0]'"; $querymod = " AND (" . join(" or ", map {"node_id='$_'"} @nodes) . ")";
} }
elsif (defined($ip)) { elsif (defined($ip)) {
$querymod = " AND cnet_ip='$ip'"; $querymod = " AND cnet_ip='$ip'";
...@@ -181,10 +259,9 @@ my $query_result = ...@@ -181,10 +259,9 @@ my $query_result =
" experiment_stats.exptidx,cnet_ip,phys_nodeid ". " experiment_stats.exptidx,cnet_ip,phys_nodeid ".
"FROM node_history,experiment_stats ". "FROM node_history,experiment_stats ".
"WHERE node_history.exptidx=experiment_stats.exptidx ". "WHERE node_history.exptidx=experiment_stats.exptidx ".
"$querymod $orderby $limitby"); $querymod .
($datetime ? " and stamp>='$datetime' " : "").
my %nodeinfo; # [ expt, starttime, uid ] "$orderby $limitby");
my @records;
while (my %row = $query_result->fetchhash()) { while (my %row = $query_result->fetchhash()) {
my $pideid = "$row{pid}/$row{eid}"; my $pideid = "$row{pid}/$row{eid}";
...@@ -254,45 +331,33 @@ while (my %row = $query_result->fetchhash()) { ...@@ -254,45 +331,33 @@ while (my %row = $query_result->fetchhash()) {
} }
$nodeinfo{$node} = [ "", undef, $stamp, $uid ]; $nodeinfo{$node} = [ "", undef, $stamp, $uid ];
} }
#print "R: $node, $opideid, $oidx, $ouid, $ostamp, $elapsed\n";
# save off the record # save off the record
push(@records, [ $node, $opideid, $oidx, $ouid, $ostamp, $elapsed ]); push(@rawrecords, [ $node, $opideid, $oidx, $ouid, $ostamp, $elapsed ]);
}
# Include the current state of nodes in a final record
my $stamp = time();
for $node (keys(%nodeinfo)) {
my ($opideid, $oidx, $ostamp, $ouid) = @{$nodeinfo{$node}};
my $elapsed = $stamp - $ostamp;
push(@records, [ $node, $opideid, $oidx, $ouid, $ostamp, $elapsed ]);
} }
# Prune the list based on date range (someday) if (!$datetime) {
# Include the current state of nodes in a final record
# Sort the list as desired my $stamp = time();
if ($sortby ne "date") { for $node (keys(%nodeinfo)) {
@records = sort byfield @records; my ($opideid, $oidx, $ostamp, $ouid) = @{$nodeinfo{$node}};
} my $elapsed = $stamp - $ostamp;
if ($revorder) { push(@rawrecords, [ $node, $opideid, $oidx, $ouid, $ostamp, $elapsed ]);
@records = reverse(@records);
}
# Prune to the proper number of entries (first/last $numrecs entries)
if ($numrecs && $numrecs < $#records) {
if ($numrecs > 0) {
@records = @records[0 .. $numrecs-1];
} else {
@records = @records[$numrecs .. -1 ];
} }
} }
# #
# Loop over the remaining records, computing summary stats # Loop over the raw records, computing summary stats and creating
# and printing (if desired). # a another set of records to print (if desired).
# #
for my $rec (@records) { my @records = ();
for my $rec (@rawrecords) {
my ($node, $pideid, $exptidx, $uid, $stamp, $elapsed) = @{$rec}; my ($node, $pideid, $exptidx, $uid, $stamp, $elapsed) = @{$rec};
#print "RR: $node, $elapsed\n";
if (!defined($nodestats{$node})) { if (!defined($nodestats{$node})) {
$nodestats{$node} = [ 0, 0, 0, 0 ]; $nodestats{$node} = [ 0, 0, 0, 0 ];
} }
...@@ -312,65 +377,51 @@ for my $rec (@records) { ...@@ -312,65 +377,51 @@ for my $rec (@records) {
$nodestats{$node} = [ $ftime, $atime, $rtime, $dtime ]; $nodestats{$node} = [ $ftime, $atime, $rtime, $dtime ];
if ($list) { if ($list) {
if ($verbose) { my ($pid, $eid);
my $start = ctime($stamp); if ($pideid eq "") {
chomp($start); $pid = $eid = "<FREE>";
my $end = ctime($stamp + $elapsed); $exptidx = 0;
chomp($end); } else {
($pid, $eid) = split("/", $pideid);
if ($datetime) { }
my $d = Math::BigInt->new($datetime); push(@records,
my $s = Math::BigInt->new($stamp); [$node, $stamp, $elapsed, $uid, $pid, $eid, $exptidx, "$pid/$eid"])
my $e = Math::BigInt->new($elapsed); if (!$showalloconly || $isalloced);
$e->badd($s); }
my $c1 = $s->bcmp($d); }
my $c2 = $e->bcmp($d);
next # Sort the list as desired
if ($c1 < 0 && $c2 <= 0); if ($sortby ne $sortbydate) {
@records = sort byfield @records;
}
if ($revorder) {
@records = reverse(@records);
}
if ($c1 <= 0 && $c2 > 0) { # Prune to the proper number of entries (first/last $numrecs entries)
print "$node: $pideid($exptidx) from $start til $end ". if ($numrecs && $numrecs < $#records) {
"($elapsed sec)\n" if ($numrecs > 0) {
if ($isalloced); @records = @records[0 .. $numrecs-1];
last; } else {
} @records = @records[$numrecs .. -1 ];
} }
print "$node: $pideid($exptidx) from $start til $end ($elapsed sec)\n" }
if (!$showalloconly || $isalloced);
} else {
my ($pid, $eid);
if ($pideid eq "") {
$pid = $eid = "<FREE>";
$exptidx = 0;
} else {
($pid, $eid) = split("/", $pideid);
}
if ($datetime) {
my $d = Math::BigInt->new($datetime);
my $s = Math::BigInt->new($stamp);
my $e = Math::BigInt->new($elapsed);
$e->badd($s);
my $c1 = $s->bcmp($d);
my $c2 = $e->bcmp($d);
next if (@records) {
if ($c1 < 0 && $c2 <= 0); foreach my $record (@records) {
my ($node, $stamp, $elapsed, $uid, $pid, $eid) = @$record;
if ($c1 <= 0 && $c2 > 0) { if ($verbose) {
print "$node REC $stamp $elapsed $uid $pid $eid $exptidx\n"; $stamp = ctime($stamp);
last; chomp($stamp);
} print "$node '$stamp' $elapsed $uid $pid $eid\n";
} }
print "$node REC $stamp $elapsed $uid $pid $eid $exptidx\n" else {
if (!$showalloconly || $isalloced); print "$node REC $stamp $elapsed $uid $pid $eid\n";
} }
} }
} }
print scalar(@records) . " records\n"
if (0 && $list && $verbose);
# #
# Print out summary information # Print out summary information
# #
......
...@@ -252,6 +252,20 @@ class Node ...@@ -252,6 +252,20 @@ class Node
return $row["isremotenode"]; return $row["isremotenode"];
} }
function IsVirtNode() {
$type = $this->type();
$query_result =
DBQueryFatal("select isvirtnode from node_types ".
"where type='$type'");
if (mysql_num_rows($query_result) == 0) {
return 0;
}
$row = mysql_fetch_array($query_result);
return $row["isremotenode"];
}
function NodeStatus() { function NodeStatus() {
$node_id = $this->node_id(); $node_id = $this->node_id();
...@@ -1331,7 +1345,7 @@ class Node ...@@ -1331,7 +1345,7 @@ class Node
# #
# Show history. # Show history.
# #
function ShowNodeHistory($node = null, function ShowNodeHistory($node_id = null,
$showall = 0, $count = 20, $reverse = 1, $showall = 0, $count = 20, $reverse = 1,
$date = null, $IP = null) { $date = null, $IP = null) {
global $TBSUEXEC_PATH; global $TBSUEXEC_PATH;
...@@ -1341,33 +1355,43 @@ function ShowNodeHistory($node = null, ...@@ -1341,33 +1355,43 @@ function ShowNodeHistory($node = null,
$rtime = 0; $rtime = 0;
$dtime = 0; $dtime = 0;
$nodestr = ""; $nodestr = "";
$arg = "";
$opt = "-ls"; $opt = "-ls";
if (!$showall) { if (!$showall) {
$opt .= "a"; $opt .= "a";
} }
if ($node) { if ($date) {
$node_id = $node->node_id(); $opt .= " -d " . escapeshellarg($date);
}
if ($node_id || $IP) {
if ($IP) {
$opt .= " -i " . escapeshellarg($IP);
$nodestr = "<th>Node</th>";
}
else {
$arg = escapeshellarg($node_id);
}
} }
else { else {
$node_id = ""; $node_id = "";
$opt .= "A"; $opt .= " -A";
$nodestr = "<th>Node</th>"; $nodestr = "<th>Node</th>";
#
# When supplying a date, we want a summary of all nodes at that
# point in time, not a listing.
#
if ($date) {
$opt .= " -c";
}
} }
if ($reverse) { if ($reverse) {
$opt .= "r"; $opt .= " -r";
} }
if ($count) { if ($count) {
$opt .= " -n $count"; $opt .= " -n $count";
} }
if ($date) {
$opt .= " -d " . escapeshellarg($date);
}
if ($IP) {
$opt .= " -i " . escapeshellarg($IP);
}
if ($fp = popen("$TBSUEXEC_PATH nobody nobody ". if ($fp = popen("$TBSUEXEC_PATH nobody nobody ".
" webnode_history $opt $node_id", "r")) { " webnode_history $opt $arg", "r")) {
if (!$showall) { if (!$showall) {
$str = "Allocation"; $str = "Allocation";
} else { } else {
...@@ -1457,8 +1481,10 @@ function ShowNodeHistory($node = null, ...@@ -1457,8 +1481,10 @@ function ShowNodeHistory($node = null,
} }
if ($node_id == "") { if ($node_id == "") {
$nodeurl = CreateURL("shownodehistory",
URLARG_NODEID, $nodeid);
echo "<tr> echo "<tr>
<td>$nodeid</td> <td><a href='$nodeurl'>$nodeid</a></td>
<td>$pid</td> <td>$pid</td>
<td>$eid</td>"; <td>$eid</td>";
if ($PROTOGENI) { if ($PROTOGENI) {
......
...@@ -26,7 +26,9 @@ $optargs = OptionalPageArguments("showall", PAGEARG_BOOLEAN, ...@@ -26,7 +26,9 @@ $optargs = OptionalPageArguments("showall", PAGEARG_BOOLEAN,
"count", PAGEARG_INTEGER, "count", PAGEARG_INTEGER,
"datetime", PAGEARG_STRING, "datetime", PAGEARG_STRING,
"IP", PAGEARG_STRING, "IP", PAGEARG_STRING,
"node", PAGEARG_NODE); # To allow for pcvm search, since they are
# transient and will not map to a node.
"node_id", PAGEARG_STRING);
# #
# Standard Testbed Header # Standard Testbed Header
...@@ -37,35 +39,52 @@ if (!isset($showall)) { ...@@ -37,35 +39,52 @@ if (!isset($showall)) {
$showall = 0; $showall = 0;
} }
if (!isset($count)) { if (!isset($count)) {
$count = 20; $count = 200;
} }
if (!isset($reverse)) { if (!isset($reverse)) {
$reverse = 1; $reverse = 1;
} }
if (!isset($datetime)) { if (isset($datetime) && $datetime != "") {
$datetime = ""; if (! strtotime($datetime)) {
USERERROR("Invalid date specified", 1);
}
$dateopt = "&datetime=" . urlencode($datetime);
}
else {
$dateopt = "";
} }
if (isset($IP)) { if (isset($IP)) {
if (! preg_match('/^[0-9\.]+$/', $IP)) { if (! preg_match('/^[0-9\.]+$/', $IP)) {
USERERROR("Does not look like a valid IP address.", 1); USERERROR("Does not look like a valid IP address.", 1);
} }
$node = Node::LookupByIP($IP); $node = Node::LookupByIP($IP);
# #
# No record might mean the node does not exist, or that it # Switch to a node_id if its a physical node. Otherwise,
# is a virtual node. We are going to pass IP through to the # continue with the IP.
# backend in either case.
# #
if ($node && $node->IsRemote()) { if ($node && !$node->IsVirtNode()) {
unset($node); $node_id = $node->node_id();
$IP = null;
} }
} }
else { else {
$IP = null; $IP = null;
} }
$node_id = (isset($node) ? $node->node_id() : ""); if (isset($node_id)) {
$node_opt = (isset($node) ? "&node_id=$node_id" : ""); $node_opt = "&node_id=$node_id";
$form_opt = "<input type=hidden name=node_id value=$node_id>";
}
else if (isset($IP)) {
$node_opt = "&IP=$IP";
$form_opt = "<input type=hidden name=IP value=$IP>";
}
else {
$node_opt = "";
$form_opt = "";
}
$opts="count=$count&reverse=$reverse$node_opt"; $opts="count=$count&reverse=$reverse$node_opt$dateopt";
echo "<b>Show records:</b> "; echo "<b>Show records:</b> ";
if ($showall) { if ($showall) {
echo "<a href='shownodehistory.php3?$opts'>allocated only</a>, echo "<a href='shownodehistory.php3?$opts'>allocated only</a>,
...@@ -75,7 +94,7 @@ if ($showall) { ...@@ -75,7 +94,7 @@ if ($showall) {
<a href='shownodehistory.php3?$opts&showall=1'>all</a>"; <a href='shownodehistory.php3?$opts&showall=1'>all</a>";
} }