File Coverage

blib/lib/PBS/Logs.pm
Criterion Covered Total %
statement 143 147 97.2
branch 88 110 80.0
condition 22 51 43.1
subroutine 20 21 95.2
pod 0 13 0.0
total 273 342 79.8


line stmt bran cond sub pod time code
1             package PBS::Logs;
2              
3             =head1 NAME
4              
5             PBS::Logs - general parser for PBS log files
6              
7             =head1 SYNOPSIS
8              
9             See the sections below:
10              
11             use PBS::Logs;
12              
13             =head1 DESCRIPTION
14              
15             =head2 EXPORT
16              
17             None by default.
18              
19             =head1 SEE ALSO
20              
21             =over
22              
23             =item The PBS Pro 5.4 Administrator Guide
24              
25             =item PBS::Logs::Acct
26              
27             =item PBS::Logs::Event
28              
29             =back
30              
31             =head1 AUTHOR
32              
33             Dr R K Owen, Erkowen@nersc.govE
34              
35             =head1 COPYRIGHT AND LICENSE
36              
37             Copyright (C) 2005 The Regents of the University of California
38              
39             This library is free software; you can redistribute it
40             and/or modify it under the terms of the GNU Lesser General
41             Public License as published by the Free Software Foundation;
42             either version 2.1 of the License, or (at your option) any
43             later version.
44              
45             This library is distributed in the hope that it will be useful,
46             but WITHOUT ANY WARRANTY; without even the implied warranty
47             of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
48             the GNU Lesser General Public License for more details,
49             which can be found at:
50              
51             http://www.gnu.org/copyleft/lesser.html
52             or http://www.opensource.org/licenses/lgpl-license.php
53              
54             =cut
55              
56 44     44   460451 use 5.006;
  44         150  
  44         1750  
57 44     44   227 use strict;
  44         81  
  44         1467  
58 44     44   220 use warnings;
  44         81  
  44         1235  
59 44     44   225 use Carp;
  44         78  
  44         3154  
60 44     44   46732 use Time::Local;
  44         109044  
  44         8082  
