[3] | 1 | #! /usr/bin/perl |
---|
| 2 | # |
---|
| 3 | # Copyright (c) 2004-2007 - Consultas, PKG.fr |
---|
| 4 | # |
---|
| 5 | # This file is part of A2P. |
---|
| 6 | # |
---|
| 7 | # A2P is free software; you can redistribute it and/or modify |
---|
| 8 | # it under the terms of the GNU General Public License as published by |
---|
| 9 | # the Free Software Foundation; either version 2 of the License, or |
---|
| 10 | # (at your option) any later version. |
---|
| 11 | # |
---|
| 12 | # A2P is distributed in the hope that it will be useful, |
---|
| 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
| 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
| 15 | # GNU General Public License for more details. |
---|
| 16 | # |
---|
| 17 | # You should have received a copy of the GNU General Public License |
---|
| 18 | # along with A2P; if not, write to the Free Software |
---|
| 19 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
---|
| 20 | # |
---|
| 21 | # $Id: dbm-status-tools.pl 3 2007-10-18 16:20:19Z guillaume $ |
---|
| 22 | # |
---|
| 23 | |
---|
| 24 | use strict ; |
---|
| 25 | use Time::HiRes qw( usleep gettimeofday tv_interval) ; |
---|
| 26 | use Fcntl qw(:flock); |
---|
| 27 | use GDBM_File ; |
---|
| 28 | use Storable qw( freeze thaw ); |
---|
| 29 | use Data::Dumper; |
---|
| 30 | use A2P::Globals ; |
---|
| 31 | use A2P::Status ; |
---|
| 32 | use A2P::JobStatus ; |
---|
| 33 | |
---|
| 34 | my $jobstatus_base = A2P::JobStatus::dbm_base() ; |
---|
| 35 | |
---|
| 36 | $LOCKID = shift @ARGV if @ARGV ; |
---|
| 37 | |
---|
| 38 | my $gdbm = ( $LOCKID =~ /^\// and -e $LOCKID )? |
---|
| 39 | $LOCKID : $jobstatus_base . $LOCKID ; |
---|
| 40 | |
---|
| 41 | # Check file is really a dbm file with 'file' command |
---|
| 42 | my @control = qx/file $gdbm/ =~ /(dbm).*(database)/i ; |
---|
| 43 | |
---|
| 44 | unless ( -e $gdbm and @control == 2 ) { |
---|
| 45 | die "File $gdbm not found\n" . |
---|
| 46 | "First argument must be the LOCKID for which you to manage status\n" ; |
---|
| 47 | } |
---|
| 48 | |
---|
| 49 | print "Reading $gdbm memory file (".(-s $gdbm).")\n" ; |
---|
| 50 | my %gdbm ; |
---|
| 51 | |
---|
| 52 | sub error { |
---|
| 53 | map { print "$_\n" } @_ ; |
---|
| 54 | return 0 ; |
---|
| 55 | } |
---|
| 56 | |
---|
| 57 | my $LCK ; |
---|
| 58 | my ( $uid, $gid ); |
---|
| 59 | sub get_gdbm { |
---|
| 60 | return error("File $gdbm doesn't exist") |
---|
| 61 | unless -e $gdbm ; |
---|
| 62 | my @stat = stat $gdbm ; |
---|
| 63 | ( $uid, $gid ) = @stat[4..5] ; |
---|
| 64 | open $LCK, '>', $gdbm . '.LCK' |
---|
| 65 | or return error("Can't open " . $gdbm . ".LCK locking file: $!"); |
---|
| 66 | flock $LCK, LOCK_EX |
---|
| 67 | or return error("Can't lock on " . $gdbm . ".LCK: $!"); |
---|
| 68 | tie %gdbm, "GDBM_File", $gdbm, GDBM_WRITER, 0666 |
---|
| 69 | or return error("Can't tie $gdbm gdbm: $!"); |
---|
| 70 | 1 ; |
---|
| 71 | } |
---|
| 72 | |
---|
| 73 | sub release_gdbm { |
---|
| 74 | untie %gdbm ; |
---|
| 75 | flock $LCK, LOCK_UN ; |
---|
| 76 | close($LCK); |
---|
| 77 | chown $uid, $gid, $gdbm ; |
---|
| 78 | } |
---|
| 79 | |
---|
| 80 | sub dump_status { |
---|
| 81 | my $this = shift or return "" ; |
---|
| 82 | my $dirty = "_".$this->dirty_bit()."_".$this->dirty_bit(3)."____" ; |
---|
| 83 | return $this->get_sid_key.": Job ".$this->is_job. |
---|
| 84 | " [$this->{STATUS}] step=$this->{STEP}: ". |
---|
| 85 | " (".join('',@{$this->{STATE}}).") [Rev$this->{REV}; Sid:$this->{SID} ;" |
---|
| 86 | ." dirty bits: $dirty ]" ; |
---|
| 87 | } |
---|
| 88 | |
---|
| 89 | sub confirm { |
---|
| 90 | print "Are you sure to do this [y/N] ?" ; |
---|
| 91 | my $response = lc(getc); |
---|
| 92 | print "\n" ; |
---|
| 93 | return $response eq 'y' ? 1 : 0 ; |
---|
| 94 | } |
---|
| 95 | |
---|
| 96 | # Prepare cache and A2P::Status privates |
---|
| 97 | my $cache = { __GDBM_WRITER__ => 1 }; |
---|
| 98 | my $s = new A2P::Status($cache,\%gdbm,'init'); |
---|
| 99 | $s->remove_from_cache ; |
---|
| 100 | |
---|
| 101 | my $quit = 0 ; |
---|
| 102 | my $index = -1 ; |
---|
| 103 | my $selected = -1 ; |
---|
| 104 | my $status ; |
---|
| 105 | my $name ; |
---|
| 106 | |
---|
| 107 | while (!$quit) { |
---|
| 108 | |
---|
| 109 | sleep 1 and next unless (&get_gdbm()); |
---|
| 110 | |
---|
| 111 | my @keys = sort(keys(%gdbm)) ; |
---|
| 112 | print "===== Service $LOCKID =====\n", |
---|
| 113 | "Memory DB keys: ",scalar(@keys)," found\n" ; |
---|
| 114 | |
---|
| 115 | my %keys = map { $_ => 1 } @keys ; |
---|
| 116 | my @lost = () ; |
---|
| 117 | my %rev = map { |
---|
| 118 | delete $keys{$_} ; |
---|
| 119 | /^(.+)_rev$/ ; |
---|
| 120 | push @lost , $_ unless (exists($gdbm{$1})); |
---|
| 121 | $_ => thaw($gdbm{$_}) |
---|
| 122 | } grep { /_rev$/ } @keys ; |
---|
| 123 | |
---|
| 124 | print "Lost revisions (".scalar(@lost)."): @lost\n" |
---|
| 125 | if ( @lost = sort(@lost) ); |
---|
| 126 | |
---|
| 127 | my %ref = map { |
---|
| 128 | delete $keys{$_} ; |
---|
| 129 | $_ => $gdbm{$_} |
---|
| 130 | } grep { ($gdbm{$_} =~ /^\d+$/ and exists($gdbm{'_SID_'.$gdbm{$_}})) or |
---|
| 131 | exists($gdbm{$gdbm{$_}}) } keys(%keys) ; |
---|
| 132 | my @ref = sort(keys(%ref)) ; |
---|
| 133 | print "References found: ", scalar(@ref), "\n" ; |
---|
| 134 | |
---|
| 135 | my %status = map { |
---|
| 136 | delete $keys{$_} ; |
---|
| 137 | $_ => thaw($gdbm{$_}) |
---|
| 138 | } grep { $gdbm{$_} =~ /\x04/ } keys(%keys) ; |
---|
| 139 | my @status = sort(keys(%status)) ; |
---|
| 140 | print "Status found: ", scalar(@status), "\nRevisions found: ", |
---|
| 141 | scalar(keys(%rev)), "\n" ; |
---|
| 142 | my $count = 2*@status + @ref ; |
---|
| 143 | if ( $count == @keys ) { |
---|
| 144 | print "Count is coherent\n" ; |
---|
| 145 | } else { |
---|
| 146 | print "Bad count found: ".(@keys - $count)." more keys found\n" ; |
---|
| 147 | } |
---|
| 148 | |
---|
| 149 | my @unexpected = sort(keys(%keys)) ; |
---|
| 150 | print "Not expected keys: @unexpected\n" if @unexpected; |
---|
| 151 | |
---|
| 152 | my $_SID_count = grep { /^_SID_(\d+)$/ } keys(%status) ; |
---|
| 153 | my $_SID_rev_count = grep { /^_SID_(\d+)_rev$/ } keys(%rev) ; |
---|
| 154 | print "_SID_# count: $_SID_count (+$_SID_rev_count _rev)\n" ; |
---|
| 155 | |
---|
| 156 | &release_gdbm() ; |
---|
| 157 | |
---|
| 158 | if ($selected>=0) { |
---|
| 159 | $name = $status[$selected] ; |
---|
| 160 | $status = $status{$name} ; |
---|
| 161 | |
---|
| 162 | } elsif ($selected > $#status) { |
---|
| 163 | $selected = $#status ; |
---|
| 164 | $name = $status[$selected] ; |
---|
| 165 | $status = $status{$name} ; |
---|
| 166 | } |
---|
| 167 | |
---|
| 168 | unless (defined($status[$selected]) and $name == $status[$selected]) { |
---|
| 169 | $selected = -1 ; |
---|
| 170 | $name = $status = undef ; |
---|
| 171 | } |
---|
| 172 | |
---|
| 173 | # Actions |
---|
| 174 | my $a = 0 ; |
---|
| 175 | while (!$quit) { |
---|
| 176 | print "\n", |
---|
| 177 | "q -> Quit l -> list jobs m -> list refs \n", |
---|
| 178 | "r -> Refresh e -> evaluation j -> select a job \n", |
---|
| 179 | "s -> Shrink dbm d -> delete job v -> validate job \n", |
---|
| 180 | "a -> list job refs x -> dump dbm keys u -> unselect job \n", |
---|
| 181 | "t -> list twin jobs \n", |
---|
| 182 | "f -> Flush lost/unexpected or done \n", |
---|
| 183 | "[0-9] -> select a job n/p -> next/previous indexed jobs list \n" |
---|
| 184 | unless ( $a ++ % 5 ); |
---|
| 185 | if ($index>=0) { |
---|
| 186 | print "Current index = $index\n" ; |
---|
| 187 | for my $i ( 0..9 ) { |
---|
| 188 | if (defined($status[$index+$i]) |
---|
| 189 | and exists($status{$status[$index+$i]})) { |
---|
| 190 | my $this = $status{$status[$index+$i]} ; |
---|
| 191 | print "$i - ",dump_status($this),"\n" ; |
---|
| 192 | } elsif (defined($status[$index+$i])) { |
---|
| 193 | print "$i - Unavailable ",$status[$index+$i],"\n" ; |
---|
| 194 | } |
---|
| 195 | } |
---|
| 196 | } |
---|
| 197 | print "DB memory file size: ".(-s $gdbm)."\n" ; |
---|
| 198 | print "Selected job '$name': ", dump_status($status), "\n", Dumper($status), |
---|
| 199 | "('$name' selected, type 'u' to unselect it)\n" |
---|
| 200 | if (defined($name) and defined($status)); |
---|
| 201 | print "Command ? " ; |
---|
| 202 | system "stty", '-icanon', 'eol', "\001"; |
---|
| 203 | my $key = lc(getc) ; |
---|
| 204 | print "\n" ; |
---|
| 205 | |
---|
| 206 | $quit ++ if ($key eq 'q'); # Quit |
---|
| 207 | last if ($key eq 'r'); # Refresh |
---|
| 208 | |
---|
| 209 | if ($key eq 'j') { |
---|
| 210 | print "Job: " ; |
---|
| 211 | system "stty", 'icanon', 'eol', '^@' ; |
---|
| 212 | my $job = <STDIN> ; |
---|
| 213 | chomp($job); |
---|
| 214 | if (exists($status{$job})) { |
---|
| 215 | $status = $status{$job} ; |
---|
| 216 | } else { |
---|
| 217 | print "$job doesn't exists\n"; |
---|
| 218 | } |
---|
| 219 | |
---|
| 220 | } elsif ($key eq 'u') { |
---|
| 221 | undef $name ; |
---|
| 222 | undef $status ; |
---|
| 223 | $selected = -1 ; |
---|
| 224 | |
---|
| 225 | } elsif ($key eq 'e') { |
---|
| 226 | print "Evaluation: " ; |
---|
| 227 | system "stty", 'icanon', 'eol', '^@' ; |
---|
| 228 | my $eval = <STDIN> ; |
---|
| 229 | chomp($eval); |
---|
| 230 | print eval $eval, "\n" ; |
---|
| 231 | |
---|
| 232 | } elsif ($key eq 'v' and defined($name) and defined($status)) { |
---|
| 233 | get_gdbm ; |
---|
| 234 | if (tied(%gdbm) and confirm) { |
---|
| 235 | $status->{STATUS} = 'DONE' ; |
---|
| 236 | $status->{STATE}->[12] = 'o' ; |
---|
| 237 | $status->{REV} += 100 ; |
---|
| 238 | $status->set_dirty_bit(3) ; |
---|
| 239 | $status->cache_timer ; |
---|
| 240 | $gdbm{$name} = freeze($status) ; |
---|
| 241 | } |
---|
| 242 | release_gdbm ; |
---|
| 243 | last ; # Refresh |
---|
| 244 | |
---|
| 245 | } elsif ($key eq 'd' and defined($name) and defined($status)) { |
---|
| 246 | get_gdbm ; |
---|
| 247 | $status->remove_from_tied_hash if (tied(%gdbm) and confirm); |
---|
| 248 | release_gdbm ; |
---|
| 249 | # Select next |
---|
| 250 | $selected += $selected < $#status ? 1 : -1 ; |
---|
| 251 | $status = $status{$status[$selected]} ; |
---|
| 252 | $name = $status[$selected] ; |
---|
| 253 | last ; # Refresh |
---|
| 254 | |
---|
| 255 | } elsif ($key eq 'f') { |
---|
| 256 | get_gdbm ; |
---|
| 257 | if (tied(%gdbm) and confirm) { |
---|
| 258 | if (@lost or @unexpected) { |
---|
| 259 | map { print '*' ; delete $gdbm{$_} } @lost ; |
---|
| 260 | map { print '+' ; delete $gdbm{$_} } @unexpected ; |
---|
| 261 | |
---|
| 262 | } else { |
---|
| 263 | my $stop = 0 ; |
---|
| 264 | $SIG{INT} = sub { $stop ++ } ; |
---|
| 265 | my $i = 0 ; |
---|
| 266 | $| = 1 ; |
---|
| 267 | foreach $name (keys(%status)) { |
---|
| 268 | $status = $status{$name} ; |
---|
| 269 | last if $stop ; |
---|
| 270 | if ($status->is_abterm |
---|
| 271 | or $status->step_status(12) ne 'o') { |
---|
| 272 | print '-' ; |
---|
| 273 | } else { |
---|
| 274 | delete $gdbm{$name} ; |
---|
| 275 | print '*' ; |
---|
| 276 | } |
---|
| 277 | print ++$i % 100 ? "" : "\n" ; |
---|
| 278 | } |
---|
| 279 | } |
---|
| 280 | print "\n" ; |
---|
| 281 | } |
---|
| 282 | release_gdbm ; |
---|
| 283 | # Remove any selection |
---|
| 284 | $selected = -1 ; |
---|
| 285 | $status = $name = undef ; |
---|
| 286 | last ; # Refresh |
---|
| 287 | |
---|
| 288 | } elsif ($key eq 'x') { |
---|
| 289 | get_gdbm ; |
---|
| 290 | print "Warning: You must abort quickly dumping if service is running\n", |
---|
| 291 | "Gdbm dump:\n"; |
---|
| 292 | my ( $i, $l, $line ) = ( 0, 0, 0 ) ; |
---|
| 293 | my @list = sort(keys(%gdbm)) ; |
---|
| 294 | foreach $key (@list) { |
---|
| 295 | my $L = length($key)>30 ? 1 : 0 ; |
---|
| 296 | if ($l or $L) { |
---|
| 297 | print "\n" ; |
---|
| 298 | $i = 0 ; |
---|
| 299 | $l = $L ; |
---|
| 300 | $line ++ ; |
---|
| 301 | } |
---|
| 302 | |
---|
| 303 | # Extract comprehensible value |
---|
| 304 | my $value = ref($gdbm{$key}) || $gdbm{$key} ; |
---|
| 305 | #$value = ref($value) |
---|
| 306 | $value = ref(thaw($value)) if ( $value =~ /\x04/ ); |
---|
| 307 | |
---|
| 308 | # Keep value as a 30 chars length string |
---|
| 309 | my $text = sprintf(": %-26s| ",substr($value,0,26)) ; |
---|
| 310 | |
---|
| 311 | print sprintf("%30s%s",$key,$text); |
---|
| 312 | |
---|
| 313 | # Avoid to output pres a key message when line=0 |
---|
| 314 | if ( ++$i % 3 ) { |
---|
| 315 | next ; |
---|
| 316 | |
---|
| 317 | } else { |
---|
| 318 | print "\n" ; |
---|
| 319 | $line ++ ; |
---|
| 320 | } |
---|
| 321 | |
---|
| 322 | unless ( $line % 20 ) { |
---|
| 323 | print " |
---|
| 324 | [ Press any key to continue but [a] to abort the dump ]" ; |
---|
| 325 | system "stty", '-icanon', 'eol', "\001"; |
---|
| 326 | my $press = lc(getc) ; |
---|
| 327 | last if ( $press eq 'a' ); |
---|
| 328 | print "\n" ; |
---|
| 329 | ( $i, $l ) = ( 0, 0 ) ; |
---|
| 330 | } |
---|
| 331 | } |
---|
| 332 | print "\n" ; |
---|
| 333 | release_gdbm ; |
---|
| 334 | |
---|
| 335 | } elsif ($key eq 't') { |
---|
| 336 | print "Twin status:\n"; |
---|
| 337 | my ( $twin, $same, $checkcount ) = ( 0, 0, 0 ); |
---|
| 338 | my %done = () ; |
---|
| 339 | my @list = sort(keys(%status)) ; |
---|
| 340 | my $listtotal = 0 ; |
---|
| 341 | $| = 1 ; |
---|
| 342 | foreach my $check (@list) { |
---|
| 343 | foreach my $cmp |
---|
| 344 | (grep { ! exists($done{'__'.$check.'__'.$_.'__'}) } @list) { |
---|
| 345 | # Don't compare on the same key |
---|
| 346 | next if ( $check eq $cmp ); |
---|
| 347 | |
---|
| 348 | # Don't compare if still compared |
---|
| 349 | $done{'__'.$cmp.'__'.$check.'__'} = 1 ; |
---|
| 350 | |
---|
| 351 | if ( $status{$check} eq $status{$cmp} ) { |
---|
| 352 | print "$check and $cmp are twins\n" ; |
---|
| 353 | $twin ++ ; |
---|
| 354 | |
---|
| 355 | } elsif ( $status{$check}->{JOBID} eq $status{$cmp}->{JOBID} ) { |
---|
| 356 | print "$check and $cmp are for the same job, but with ", |
---|
| 357 | $status{$check}->{REV}, " vs ", $status{$cmp}->{REV}, |
---|
| 358 | " revision, and ", $status{$check}->{STEP}, ":'", |
---|
| 359 | $status{$check}->step_status($status{$check}->{STEP}), |
---|
| 360 | "' vs ", $status{$cmp}->{STEP}, ":'", |
---|
| 361 | $status{$cmp}->step_status($status{$cmp}->{STEP}), |
---|
| 362 | " step/status\n" ; |
---|
| 363 | $same ++ ; |
---|
| 364 | } |
---|
| 365 | |
---|
| 366 | $checkcount ++ ; |
---|
| 367 | } |
---|
| 368 | print sprintf("%.2f%% \r", ++$listtotal * 100 / @list ); |
---|
| 369 | } |
---|
| 370 | print "Checks: $checkcount, Twins: $twin, Same job status: $same\n" ; |
---|
| 371 | |
---|
| 372 | } elsif ($key eq 'l') { |
---|
| 373 | print "Job keys:\n"; |
---|
| 374 | my $i = 0 ; my @list = sort(keys(%status)) ; |
---|
| 375 | foreach $key (@list) { |
---|
| 376 | print sprintf("%30s",$key), ++$i % 5 ? " " : "\n" ; |
---|
| 377 | } |
---|
| 378 | print "\n" if ($i % 5); |
---|
| 379 | |
---|
| 380 | } elsif ($key eq 'm') { |
---|
| 381 | print "Ref keys:\n" ; |
---|
| 382 | my $i = 0 ; my @list = sort(keys(%ref)) ; |
---|
| 383 | foreach $key (@list) { |
---|
| 384 | print sprintf("%20s -> %5s",$key,$ref{$key}), |
---|
| 385 | ++$i % 5 ? " | " : "\n" ; |
---|
| 386 | } |
---|
| 387 | print "\n" if ($i % 5); |
---|
| 388 | |
---|
| 389 | } elsif ($key eq 'a' and $name) { |
---|
| 390 | print "$name references:\n" ; |
---|
| 391 | my $i = 0 ; my @list = grep { |
---|
| 392 | $ref{$_} eq $name or |
---|
| 393 | ($ref{$_} =~ /^\d+$/ and $name eq '_SID_'.$ref{$_}) |
---|
| 394 | } sort(keys(%ref)) ; |
---|
| 395 | my $list_count = 0 ; my @chained = () ; # Search for chained refs |
---|
| 396 | while ( @list + @chained != $list_count ) { |
---|
| 397 | push @chained, @list ; |
---|
| 398 | $list_count = @chained ; |
---|
| 399 | @list = grep { $key = $ref{$_} ; grep { /^$key$/ } @list |
---|
| 400 | } sort(keys(%ref)) ; |
---|
| 401 | } |
---|
| 402 | foreach $key (@chained) { |
---|
| 403 | print sprintf("%40s",$key), ++$i % 5 ? " " : "\n" ; |
---|
| 404 | } |
---|
| 405 | print "\n" if ($i % 5); |
---|
| 406 | |
---|
| 407 | } elsif ($key eq 'n' or $key eq 'p') { |
---|
| 408 | if ( $key eq 'n') { |
---|
| 409 | if ($index<0) { |
---|
| 410 | $index = 0 ; |
---|
| 411 | } elsif (keys(%status)>$index+10) { |
---|
| 412 | $index += 10 ; |
---|
| 413 | } |
---|
| 414 | } elsif ($key eq 'p') { |
---|
| 415 | if ($index>10) { |
---|
| 416 | $index -= 10 ; |
---|
| 417 | } else { |
---|
| 418 | $index = 0 ; |
---|
| 419 | } |
---|
| 420 | } |
---|
| 421 | |
---|
| 422 | } elsif ($key =~ /^\d$/) { |
---|
| 423 | $selected = $index+$key ; |
---|
| 424 | $status = $status{$status[$selected]} ; |
---|
| 425 | $name = $status[$selected] ; |
---|
| 426 | |
---|
| 427 | } elsif ($key eq 's') { |
---|
| 428 | get_gdbm ; |
---|
| 429 | my $tied = tied(%gdbm) ; |
---|
| 430 | $tied->reorganize if (defined($tied) and confirm); |
---|
| 431 | release_gdbm ; |
---|
| 432 | } |
---|
| 433 | } # End while actions |
---|
| 434 | } |
---|
| 435 | |
---|
| 436 | system "stty", 'icanon', 'eol', '^@' ; |
---|