File Coverage

blib/lib/PBS/Logs/Acct.pm
Criterion Covered Total %
statement 111 112 99.1
branch 55 70 78.5
condition 12 27 44.4
subroutine 12 12 100.0
pod 0 6 0.0
total 190 227 83.7


line stmt bran cond sub pod time code
1             package PBS::Logs::Acct;
2              
3             =head1 NAME
4              
5             PBS::Logs::Acct - parses the PBS accounting log files
6             and inherits from PBS::Logs.
7              
8             =head1 SYNOPSIS
9              
10             See the sections below:
11              
12             use PBS::Logs::Acct;
13              
14             The only non-inheritable function is the class level debug()
15              
16             PBS::Logs::Acct::debug()
17              
18             You must use
19              
20             PBS::Logs::debug()
21              
22             to read or set global debugging.
23             However, the instance version works just fine:
24              
25             $pl->debug()
26              
27             Other than that
28             PBS::Logs::Acct inherits all the methods that are available from
29             PBS::Logs, plus adds the methods listed below.
30              
31             =head1 DESCRIPTION
32              
33             =head2 EXPORT
34              
35             Can export message_hash() and message_hash_dump()
36              
37             =head1 SEE ALSO
38              
39             =over
40              
41             =item PBS::Logs
42              
43             =item PBS::Logs::Event
44              
45             =item The PBS Pro 5.4 Administrator Guide
46              
47             =back
48              
49             =head1 AUTHOR
50              
51             Dr R K Owen, Erkowen@nersc.govE
52              
53             =head1 COPYRIGHT AND LICENSE
54              
55             Copyright (C) 2005 The Regents of the University of California
56              
57             This library is free software; you can redistribute it
58             and/or modify it under the terms of the GNU Lesser General
59             Public License as published by the Free Software Foundation;
60             either version 2.1 of the License, or (at your option) any
61             later version.
62              
63             This library is distributed in the hope that it will be useful,
64             but WITHOUT ANY WARRANTY; without even the implied warranty
65             of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
66             the GNU Lesser General Public License for more details,
67             which can be found at:
68              
69             http://www.gnu.org/copyleft/lesser.html
70             or http://www.opensource.org/licenses/lgpl-license.php
71              
72             =cut
73              
74 16     16   397057 use 5.006;
  16         60  
  16         640  
75 16     16   93 use strict;
  16         27  
  16         530  
76 16     16   91 use warnings;
  16         47  
  16         489  
77 16     16   100 use Carp;
  16         29  
  16         1578  
78 16     16   9444 use PBS::Logs;
  16         53  
  16         645  
79 16     16   144 use Exporter;
  16         33  
  16         30511  