61              
62             our @ISA = qw();
63              
64             our $VERSION = '0.05';
65              
66             my $debug = 0;
67              
68             my $datetime_regex = '(\d{2})/(\d{2})/(\d{4}) (\d{2}):(\d{2}):(\d{2})';
69              
70             BEGIN {
71 44     44   139 my @fields = qw{input type};
72             # construct read-only accessor functions here - no need for AUTOLOAD
73 44         133 foreach my $f (@fields) {
74 88         416 my $code = "package ".__PACKAGE__.";\n"
75             .qq{sub $f {
76             my \$self = shift;
77             carp __PACKAGE__."->$f \$self (".join(',',\@_).")\n"
78             if (\$debug || \$self->{'-debug'});
79             \$self->{'$f'};
80             }
81             };
82 88 0 0 0 0 111325 eval $code;
  0 50 33 50 0 0  
  0         0  
  0         0  
  50         1243  
  50         514  
  50         320  
83             }
84             }
85              
86             # Preloaded methods go here.
87              
88             =head1 new('file_name')
89              
90             =head1 new(\@array_ref)
91              
92             =head1 new(\*FILE_HANDLE)
93              
94             Create a PBS::Logs object.
95             It takes only one argument which is either a filename, array reference,
96             or a FILE glob reference.
97              
98             Pass a PBS log file name to read:
99              
100             my $pl = new PBS::Logs('/var/spool/PBS/server_logs/20050512');
101              
102             Slurp the file into an array and pass the array reference
103              
104             open PL, '/var/spool/PBS/server_logs/20050512'
105             || die "can not open log";
106             my @pl = ;
107             my $pl = new PBS::Logs(\@pl);
108              
109             Or finally, pass a FILEHANDLE glob. This can be useful if creating a filter.
110              
111             my $pl = new PBS::Logs(\*STDIN);
112              
113             =cut
114              
115             sub new {
116 48     48 0 12359 my $proto = shift;
117 48   33     385 my $class = ref($proto) || $proto;
118 48         412 my $self = {
119             '-debug' => 0,
120             '-lastline' => undef,
121             '-start' => undef,
122             '-end' => undef,
123             'input' => undef,
124             'line' => 0,
125             'type' => undef,
126             };
127 48 50 33     496 carp __PACKAGE__.": creating $self\n"
128             if ($debug || $self->{'-debug'});
129 48         412 my $x = shift;
130 48 100       287 if (ref $x eq "ARRAY") { # slurped ARRAY
    100          
    100          
131 26         65 $self->{'input'} = $x;
132 26         67 $self->{'type'} = 'ARRAY';
133             } elsif (ref $x eq "GLOB") { # FILEHANDLE
134 8         43 $self->{'input'} = $x;
135 8         23 $self->{'type'} = 'FILTER';
136             } elsif (! ref $x) { # filename
137 10 100       874 open PBSIN, $x
138             or croak __PACKAGE__.": new - can not open '$x'";
139 8         29 $self->{'input'} = \*PBSIN;
140 8         24 $self->{'type'} = 'FILE';
141             } else {
142 4         582 croak __PACKAGE__
143             .": new - must pass either filename, array reference, "
144             ."or filehandle glob ... not ".ref($x)." '$x'";
145             }
146 42         113 bless ($self, $class);
147 42         179 return $self;
148             }
149              
150             sub DESTROY {
151 42     42   17927 my $self = shift;
152 42 50 33     457 carp __PACKAGE__.": destroying $self\n"
153             if ($debug || $self->{'-debug'});
154 42 100       13030 close $self->{'input'} if ref $self->{'input'} eq "GLOB";
155             }
156              
157             sub END {
158 44 50   44   2595 carp __PACKAGE__.": ending\n"
159             if ($debug);
160             }
161              
162             =head1 debug([enable])
163              
164             Debugging can be enabled for the entire class by calling
165             C.
166              
167             Or debugging can be enabled for a single object with
168             C<$obj-Edebug(1)>.
169              
170             To disable debugging just set to 0.
171              
172             Calling either form with no argument will just cause
173             the current value to be returned.
174              
175             =cut
176              
177             sub debug {
178 16     16 0 7004 my $self = shift;
179 16 100       49 if (index(ref($self), __PACKAGE__) == 0) { # just myself
180 12 100       90 @_ ? $self->{'-debug'} = shift
181             : $self->{'-debug'};
182             } else { # whole class
183 4 100       19 defined($self) ? $debug = $self
184             : $debug;
185             }
186             }
187              
188             =head1 line()
189              
190             Return the "log line number" that will be read next (zero based),
191             and returns -1 when at the "end of file". (Remember the "file"
192             could have been slurped into an array.)
193              
194             =cut
195              
196             sub line {
197 1169     1169 0 6157 my $self = shift;
198 1169 50 33     6081 carp __PACKAGE__." : line $self (".join(',',@_).")\n"
199             if ($debug || $self->{'-debug'});
200 1169 100       2591 return undef if ! defined $self->{'line'};
201             # the line count is always high by one since we must pre-read a line
202 1153 100       7344 $self->{'line'} > 0 ? $self->{'line'} - 1 : $self->{'line'};
203             }
204              
205             =head1 current()
206              
207             Return the "current" concatenated PBS record that has been read and that
208             meets the selection criterion. Remember, though, that actuall PBS logs can
209             have a record that is spread across multiple lines.
210             New records begin with a date/time-stamp.
211             This gives the entire record as one line.
212              
213             =cut
214              
215             sub current {
216 1231     1231 0 1866 my $self = shift;
217 1231 50 33     8889 carp __PACKAGE__." : current $self (".join(',',@_).")\n"
218             if ($debug || $self->{'-debug'});
219 1231 100       4585 return undef if ! defined $self->{'current'};
220 1015         5078 $self->{'current'};
221             }
222              
223             =head1 start()
224              
225             Begin reading at the start of the log, if not a filter.
226              
227             =cut
228              
229             sub start {
230 66     66 0 148 my $self = shift;
231 66 50 33     484 carp __PACKAGE__.": start $self(".join(',',@_).")\n"
232             if ($debug || $self->{'-debug'});
233 66 100       263 if ($self->{'type'} eq "FILE") {
234 8 50       110 seek $self->{'input'}, 0, 0
235             or croak __PACKAGE__.": start - can not seek on file";
236             }
237 66         129 $self->{'-lastline'} = undef;
238 66 50       241 $self->{'line'} = 0 if ($self->{'type'} ne 'FILTER');
239 66         201 $self->{'current'} = undef;
240             }
241              
242             =head1 end()
243              
244             End reading of the log and close it out, if not a filter.
245             Sets all the internal values to undef.
246              
247             =cut
248              
249             sub end {
250 16     16 0 39 my $self = shift;
251 16 50 33     218 carp __PACKAGE__.": end $self(".join(',',@_).")\n"
252             if ($debug || $self->{'-debug'});
253 16 100       88 if ($self->{'type'} eq "FILE") {
254 8         298 close $self->{'input'};
255             }
256 16         41 $self->{'-lastline'} = undef;
257 16         33 $self->{'-start'} = undef;
258 16         36 $self->{'-end'} = undef;
259 16         41 $self->{'input'} = undef;
260 16         34 $self->{'line'} = undef;
261 16         33 $self->{'current'} = undef;
262 16         45 $self->{'type'} = undef;
263             }
264              
265             #=head1 getline()
266             #
267             #Get the next text line from the log returning a string
268             # (stripped of trailing \n's).
269             #This method is used internally only, and should not be called directly
270             #
271             #=cut
272              
273             sub getline {
274 4633     4633 0 5437 my $self = shift;
275 4633 50 33     18170 carp __PACKAGE__.": getline $self(".join(',',@_).")\n"
276             if ($debug || $self->{'-debug'});
277              
278 4633         5210 my $l = undef;
279 4633 100       8835 if ($self->{'type'} eq 'ARRAY') {
280 3709         20352 $l = $self->{'input'}->[$self->{'line'}]
281 3709 100 100     3471 if scalar @{$self->{'input'}} > $self->{'line'}
282             && $self->{'line'} != -1;
283             } else {
284 924 100       3923 $l = readline $self->{'input'}
285             if not eof($self->{'input'});
286             }
287              
288 4633 100       7690 if (defined $l) {
289 4485         6118 chomp $l;
290 4485         5612 $self->{'line'}++;
291             } else { # reached EOF
292 148         313 $self->{'line'} = -1;
293 148         284 $self->{'current'} = undef;
294             }
295 4633         12349 $l; # return array ref
296             }
297              
298             #=head1 getdata()
299             #
300             #Get the next data batch from the log returning an array reference
301             #of elements.
302             #This method is used internally only, and should not be called directly
303             #
304             #=cut
305              
306             sub getdata {
307 1389     1389 0 1723 my $self = shift;
308 1389 50 33     5662 carp __PACKAGE__.": getdata $self(".join(',',@_).")\n"
309             if ($debug || $self->{'-debug'});
310              
311 1389         2750 my ($a,$l,$line) = (undef,undef,$self->{'-lastline'});
312              
313 1389 100       2938 $line = $self->getline() if ! defined $line;
314              
315 1389         2932 while ($l = $self->getline()) {
316 4385 50       8757 last if ! defined $l;
317 4385 100       20269 if ($l =~ /^$datetime_regex/) {
318 1265         2092 $self->{'-lastline'} = $l;
319 1265         2032 last;
320             } else { # a continuation record
321 3120         9003 $line .= " $l";
322             }
323             }
324 1389 100       2657 $self->{'-lastline'} = undef if ! defined $l;
325              
326 1389 100       2093 if (defined $line) {
327 1365         6501 $a = [split(';',$line)];
328 1365         3156 $self->{'current'} = $line;
329             } else {
330 24         46 $self->{'current'} = undef;
331             }
332              
333 1389         3794 $a; # return array ref
334             }
335              
336             =head1 get()
337              
338             Get the next entry from the log and return as an array reference
339             if in an scalar context, else return a list if called otherwise.
340              
341             $a = $pl->get(); # returns array reference
342             @a = $pl->get(); # returns array
343              
344             However, at the end of the log the array reference context will return undef
345             and the array context will return an empty list ();
346              
347             =cut
348              
349             sub get {
350 1243     1243 0 1684 my $self = shift;
351 1243 50 33     7216 carp __PACKAGE__.": get $self(".join(',',@_).")\n"
352             if ($debug || $self->{'-debug'});
353              
354 1243 100       2924 if ($self->{'line'} == -1) { # nothing to do at EOF
355 76         173 $self->{'current'} = undef;
356 76 100       364 return () if (wantarray);
357 61         212 return undef;
358             }
359              
360             # my ($a,$l,$line) = (undef,undef,$self->{'-lastline'});
361 1167         2612 my $a;
362 1167         3936 while ($a = $self->getdata()) {
363 1365         3467 my $dt = $self->datetime($a->[0]);
364 1365 100 100     39197 next if defined($self->{'-start'})
365             && ($dt < $self->{'-start'});
366 1275 100 100     3951 next if defined($self->{'-end'})
367             && ($dt > $self->{'-end'});
368 1143         1429 last;
369             }
370              
371 1167 50       2410 return if ! defined wantarray; # just read log line
372 1167 50       3201 return (defined $a ? @$a : ()) if (wantarray); # return array
    100          
373 967         2738 $a; # return array ref
374             }
375              
376             =head1 datetime($datetime)
377              
378             Parse the PBS date-time string and return the number of seconds
379             since the epoch if in a scalar context (UTC time),
380             or return a 6-element array similar to the gmtime() or localtime()
381             functions with
382             (0:$sec, 1:$min, 2:$hour, 3:$mday, 4:$mon, 5:$year)
383             where $mon is in the range 0..11 and $year is a 4-digit year.
384              
385             $dt = '02/01/2005 18:48:10';
386             $a = $pl->datetime($dt); # returns seconds since January 1, 1970 UTC
387             @a = $pl->datetime($dt); # returns array
388              
389             =cut
390              
391             sub datetime {
392 1409     1409 0 1706 my $self = shift;
393 1409 50 33     5951 carp __PACKAGE__.": datetime $self(".join(',',@_).")\n"
394             if ($debug || $self->{'-debug'});
395              
396 1409         1882 my $dt = shift;
397 1409 50       2653 carp __PACKAGE__.": datetime : received an undefined value"
398             if ! defined $dt;
399              
400 1409         10140 my @dt = $dt =~ /$datetime_regex/;
401              
402 1409 50       5398 if (wantarray) {
403             # rewrite in proper order
404 0         0 return ($dt[5],$dt[4],$dt[3],$dt[1],$dt[0]-1,$dt[2]);
405             } else {
406 1409         6534 return timegm($dt[5],$dt[4],$dt[3],$dt[1],$dt[0]-1,$dt[2]);
407             }
408             }
409              
410             =head1 filter_datetime([start,end])
411              
412             Sets or reads the datetime filter for the get() method.
413              
414             get() will only retrieve lines that have a datetime between
415             "start" and "end" inclusive.
416              
417             Either one can be 'none' to signify that no filtering will be
418             performed with respect to that time endpoint. No filtering is
419             essentially ('none','none'). Or just do not call this method.
420              
421             The start or end value can be given either in the PBS datetime
422             format ( DD/MM/YYYY HH:MM:SS ) or in seconds from the epoch.
423              
424             It will return '1' if successful, else undef if some warning occurs.
425              
426             If no arguments are given then the method will return an array
427             (start,end) where the values are in seconds since the epoch.
428              
429             =cut
430              
431             sub filter_datetime {
432 96     96 0 41235 my $self = shift;
433 96 50 33     514 carp __PACKAGE__.": filter_datetime $self(".join(',',@_).")\n"
434             if ($debug || $self->{'-debug'});
435              
436 96         161 my ($st,$et) = @_;
437              
438 96 100       283 return ($self->{'-start'}, $self->{'-end'})
439             if (! defined $st);
440              
441 69 100       173 if (! defined $et) {
442 3         678 carp __PACKAGE__
443             .": filter_datetime : received an undefined value";
444 3         287 return undef;
445             }
446              
447 66 100       5318 if ($st eq 'none') {
    100          
    100          
448 32         71 $self->{'-start'} = undef;
449             } elsif ($st =~ /^\d+$/) {
450 6         12 $self->{'-start'} = $st;
451             } elsif ($st =~ /^$datetime_regex$/) {
452 22         73 $self->{'-start'} = $self->datetime($st);
453             } else {
454 6         577 carp __PACKAGE__.": filter_datetime : bad start value = '"
455             .$st."'";
456 6         385 return undef;
457             }
458              
459 60 100       1400 if ($et eq 'none') {
    100          
    100          
460 26         46 $self->{'-end'} = undef;
461             } elsif ($et =~ /^\d+$/) {
462 6         31 $self->{'-end'} = $et;
463             } elsif ($et =~ /^$datetime_regex$/) {
464 22         67 $self->{'-end'} = $self->datetime($et);
465             } else {
466 6         627 carp __PACKAGE__.": filter_datetime : bad end value = '"
467             .$et."'";
468 6         486 return undef;
469             }
470 54         742 1;
471             }
472              
473             1;
474             __END__