80              
81             our @ISA = qw(Exporter PBS::Logs);
82              
83             our @EXPORT_OK = qw{message_hash message_hash_dump};
84              
85             our $VERSION = $PBS::Logs::VERSION;
86              
87             our %num2keys = (
88             0 => 'datetime',
89             1 => 'record_type',
90             2 => 'id',
91             3 => 'message'
92             );
93              
94             our %keys;
95             $keys{$num2keys{$_}}=$_ for (keys %num2keys);
96              
97             our %record_type = (
98             'A' => 'job aborted by server',
99             'B' => 'resource reservation period begin',
100             'C' => 'job checkpointed and held',
101             'D' => 'job deleted by request',
102             'E' => 'job ended',
103             'F' => 'resource reservation period finish',
104             'K' => 'removal of resource reservation by sheduler or server',
105             'k' => 'removal of resource reservation by client',
106             'Q' => 'job queued',
107             'R' => 'job rerun',
108             'S' => 'job execution started',
109             'T' => 'job restarted from checkpoint',
110             'U' => 'unconfirmed resource reservation created by server',
111             'Y' => 'confirmed resource reservation created by scheduler',
112             );
113              
114             our %record_message_fields = (
115             'B' => [
116             qw{owner name account queue ctime start end duration nodes
117             authorized_users authorized_groups authorized_hosts resource_list.}],
118             'E' => [
119             qw{user group account jobname queue resvname resvID resvjobID
120             ctime qtime etime start exec_host Resource_List. session
121             alt_id end Exit_status Resources_used.}],
122             'S' => [
123             qw{user group jobname queue
124             ctime qtime etime start exec_host Resource_List. session}],
125             );
126              
127             # Preloaded methods go here.
128              
129             =head1 new
130              
131             Create a PBS::Logs::Acct object.
132             It takes only one argument which is either a filename, array reference,
133             or a FILE glob reference.
134              
135             See PBS::Logs::new for examples and specifics.
136              
137             =cut
138              
139             sub new {
140 16     16 0 10010 my $proto = shift;
141 16   33     134 my $class = ref($proto) || $proto;
142 16         220 my $self = $class->SUPER::new(@_);
143 16 50 33     217 carp __PACKAGE__.": creating $self\n"
144             if ($SUPER::debug || $self->{'-debug'});
145 16         55 $self->{'-records'} = undef;
146 16         38 bless ($self, $class);
147 16         50 return $self;
148             }
149              
150             =head1 get()
151              
152             Get the next accounting log entry. Extends PBS::Logs::get()
153             by filtering based on record_types. See PBS::Logs::Acct::filter_records()
154             below for more info on this filtering, and PBS::Logs::get() for
155             info on return values.
156              
157             =cut
158              
159             sub get {
160 482     482 0 749 my $self = shift;
161 482 50 33     2560 carp __PACKAGE__.": get $self(".join(',',@_).")\n"
162             if ($SUPER::debug || $self->{'-debug'});
163              
164 482         510 my $a;
165 482         1761 while ($a = $self->SUPER::get()) {
166 560 100       1274 if (defined $self->{'-records'}) {
167             last if exists
168 240 100       998 $self->{'-records'}->{$a->[$keys{'record_type'}]};
169             } else {
170 320         377 last;
171             }
172             }
173              
174 482 50       949 return if ! defined wantarray; # just read log entry
175 482 100       1195 return (defined $a ? @$a : ()) if (wantarray); # return array
    100          
176 407         788 $a; # return array ref
177             }
178              
179             =head1 Special Arrays
180              
181             The following special associative arrays (hashes)
182             are provided by this package, which may be useful for
183             translating between arrays returned by the get() method
184             to/from hashes returned by the get_hash() method, or for
185             selecting a subset of the log entry.
186              
187             =head2 %PBS::Logs::Acct::num2keys
188              
189             Relates array position (number) to the keys (or field
190             descriptions) of a get_hash() generated hash.
191              
192             %num2keys = (
193             0 => 'datetime',
194             1 => 'record_type',
195             2 => 'id',
196             3 => 'message'
197             );
198              
199             =head2 %PBS::Logs::Acct::keys
200              
201             Relates keys (field descriptions) as used by the get_hash() method
202             to array positions (number) as returned from the get() method.
203             Essentially, just the inverse of %PBS::Logs::Acct::num2keys above.
204              
205             =head2 %PBS::Logs::Acct::record_type
206              
207             Describes the record types, which are keys to this hash array.
208              
209             %record_type = (
210             'A' => 'job aborted by server',
211             'B' => 'resource reservation period begin',
212             'C' => 'job checkpointed and held',
213             'D' => 'job deleted by request',
214             'E' => 'job ended',
215             'F' => 'resource reservation period finish',
216             'K' => 'removal of resource reservation by sheduler or server',
217             'k' => 'removal of resource reservation by client',
218             'Q' => 'job queued',
219             'R' => 'job rerun',
220             'S' => 'job execution started',
221             'T' => 'job restarted from checkpoint',
222             'U' => 'unconfirmed resource reservation created by server',
223             'Y' => 'confirmed resource reservation created by scheduler',
224             );
225              
226             =head1 get_hash()
227              
228             Like the PBS::Logs::Acct::get() method; however, instead of returning an
229             array reference, it (obviously) returns a hash
230             where the keys are the same keys as given by %PBS::Logs::Acct::keys .
231              
232             The accounting log entry looks like this with respect to the keys:
233              
234             datetime;record_type;id;message
235              
236             where the message field can have several key=value pairs depending on
237             the record_type and all the new-lines have been replaced with spaces.
238              
239             If in a scalar mode it will return a hash reference else it
240             returns a hash.
241              
242             =cut
243              
244             sub get_hash {
245 166     166 0 554 my $self = shift;
246 166 50 33     1965 carp __PACKAGE__.": get_hashref $self(".join(',',@_).")\n"
247             if ($SUPER::debug || $self->{'-debug'});
248              
249 166         594 my $a = $self->get();
250              
251 166 100       327 if (! defined $a) {
252 20 50       37 return () if (wantarray);
253 20         53 return undef;
254             }
255 146         200 my $h = {};
256              
257 146         1310 $h->{$_} = $a->[$keys{$_}] for (keys %keys);
258              
259 146 50       362 return if ! defined wantarray; # just read log line
260 146 0       273 return (defined $h ? %$h : ()) if (wantarray); # return hash
    50          
261 146         411 $h; # return hash ref
262             }
263              
264             =head1 filter_records(\@array_reference_list_of_record_types)
265              
266             =head1 filter_records(@array_list_of_record_types)
267              
268             Sets or reads the record_type filter for the get() method.
269              
270             get() or get_hash() will only retrieve lines that have a record_type
271             in the list given.
272              
273             Sending an empty array reference will clear the record_type filtering.
274              
275             It will return '1' if successful, else undef if some warning occurs.
276              
277             If no arguments are given then the method will return an array
278             of record_types filtered.
279              
280             =cut
281              
282             sub filter_records {
283 39     39 0 2953 my $self = shift;
284 39 50 33     223 carp __PACKAGE__.": filter_records $self(".join(',',@_).")\n"
285             if ($SUPER::debug || $self->{'-debug'});
286              
287 39         50 my @f;
288 39 100       142 if (! defined $_[0]) {
    100          
289 7 100       17 return (sort keys %{$self->{'-records'}})
  4         29  
290             if defined $self->{'-records'};
291 3         8 return ();
292             } elsif (ref $_[0] eq "ARRAY") {
293 6         10 @f = (@{$_[0]});
  6         16  
294             } else {
295 26         67 @f = @_;
296             }
297 32 100       82 if (! scalar @f) {
298 4         11 $self->{'-records'} = undef;
299             } else {
300             # create hash
301 28         69 $self->{'-records'} = {};
302 28         257 $self->{'-records'}->{$_} = 1 for (sort @f);
303             }
304              
305 32         100 1;
306             }
307              
308             =head1 message_hash($message_text)
309              
310             Parses an accounting log message and returns an associative array (hash)
311             containing the key/value pairs. And for certain fields, such as:
312             Resource_List and resources_used, the value is another hash array
313             containing the resource key and value.
314             Can be called either as a method of an instantiated
315             object, or as a class function.
316              
317             An example of the message text with resources_used dotted field:
318              
319             ...
320             resources_used.cpupercent=0 resources_used.cput=00:00:00
321             resources_used.mem=2880kb resources_used.ncpus=4
322             resources_used.vmem=6848kb resources_used.walltime=00:00:00
323             ...
324              
325             Results in a hash array of:
326              
327             ...
328             resources_used => {
329             cpupercent => 0,
330             cput => 00:00:00,
331             mem => 2880kb,
332             ncpus => 4,
333             vmem => 6848kb,
334             walltime => 00:00:00
335             }, ...
336              
337             =cut
338              
339             sub message_hash {
340 3     3 0 11 my $self = shift;
341 3 100       9 if (ref $self eq __PACKAGE__) {
342 1 50 33     7 carp __PACKAGE__.": message_hash $self(".join(',',@_).")\n"
343             if ($SUPER::debug || $self->{'-debug'});
344             } else { # called directly as function
345 2         15 unshift @_, $self;
346 2         3 $self = undef;
347 2 50       7 carp __PACKAGE__.":: message_hash(".join(',',@_).")\n"
348             if ($SUPER::debug);
349             }
350 3         6 my $m = shift;
351              
352 3         5 my @m;
353 3         4 my ($text,$quote) = (undef,undef);
354              
355             # Handle any key=value where the value may be ' or " delimited
356             # (which are the only two delimiters recognized).
357             # Since we are splitting on any whitespace ... this will have
358             # the effect that all whitespace gets replaced by spaces (' ').
359 3         55 foreach (split /\s/, $m) {
360 84 100 100     1349 if (defined $quote && /(.*)$quote$/) {
    100 66        
    50          
    100          
    100          
361             # end of quoted block
362 9         24 push @m, "$text $1";
363 9         11 $text = undef;
364 9         16 $quote = undef;
365             } elsif (defined $quote && /^$/) {
366             # some type of whitespace (replace with space)
367 6         13 $text .= ' ';
368             } elsif (defined $quote) {
369 0         0 $text .= $_;
370             } elsif (/^([^=]+)=(['"])(.*)\2$/) {
371 3         10 push @m, "$1=$3";
372             } elsif (/^([^=]+)=(['"])(.*)$/) {
373 9         23 $text = "$1=$3";
374 9         20 $quote = $2;
375             } else {
376 57         72 push @m, $_;
377 57         58 $text = undef;
378 57         76 $quote = undef;
379             }
380             }
381              
382 3         12 my $h = {};
383              
384 3         7 for (@m) {
385 69         358 my ($k,$v) = m/^([^=]*)=*(.*)$/;
386 69 100       147 if ($k =~ /\./) {
387 30         70 my ($kk,$vv) = split('\.',$k);
388 30 100       81 $h->{$kk} = {} if ! exists $h->{$kk};
389 30         78 $h->{$kk}->{$vv} = $v;
390             } else {
391 39         98 $h->{$k} = $v;
392             }
393             }
394 3         26 return $h;
395             }
396              
397             =head1 message_hash_dump($message_hash)
398              
399             Takes the hash returned by message_hash() and recursively
400             dumps the keys and values into a string suitable for viewing
401             or evaluation. Can be called either as a method of an instantiated
402             object, or as a class function.
403              
404             Example of evaluating the output:
405              
406             my $m = PBS::Logs::Acct::message_hash($some_message);
407             my $t = PBS::Logs::Acct::message_hash_dump($m);
408             my $x;
409             eval "\$x = $t"; # $x is now a HASH reference, equivalent to $m
410              
411             =cut
412              
413             sub message_hash_dump {
414 9     9 0 14 my $self = shift;
415 9 100       24 if (ref $self eq "HASH") { # called directly as function
416 6         11 unshift @_, $self;
417 6         9 $self = undef;
418 6 50       16 carp __PACKAGE__.":: message_hash_dump(".join(',',@_).")\n"
419             if ($SUPER::debug);
420             } else {
421 3 50 33     19 carp __PACKAGE__.": message_hash_dump $self(".join(',',@_).")\n"
422             if ($SUPER::debug || $self->{'-debug'});
423             }
424 9         34 my $h = shift;
425 9         11 my $level = shift;
426 9 100       22 $level = 0 if ! defined $level;
427              
428 9         20 my $text = (" " x $level)."{\n";
429              
430 9         38 foreach my $k (sort {lc($a) cmp lc($b);} keys %$h) {
  171         227  
431 75 100       284 if (ref $h->{$k} eq "HASH") {
432 6         14 $text .= (" " x $level)."'".$k."' => \n";
433 6 100       12 if (defined $self) {
434 2         32 $text .=
435             $self->message_hash_dump($h->{$k}, $level + 1);
436             } else {
437 4         12 $text .=
438             &message_hash_dump($h->{$k}, $level + 1);
439             }
440             } else {
441 69         178 $text .= (" " x $level)."'".$k."' => '".
442             $h->{$k}."',\n";
443             }
444             }
445 9 100       36 $text .= (" " x $level)."}".($level?",":"")."\n";
446 9         37 $text;
447             }
448              
449             1;
450             __END__