File Coverage

blib/lib/Monitoring/Availability.pm
Criterion Covered Total %
statement 550 687 80.0
branch 282 438 64.3
condition 88 134 65.6
subroutine 34 38 89.4
pod 4 4 100.0
total 958 1301 73.6


line stmt bran cond sub pod time code
1             package Monitoring::Availability;
2              
3 17     17   30134 use 5.008;
  17         62  
  17         699  
4 17     17   114 use strict;
  17         32  
  17         588  
5 17     17   103 use warnings;
  17         28  
  17         693  
6 17     17   92 use Data::Dumper;
  17         28  
  17         1372  
7 17     17   96 use Carp;
  17         27  
  17         1165  
8 17     17   9464 use POSIX qw(strftime mktime);
  17         105296  
  17         150  
9 17     17   24832 use Monitoring::Availability::Logs;
  17         48  
  17         1787  
10              
11             our $VERSION = '0.46';
12              
13              
14             =head1 NAME
15              
16             Monitoring::Availability - Calculate Availability Data from
17             Nagios / Icinga and Shinken Logfiles.
18              
19             =head1 SYNOPSIS
20              
21             use Monitoring::Availability;
22             my $ma = Monitoring::Availability->new();
23              
24             =head1 DESCRIPTION
25              
26             This module calculates the availability for hosts/server from given logfiles.
27             The Logfileformat is Nagios/Icinga only.
28              
29             =head1 REPOSITORY
30              
31             Git: http://github.com/sni/Monitoring-Availability
32              
33             =head1 CONSTRUCTOR
34              
35             =head2 new ( [ARGS] )
36              
37             Creates an C object. C takes at least the
38             logs parameter. Arguments are in key-value pairs.
39              
40             =over 4
41              
42             =item rpttimeperiod
43              
44             report timeperiod. defines a timeperiod for this report. Will use 24x7 if not
45             specified.
46              
47             =item assumeinitialstates
48              
49             Assume the initial host/service state if none is found, default: yes
50              
51             =item assumestateretention
52              
53             Assume state retention, default: yes
54              
55             =item assumestatesduringnotrunning
56              
57             Assume state during times when the monitoring process is not running, default: yes
58              
59             =item includesoftstates
60              
61             Include soft states in the calculation. Only hard states are used otherwise, default: no
62              
63             =item initialassumedhoststate
64              
65             Assumed host state if none is found, default: unspecified
66              
67             valid options are: unspecified, current, up, down and unreachable
68              
69             =item initialassumedservicestate
70              
71             Assumed service state if none is found, default: unspecified
72              
73             valid options are: unspecified, current, ok, warning, unknown and critical
74              
75             =item backtrack
76              
77             Go back this amount of days to find initial states, default: 4
78              
79             =item showscheduleddowntime
80              
81             Include downtimes in calculation, default: yes
82              
83             =item timeformat
84              
85             Time format for the log output, default: %s
86              
87             =item verbose
88              
89             verbose mode
90              
91             =item breakdown
92              
93             Breakdown availability into 'months', 'weeks', 'days', 'none'
94              
95             adds additional 'breakdown' hash to each result with broken down results
96              
97             =back
98              
99             =cut
100              
101             use constant {
102 17         216187 TRUE => 1,
103             FALSE => 0,
104              
105             STATE_NOT_RUNNING => -3,
106             STATE_UNSPECIFIED => -2,
107             STATE_CURRENT => -1,
108              
109             STATE_UP => 0,
110             STATE_DOWN => 1,
111             STATE_UNREACHABLE => 2,
112              
113             STATE_OK => 0,
114             STATE_WARNING => 1,
115             STATE_CRITICAL => 2,
116             STATE_UNKNOWN => 3,
117              
118             START_NORMAL => 1,
119             START_RESTART => 2,
120             STOP_NORMAL => 0,
121             STOP_ERROR => -1,
122              
123             HOST_ONLY => 2,
124             SERVICE_ONLY => 3,
125              
126             BREAK_NONE => 0,
127             BREAK_DAYS => 1,
128             BREAK_WEEKS => 2,
129             BREAK_MONTHS => 3,
130 17     17   118 };
  17         26  
131              
132             my $verbose = 0;
133             my $report_options_start;
134             my $report_options_end;
135             my $report_options_includesoftstates;
136             my $report_options_calc_all;
137              
138             sub new {
139 18     18 1 22305 my $class = shift;
140 18         192 my(%options) = @_;
141              
142 18         224 my $self = {
143             'verbose' => 0, # enable verbose output
144             'logger' => undef, # logger object used for verbose output
145             'timeformat' => undef,
146             'rpttimeperiod' => undef,
147             'assumeinitialstates' => undef,
148             'assumestateretention' => undef,
149             'assumestatesduringnotrunning' => undef,
150             'includesoftstates' => undef,
151             'initialassumedhoststate' => undef,
152             'initialassumedservicestate' => undef,
153             'backtrack' => undef,
154             'showscheduleddowntime' => undef,
155             'breakdown' => undef,
156             };
157              
158 18         106 bless $self, $class;
159              
160             # verify the options we got so far
161 18         77 $self = $self->_verify_options($self);
162              
163 18         94 for my $opt_key (keys %options) {
164 124 50       269 if(exists $self->{$opt_key}) {
165 124         232 $self->{$opt_key} = $options{$opt_key};
166             }
167             else {
168 0         0 croak("unknown option: $opt_key");
169             }
170             }
171              
172             # translation hash
173 18         234 $self->{'state_string_2_int'} = {
174             'ok' => STATE_OK,
175             'warning' => STATE_WARNING,
176             'unknown' => STATE_UNKNOWN,
177             'critical' => STATE_CRITICAL,
178             'up' => STATE_UP,
179             'down' => STATE_DOWN,
180             'unreachable' => STATE_UNREACHABLE,
181             '0' => STATE_OK,
182             '1' => STATE_WARNING,
183             '2' => STATE_CRITICAL,
184             '3' => STATE_UNKNOWN,
185             };
186              
187             # allow setting debug mode from env
188 18 50       96 if(defined $ENV{'MONITORING_AVAILABILITY_DEBUG'}) {
189 0         0 $self->{'verbose'} = 1;
190             }
191              
192             # init log4perl, may require additional modules
193 18 50 33     109 if($self->{'verbose'} and !defined $self->{'logger'}) {
194 0         0 require Log::Log4perl;
195 0         0 Log::Log4perl->import(qw(:easy));
196 0         0 Log::Log4perl->easy_init({
197             level => 'DEBUG',
198             file => ">/tmp/Monitoring-Availability-Debug.log"
199             });
200 0         0 $self->{'logger'} = get_logger();
201             }
202              
203 18 50       99 $self->_log('initialized '.$class) if $self->{'verbose'};
204 18 50       64 $self->_log($self) if $self->{'verbose'};
205              
206 18         41 $verbose = $self->{'verbose'};
207              
208 18         83 return $self;
209             }
210              
211              
212             ########################################
213              
214             =head1 METHODS
215              
216             =head2 calculate
217              
218             calculate()
219              
220             Calculate the availability
221              
222             =over 4
223              
224             =item start
225              
226             Timestamp of start
227              
228             =item end
229              
230             Timestamp of end
231              
232             =item log_string
233              
234             String containing the logs
235              
236             =item log_file
237              
238             File containing the logs
239              
240             =item log_dir
241              
242             Directory containing *.log files
243              
244             =item log_livestatus
245              
246             Array with logs from a livestatus query
247              
248             a sample query could be:
249             selectall_arrayref(GET logs...\nColumns: time type options, {Slice => 1})
250              
251             =item log_iterator
252              
253             Iterator object for logentry objects. For example a L object.
254              
255             =item hosts
256              
257             array with hostnames for which the report should be generated
258              
259             =item services
260              
261             array with hashes of services for which the report should be generated.
262             The array should look like this:
263              
264             [{host => 'hostname', service => 'description'}, ...]
265              
266             =item initial_states
267              
268             if you use the "current" option for initialassumedservicestate or initialassumedhoststate you
269             have to provide the current states with a hash like this:
270              
271             {
272             hosts => {
273             'hostname' => 'ok',
274             ...
275             },
276             services => {
277             'hostname' => {
278             'description' => 'warning',
279             ...
280             }
281             }
282             }
283              
284             valid values for hosts are: up, down and unreachable
285              
286             valid values for services are: ok, warning, unknown and critical
287              
288             =back
289              
290             =cut
291              
292             sub calculate {
293 20     20 1 39169 my $self = shift;
294 20         125 my(%opts) = @_;
295              
296             # clean up namespace
297 20         83 $self->_reset();
298              
299 20   100     491 $self->{'report_options'} = {
300             'start' => undef,
301             'end' => undef,
302             'hosts' => [],
303             'services' => [],
304             'initial_states' => {},
305             'log_string' => undef, # logs from string
306             'log_livestatus' => undef, # logs from a livestatus query
307             'log_file' => undef, # logs from a file
308             'log_dir' => undef, # logs from a dir
309             'log_iterator' => undef, # logs from a iterator object
310             'rpttimeperiod' => $self->{'rpttimeperiod'} || '',
311             'assumeinitialstates' => $self->{'assumeinitialstates'},
312             'assumestateretention' => $self->{'assumestateretention'},
313             'assumestatesduringnotrunning' => $self->{'assumestatesduringnotrunning'},
314             'includesoftstates' => $self->{'includesoftstates'},
315             'initialassumedhoststate' => $self->{'initialassumedhoststate'},
316             'initialassumedservicestate' => $self->{'initialassumedservicestate'},
317             'backtrack' => $self->{'backtrack'},
318             'showscheduleddowntime' => $self->{'showscheduleddowntime'},
319             'timeformat' => $self->{'timeformat'},
320             'breakdown' => $self->{'breakdown'},
321             };
322 20 50       94 $self->_log('calculate()') if $self->{'verbose'};
323 20 50       76 $self->_log($self->{'report_options'}) if $self->{'verbose'};
324 20         32 my $result;
325              
326 20         80 for my $opt_key (keys %opts) {
327 86 50       209 if(exists $self->{'report_options'}->{$opt_key}) {
328 86         191 $self->{'report_options'}->{$opt_key} = $opts{$opt_key};
329             }
330             else {
331 0         0 croak("unknown option: $opt_key");
332             }
333             }
334 20         56 $verbose = $self->{'verbose'};
335              
336 20         104 $self->{'report_options'} = $self->_set_default_options($self->{'report_options'});
337 20         73 $self->{'report_options'} = $self->_verify_options($self->{'report_options'});
338              
339             # create lookup hash for faster access
340 20         71 $result->{'hosts'} = {};
341 20         46 $result->{'services'} = {};
342 20         43 for my $host (@{$self->{'report_options'}->{'hosts'}}) {
  20         75  
343 8         36 $result->{'hosts'}->{$host} = 1;
344             }
345 20         180 for my $service (@{$self->{'report_options'}->{'services'}}) {
  20         72  
346 32 50       121 if(ref $service ne 'HASH') {
347 0         0 croak("services have to be an array of hashes, for example: [{host => 'hostname', service => 'description'}, ...]\ngot: ".Dumper($service));
348             }
349 32 50 33     150 if(!defined $service->{'host'} or !defined $service->{'service'}) {
350 0         0 croak("services have to be an array of hashes, for example: [{host => 'hostname', service => 'description'}, ...]\ngot: ".Dumper($service));
351             }
352 32         100 $result->{'services'}->{$service->{'host'}}->{$service->{'service'}} = 1;
353             }
354              
355             # if we have more than one host or service, we dont build up a log
356 20 100       58 if(scalar @{$self->{'report_options'}->{'hosts'}} == 1) {
  20 50       92  
  12         66  
357 8         27 $self->{'report_options'}->{'build_log'} = HOST_ONLY;
358             }
359             elsif(scalar @{$self->{'report_options'}->{'services'}} == 1) {
360 12         39 $self->{'report_options'}->{'build_log'} = SERVICE_ONLY;
361             }
362             else {
363 0         0 $self->{'report_options'}->{'build_log'} = FALSE;
364             }
365              
366 20         51 $self->{'report_options'}->{'calc_all'} = FALSE;
367 20 50 66     34 if(scalar keys %{$result->{'services'}} == 0 and scalar keys %{$result->{'hosts'}} == 0) {
  20         239  
  7         43  
368 0 0       0 $self->_log('will calculate availability for all hosts/services found') if $self->{'verbose'};
369 0         0 $self->{'report_options'}->{'calc_all'} = TRUE;
370             }
371              
372 20         78 $self->_set_breakpoints();
373              
374 20 50       102 unless($self->{'report_options'}->{'calc_all'}) {
375 20         120 $self->_set_empty_hosts($result);
376 20         71 $self->_set_empty_services($result);
377             }
378              
379             # set some variables for faster access
380 20         47 $report_options_start = $self->{'report_options'}->{'start'};
381 20         44 $report_options_end = $self->{'report_options'}->{'end'};
382 20         46 $report_options_includesoftstates = $self->{'report_options'}->{'includesoftstates'};
383 20         43 $report_options_calc_all = $self->{'report_options'}->{'calc_all'};
384              
385             # read in logs
386 20 50 33     352 if($self->{'report_options'}->{'log_file'} and !$self->{'report_options'}->{'log_string'} and !$self->{'report_options'}->{'log_dir'}) {
    100 33        
    50 66        
    0 66        
387             # single file can be read line by line to save memory
388 0         0 $self->_compute_availability_line_by_line($result, $self->{'report_options'}->{'log_file'});
389             }
390             elsif(defined $self->{'report_options'}->{'log_string'} or $self->{'report_options'}->{'log_file'} or $self->{'report_options'}->{'log_dir'}) {
391 19         177 my $mal = Monitoring::Availability::Logs->new(
392             'log_string' => $self->{'report_options'}->{'log_string'},
393             'log_file' => $self->{'report_options'}->{'log_file'},
394             'log_dir' => $self->{'report_options'}->{'log_dir'},
395             );
396 19         98 my $logs = $mal->get_logs();
397 19         84 $self->_compute_availability_from_log_store($result, $logs);
398             }
399             elsif(defined $self->{'report_options'}->{'log_livestatus'}) {
400 1         3 $self->_compute_availability_on_the_fly($result, $self->{'report_options'}->{'log_livestatus'});
401             }
402             elsif(defined $self->{'report_options'}->{'log_iterator'}) {
403 0         0 $self->_compute_availability_from_iterator($result, $self->{'report_options'}->{'log_iterator'});
404             }
405 20         174 return($result);
406             }
407              
408              
409             ########################################
410              
411             =head2 get_condensed_logs
412              
413             get_condensed_logs()
414              
415             returns an array of hashes with the condensed log used for this report
416              
417             =cut
418              
419             sub get_condensed_logs {
420 8     8 1 18219 my $self = shift;
421              
422 8 50       72 return if $self->{'report_options'}->{'build_log'} == FALSE;
423              
424 8 50       54 $self->_calculate_log() unless $self->{'log_output_calculated'};
425              
426 8         29 return $self->{'log_output'};
427             }
428              
429              
430             ########################################
431              
432             =head2 get_full_logs
433              
434             get_full_logs()
435              
436             returns an array of hashes with the full log used for this report
437              
438             =cut
439              
440             sub get_full_logs {
441 9     9 1 32606 my $self = shift;
442              
443 9 50       117 return if $self->{'report_options'}->{'build_log'} == FALSE;
444              
445 9 100       191 $self->_calculate_log() unless $self->{'log_output_calculated'};
446              
447 9         155 return $self->{'full_log_output'};
448             }
449              
450              
451             ########################################
452             # INTERNAL SUBS
453             ########################################
454             sub _reset {
455 20     20   38 my $self = shift;
456 20 50       94 $self->_log('_reset()') if $self->{'verbose'};
457              
458 20         53 undef $self->{'full_log_store'};
459 20         67 $self->{'full_log_store'} = [];
460              
461 20         55 delete $self->{'first_known_state_before_report'};
462 20         44 delete $self->{'first_known_proc_before_report'};
463 20         40 delete $self->{'log_output_calculated'};
464              
465 20         58 delete $self->{'report_options'};
466 20         34 delete $self->{'full_log_output'};
467 20         47 delete $self->{'log_output'};
468              
469 20         46 return 1;
470             }
471              
472             ########################################
473             sub _set_empty_hosts {
474 20     20   40 my $self = shift;
475 20         40 my $data = shift;
476              
477 20         73 my $initial_assumend_state = STATE_UNSPECIFIED;
478 20 50       85 if($self->{'report_options'}->{'assumeinitialstates'}) {
479 20         75 $initial_assumend_state = $self->{'report_options'}->{'initialassumedhoststate'};
480             }
481              
482 20 50       77 $self->_log('_set_empty_hosts()') if $self->{'verbose'};
483 20         39 for my $hostname (keys %{$data->{'hosts'}}) {
  20         71  
484 8         15 my $first_state = $initial_assumend_state;
485 8 50       42 if($initial_assumend_state == STATE_CURRENT) {
486 0         0 eval { $first_state = $self->_state_to_int($self->{'report_options'}->{'initial_states'}->{'hosts'}->{$hostname}); };
  0         0  
487 0 0       0 if($@) { croak("found no initial state for host '$hostname'\ngot: ".Dumper($self->{'report_options'}->{'initial_states'}).Dumper($@)); }
  0         0  
488 0         0 $self->{'report_options'}->{'first_state'} = $first_state;
489             }
490 8         43 $data->{'hosts'}->{$hostname} = $self->_new_host_data($self->{'report_options'}->{'breakdown'});
491 8         60 $self->{'host_data'}->{$hostname} = {
492             'in_downtime' => 0,
493             'last_state' => $initial_assumend_state,
494             'last_known_state' => undef,
495             'last_state_time' => 0,
496             };
497             }
498 20         63 return 1;
499             }
500              
501             ########################################
502             sub _set_empty_services {
503 20     20   54 my $self = shift;
504 20         37 my $data = shift;
505 20 50       83 $self->_log('_set_empty_services()') if $self->{'verbose'};
506              
507 20         36 my $initial_assumend_state = STATE_UNSPECIFIED;
508 20         37 my $initial_assumend_host_state = STATE_UNSPECIFIED;
509 20 50       86 if($self->{'report_options'}->{'assumeinitialstates'}) {
510 20         85 $initial_assumend_state = $self->{'report_options'}->{'initialassumedservicestate'};
511 20         59 $initial_assumend_host_state = $self->{'report_options'}->{'initialassumedhoststate'};
512             }
513              
514 20         62 for my $hostname (keys %{$data->{'services'}}) {
  20         71  
515 13         23 for my $service_description (keys %{$data->{'services'}->{$hostname}}) {
  13         59  
516 32         43 my $first_state = $initial_assumend_state;
517 32 100       85 if($initial_assumend_state == STATE_CURRENT) {
518 1         2 eval { $first_state = $self->_state_to_int($self->{'report_options'}->{'initial_states'}->{'services'}->{$hostname}->{$service_description}); };
  1         7  
519 1 50       4 if($@) { croak("found no initial state for service '$service_description' on host '$hostname'\ngot: ".Dumper($self->{'report_options'}->{'initial_states'}).Dumper($@)); }
  0         0  
520 1         3 $self->{'report_options'}->{'first_state'} = $first_state;
521             }
522 32         115 $data->{'services'}->{$hostname}->{$service_description} = $self->_new_service_data($self->{'report_options'}->{'breakdown'});
523              
524             # create last service data
525 32         205 $self->{'service_data'}->{$hostname}->{$service_description} = {
526             'in_downtime' => 0,
527             'last_state' => $first_state,
528             'last_known_state' => undef,
529             'last_state_time' => 0,
530             };
531             }
532 13         30 my $first_host_state = $initial_assumend_host_state;
533 13 50       53 if($initial_assumend_host_state == STATE_CURRENT) {
534 0         0 eval { $first_host_state = $self->_state_to_int($self->{'report_options'}->{'initial_states'}->{'hosts'}->{$hostname}); };
  0         0  
535 0 0       0 if($@) { $first_host_state = STATE_UNSPECIFIED; }
  0         0  
536             }
537 13         116 $self->{'host_data'}->{$hostname} = {
538             'in_downtime' => 0,
539             'last_state' => $first_host_state,
540             'last_known_state' => undef,
541             'last_state_time' => 0,
542             };
543             }
544 20         58 return 1;
545             }
546              
547             ########################################
548             sub _compute_for_data {
549 176     176   289 my($self, $last_time, $data, $result) = @_;
550              
551             # if we reach the start date of our report, insert a fake entry
552 176 100 100     833 if($last_time < $report_options_start and $data->{'time'} >= $report_options_start) {
553 20         93 $self->_insert_fake_event($result, $report_options_start);
554             }
555              
556             # if we passed a breakdown point, insert fake event
557 176 100       465 if($self->{'report_options'}->{'breakdown'} != BREAK_NONE) {
558 6         9 my $breakpoint = $self->{'breakpoints'}->[0];
559 6   66     37 while(defined $breakpoint and $last_time < $breakpoint and $data->{'time'} >= $breakpoint) {
      100        
560 4 50       9 $self->_log('_compute_for_data(): inserted breakpoint: '.$breakpoint." (".scalar localtime($breakpoint).")") if $self->{'verbose'};
561 4         9 $self->_insert_fake_event($result, $breakpoint);
562 4         5 shift(@{$self->{'breakpoints'}});
  4         4  
563 4         26 $breakpoint = $self->{'breakpoints'}->[0];
564             }
565             }
566              
567             # end of report reached, insert fake end event
568 176 100 66     460 if($data->{'time'} >= $report_options_end and $last_time < $report_options_end) {
569 3         8 $self->_insert_fake_event($result, $report_options_end);
570              
571             # set a log entry
572 3 100       17 $self->_add_log_entry(
573             'full_only' => 1,
574             'log' => {
575             'start' => $report_options_end,
576             },
577             ) unless defined $data->{'fake'};
578             }
579              
580             # now process the real line
581 176 100       570 &_process_log_line($self,$result, $data) unless defined $data->{'fake'};
582              
583 176         279 return 1;
584             }
585              
586             ########################################
587             sub _compute_availability_line_by_line {
588 0     0   0 my($self,$result,$file) = @_;
589              
590 0 0       0 if($self->{'verbose'}) {
591 0         0 $self->_log('_compute_availability_line_by_line()');
592 0         0 $self->_log('_compute_availability_line_by_line() report start: '.(scalar localtime $self->{'report_options'}->{'start'}));
593 0         0 $self->_log('_compute_availability_line_by_line() report end: '.(scalar localtime $self->{'report_options'}->{'end'}));
594             }
595              
596 0         0 my $last_time = -1;
597              
598 0 0       0 open(my $fh, '<', $file) or die("cannot read ".$file.": ".$!);
599 0         0 binmode($fh);
600              
601             # process all log lines we got
602             # logs should be sorted already
603 0         0 while(<$fh>) {
604 0         0 chop;
605 0         0 my $data = &Monitoring::Availability::Logs::parse_line($_);
606 0 0       0 next unless $data;
607 0         0 &_compute_for_data($self,$last_time, $data, $result);
608             # set timestamp of last log line
609 0         0 $last_time = $data->{'time'};
610             }
611 0         0 close($fh);
612              
613 0         0 $self->_add_last_time_event($last_time, $result);
614              
615 0         0 return 1;
616             }
617              
618              
619             ########################################
620             sub _compute_availability_from_iterator {
621 0     0   0 my $self = shift;
622 0         0 my $result = shift;
623 0         0 my $logs = shift;
624              
625 0 0       0 if($self->{'verbose'}) {
626 0         0 $self->_log('_compute_availability_from_iterator()');
627 0         0 $self->_log('_compute_availability_from_iterator() report start: '.(scalar localtime $self->{'report_options'}->{'start'}));
628 0         0 $self->_log('_compute_availability_from_iterator() report end: '.(scalar localtime $self->{'report_options'}->{'end'}));
629             }
630              
631 0         0 my $last_time = -1;
632             # no logs at all?
633 0 0       0 unless($logs->has_next) {
634 0         0 $self->_compute_for_data(-1,
635             {time => $self->{'report_options'}->{'end'}, fake => 1},
636             $result);
637 0         0 $last_time = $self->{'report_options'}->{'end'};
638             }
639              
640             # process all log lines we got
641             # logs should be sorted already
642 0         0 while(my $data = $logs->next) {
643 0         0 $self->_compute_for_data($last_time,
644             Monitoring::Availability::Logs->_parse_livestatus_entry($data),
645             $result);
646              
647             # set timestamp of last log line
648 0         0 $last_time = $data->{'time'};
649             }
650              
651             # processing logfiles finished
652              
653 0         0 $self->_add_last_time_event($last_time, $result);
654              
655 0         0 return 1;
656             }
657              
658              
659             ########################################
660             sub _compute_availability_on_the_fly {
661 1     1   2 my $self = shift;
662 1         2 my $result = shift;
663 1         1 my $logs = shift;
664              
665 1 50       4 if($self->{'verbose'}) {
666 0         0 $self->_log('_compute_availability_on_the_fly()');
667 0         0 $self->_log('_compute_availability_on_the_fly() report start: '.(scalar localtime $self->{'report_options'}->{'start'}));
668 0         0 $self->_log('_compute_availability_on_the_fly() report end: '.(scalar localtime $self->{'report_options'}->{'end'}));
669             }
670              
671 1         2 my $last_time = -1;
672 1 50       2 if(scalar @{$logs} == 0) {
  1         3  
673 1         10 $self->_compute_for_data(-1,
674             {time => $self->{'report_options'}->{'end'}, fake => 1},
675             $result);
676 1         3 $last_time = $self->{'report_options'}->{'end'};
677             }
678              
679             # process all log lines we got
680             # make sure our logs are sorted by time
681 1         2 for my $data ( sort { $a->{'time'} <=> $b->{'time'} } @{$logs} ) {
  0         0  
  1         4  
682 0         0 eval {
683 0         0 $self->_compute_for_data($last_time,
684             Monitoring::Availability::Logs->_parse_livestatus_entry($data),
685             $result);
686              
687             # set timestamp of last log line
688 0         0 $last_time = $data->{'time'};
689             }
690             }
691              
692             # processing logfiles finished
693              
694 1         10 $self->_add_last_time_event($last_time, $result);
695              
696 1         2 return 1;
697             }
698              
699              
700             ########################################
701             sub _compute_availability_from_log_store {
702 19     19   35 my $self = shift;
703 19         31 my $result = shift;
704 19         40 my $logs = shift;
705              
706 19 50       90 if($self->{'verbose'}) {
707 0         0 $self->_log('_compute_availability_from_log_store()');
708 0         0 $self->_log('_compute_availability_from_log_store() report start: '.(scalar localtime $self->{'report_options'}->{'start'}));
709 0         0 $self->_log('_compute_availability_from_log_store() report end: '.(scalar localtime $self->{'report_options'}->{'end'}));
710             }
711              
712             # make sure our logs are sorted by time
713 19         39 @{$logs} = sort { $a->{'time'} <=> $b->{'time'} } @{$logs};
  19         80  
  215         459  
  19         230  
714              
715 19 50       82 $self->_log('_compute_availability_from_log_store() sorted logs') if $self->{'verbose'};
716              
717             # process all log lines we got
718 19         38 my $last_time = -1;
719 19         58 for my $data (@{$logs}) {
  19         72  
720              
721 175         411 $self->_compute_for_data($last_time, $data, $result);
722              
723             # set timestamp of last log line
724 175         471 $last_time = $data->{'time'};
725             }
726              
727             # processing logfiles finished
728              
729 19         82 $self->_add_last_time_event($last_time, $result);
730              
731 19         443 return 1;
732             }
733              
734              
735             ########################################
736             sub _add_last_time_event {
737 20     20   56 my($self, $last_time, $result) = @_;
738              
739             # no start event yet, insert a fake entry
740 20 50       103 if($last_time < $self->{'report_options'}->{'start'}) {
741 0         0 $self->_insert_fake_event($result, $self->{'report_options'}->{'start'});
742             }
743              
744             # breakpoints left?
745 20         63 my $breakpoint = $self->{'breakpoints'}->[0];
746 20         76 while(defined $breakpoint) {
747 1 50       4 $self->_log('_add_last_time_event(): inserted breakpoint: '.$breakpoint." (".scalar localtime($breakpoint).")") if $self->{'verbose'};
748 1         3 $self->_insert_fake_event($result, $breakpoint);
749 1         1 shift(@{$self->{'breakpoints'}});
  1         3  
750 1         3 $breakpoint = $self->{'breakpoints'}->[0];
751             }
752              
753              
754             # no end event yet, insert fake end event
755 20 100       77 if($last_time < $self->{'report_options'}->{'end'}) {
756 17         83 $self->_insert_fake_event($result, $self->{'report_options'}->{'end'});
757             }
758              
759 20         53 return 1;
760             }
761              
762             ########################################
763             sub _process_log_line {
764 175     175   222 my($self, $result, $data) = @_;
765              
766 175 50       350 if($verbose) {
767 0         0 $self->_log('#######################################');
768 0         0 $self->_log('_process_log_line() at '.(scalar localtime $data->{'time'}));
769 0         0 $self->_log($data);
770             }
771              
772             # only hard states?
773 175 100 66     901 if(exists $data->{'hard'} and !$report_options_includesoftstates and $data->{'hard'} != 1) {
      100        
774 2 50       3 $self->_log(' -> skipped soft state') if $verbose;
775 2         4 return;
776             }
777              
778             # process starts / stops?
779 173 100       518 if(defined $data->{'proc_start'}) {
    100          
780 57 100       196 unless($self->{'report_options'}->{'assumestatesduringnotrunning'}) {
781 12 100 66     63 if($data->{'proc_start'} == START_NORMAL or $data->{'proc_start'} == START_RESTART) {
782             # set an event for all services and set state to no_data
783 10 50       33 $self->_log('_process_log_line() process start, inserting fake event for all services') if $verbose;
784 10         70 for my $host_name (keys %{$self->{'service_data'}}) {
  10         44  
785 7         14 for my $service_description (keys %{$self->{'service_data'}->{$host_name}}) {
  7         24  
786 6         19 my $last_known_state = $self->{'service_data'}->{$host_name}->{$service_description}->{'last_known_state'};
787 6         10 my $last_state = STATE_UNSPECIFIED;
788 6 100 66     34 $last_state = $last_known_state if(defined $last_known_state and $last_known_state >= 0);
789 6         43 &_set_service_event($self, $host_name, $service_description, $result, { 'start' => $data->{'start'}, 'end' => $data->{'end'}, 'time' => $data->{'time'}, 'state' => $last_state });
790             }
791             }
792 10         26 for my $host_name (keys %{$self->{'host_data'}}) {
  10         37  
793 10         32 my $last_known_state = $self->{'host_data'}->{$host_name}->{'last_known_state'};
794 10         23 my $last_state = STATE_UNSPECIFIED;
795 10 100 66     94 $last_state = $last_known_state if(defined $last_known_state and $last_known_state >= 0);
796 10         72 &_set_host_event($self, $host_name, $result, { 'time' => $data->{'time'}, 'state' => $last_state });
797             }
798             } else {
799             # set an event for all services and set state to not running
800 2 50       8 $self->_log('_process_log_line() process stop, inserting fake event for all services') if $verbose;
801 2         5 for my $host_name (keys %{$self->{'service_data'}}) {
  2         5  
802 2         6 for my $service_description (keys %{$self->{'service_data'}->{$host_name}}) {
  2         7  
803 1         6 &_set_service_event($self, $host_name, $service_description, $result, { 'time' => $data->{'time'}, 'state' => STATE_NOT_RUNNING });
804             }
805             }
806 2         4 for my $host_name (keys %{$self->{'host_data'}}) {
  2         7  
807 2         11 &_set_host_event($self, $host_name, $result, { 'time' => $data->{'time'}, 'state' => STATE_NOT_RUNNING });
808             }
809             }
810             }
811             # set a log entry
812 57 100 100     261 if($data->{'proc_start'} == START_NORMAL or $data->{'proc_start'} == START_RESTART) {
813 39         71 my $plugin_output = 'Program start';
814 39 100       121 $plugin_output = 'Program restart' if $data->{'proc_start'} == START_RESTART;
815 39         247 $self->_add_log_entry(
816             'full_only' => 1,
817             'proc_start' => $data->{'proc_start'},
818             'log' => {
819             'start' => $data->{'time'},
820             'type' => 'PROGRAM (RE)START',
821             'plugin_output' => $plugin_output,
822             'class' => 'INDETERMINATE',
823             },
824             );
825             } else {
826 18         28 my $plugin_output = 'Normal program termination';
827 18 50       52 $plugin_output = 'Abnormal program termination' if $data->{'proc_start'} == STOP_ERROR;
828 18         107 $self->_add_log_entry(
829             'full_only' => 1,
830             'log' => {
831             'start' => $data->{'time'},
832             'type' => 'PROGRAM END',
833             'plugin_output' => $plugin_output,
834             'class' => 'INDETERMINATE',
835             },
836             );
837             }
838 57         102 return;
839             }
840              
841             # timeperiod transitions
842             elsif(defined $data->{'timeperiod'}) {
843 6 100       20 if($self->{'report_options'}->{'rpttimeperiod'} eq $data->{'timeperiod'} ) {
844 3 50       8 $self->_log('_process_log_line() timeperiod translation, inserting fake event for all hosts/services') if $verbose;
845 3         3 for my $host_name (keys %{$self->{'service_data'}}) {
  3         10  
846 3         4 for my $service_description (keys %{$self->{'service_data'}->{$host_name}}) {
  3         7  
847 3         6 my $last_known_state = $self->{'service_data'}->{$host_name}->{$service_description}->{'last_known_state'};
848 3         4 my $last_state = STATE_UNSPECIFIED;
849 3 50 33     18 $last_state = $last_known_state if(defined $last_known_state and $last_known_state >= 0);
850 3         15 &_set_service_event($self, $host_name, $service_description, $result, { 'start' => $data->{'start'}, 'end' => $data->{'end'}, 'time' => $data->{'time'}, 'state' => $last_state });
851             }
852             }
853 3         6 for my $host_name (keys %{$self->{'host_data'}}) {
  3         8  
854 3         5 my $last_known_state = $self->{'host_data'}->{$host_name}->{'last_known_state'};
855 3         5 my $last_state = STATE_UNSPECIFIED;
856 3 50 33     9 $last_state = $last_known_state if(defined $last_known_state and $last_known_state >= 0);
857 3         14 &_set_host_event($self,$host_name, $result, { 'time' => $data->{'time'}, 'state' => $last_state });
858             }
859 3         46 $self->{'in_timeperiod'} = $data->{'to'};
860              
861             # set a log entry
862 3         6 my $start = 'STOP';
863 3         7 my $plugin_output = 'leaving timeperiod: '.$data->{'timeperiod'};
864 3 100       10 if($self->{'in_timeperiod'}) {
865 2         4 $plugin_output = 'entering timeperiod: '.$data->{'timeperiod'};
866 2         10 $start = 'START';
867             }
868             $self->_add_log_entry(
869 3         25 'full_only' => 1,
870             'log' => {
871             'start' => $data->{'time'},
872             'type' => 'TIMEPERIOD '.$start,
873             'plugin_output' => $plugin_output,
874             'class' => 'INDETERMINATE',
875             },
876             );
877             }
878 6         11 return;
879             }
880              
881             # skip hosts we dont need
882 110 100 66     737 if($report_options_calc_all == 0 and defined $data->{'host_name'} and !defined $self->{'host_data'}->{$data->{'host_name'}} and !defined $self->{'service_data'}->{$data->{'host_name'}}) {
      100        
      66        
883 2 50       11 $self->_log(' -> skipped not needed host event') if $verbose;
884 2         4 return;
885             }
886              
887             # skip services we dont need
888 108 50 66     841 if($report_options_calc_all == 0
      100        
      66        
889             and $data->{'host_name'}
890             and $data->{'service_description'}
891             and !defined $self->{'service_data'}->{$data->{'host_name'}}->{$data->{'service_description'}}
892             ) {
893 0 0       0 $self->_log(' -> skipped not needed service event') if $verbose;
894 0         0 return;
895             }
896              
897             # service events
898 108 100       344 if($data->{'service_description'}) {
    100          
899 47         125 my $service_hist = $self->{'service_data'}->{$data->{'host_name'}}->{$data->{'service_description'}};
900              
901 47 100 100     247 if($data->{'type'} eq 'CURRENT SERVICE STATE' or $data->{'type'} eq 'SERVICE ALERT' or $data->{'type'} eq 'INITIAL SERVICE STATE') {
    50 66        
902 45         130 &_set_service_event($self,$data->{'host_name'}, $data->{'service_description'}, $result, $data);
903              
904             # set a log entry
905 45         51 my $state_text;
906 45 100       183 if( $data->{'state'} == STATE_OK ) { $state_text = "OK"; }
  35 100       48  
    100          
    50          
907 3         5 elsif($data->{'state'} == STATE_WARNING) { $state_text = "WARNING"; }
908 3         6 elsif($data->{'state'} == STATE_UNKNOWN) { $state_text = "UNKNOWN"; }
909 4         10 elsif($data->{'state'} == STATE_CRITICAL) { $state_text = "CRITICAL"; }
910 45 50       115 if(defined $state_text) {
911 45         70 my $hard = "";
912 45 50       127 $hard = " (HARD)" if $data->{'hard'};
913 45 100       312 $self->_add_log_entry(
914             'log' => {
915             'start' => $data->{'time'},
916             'type' => 'SERVICE '.$state_text.$hard,
917             'plugin_output' => $data->{'plugin_output'},
918             'class' => $state_text,
919             'in_downtime' => $service_hist->{'in_downtime'},
920             },
921             ) unless $self->{'report_options'}->{'build_log'} == HOST_ONLY;
922             }
923             }
924             elsif($data->{'type'} eq 'SERVICE DOWNTIME ALERT') {
925 2 50       6 next unless $self->{'report_options'}->{'showscheduleddowntime'};
926              
927 2         5 undef $data->{'state'}; # we dont know the current state, so make sure it wont be overwritten
928 2         6 &_set_service_event($self,$data->{'host_name'}, $data->{'service_description'}, $result, $data);
929              
930 2         2 my $start;
931             my $plugin_output;
932 2 100       5 if($data->{'start'}) {
933 1         2 $start = "START";
934 1         2 $plugin_output = 'Start of scheduled downtime';
935 1         2 $service_hist->{'in_downtime'} = 1;
936             }
937             else {
938 1         2 $start = "END";
939 1         8 $plugin_output = 'End of scheduled downtime';
940 1         3 $service_hist->{'in_downtime'} = 0;
941             }
942              
943             # set a log entry
944 2 50       19 $self->_add_log_entry(
945             'log' => {
946             'start' => $data->{'time'},
947             'type' => 'SERVICE DOWNTIME '.$start,
948             'plugin_output' => $plugin_output,
949             'class' => 'INDETERMINATE',
950             'in_downtime' => $service_hist->{'in_downtime'},
951             },
952             ) unless $self->{'report_options'}->{'build_log'} == HOST_ONLY;
953             }
954             else {
955 0 0       0 $self->_log(' -> unknown log type') if $verbose;
956             }
957             }
958              
959             # host events
960             elsif(defined $data->{'host_name'}) {
961 16         45 my $host_hist = $self->{'host_data'}->{$data->{'host_name'}};
962              
963 16 100 100     129 if($data->{'type'} eq 'CURRENT HOST STATE' or $data->{'type'} eq 'HOST ALERT' or $data->{'type'} eq 'INITIAL HOST STATE') {
    50 66        
964 11         29 &_set_host_event($self,$data->{'host_name'}, $result, $data);
965              
966             # set a log entry
967 11         12 my $state_text;
968 11 100       38 if( $data->{'state'} == STATE_UP) { $state_text = "UP"; }
  7 100       18  
    50          
969 3         5 elsif($data->{'state'} == STATE_DOWN) { $state_text = "DOWN"; }
970 1         1 elsif($data->{'state'} == STATE_UNREACHABLE) { $state_text = "UNREACHABLE"; }
971 11 50       29 if(defined $state_text) {
972 11         15 my $hard = "";
973 11 50       44 $hard = " (HARD)" if $data->{'hard'};
974 11         84 $self->_add_log_entry(
975             'log' => {
976             'start' => $data->{'time'},
977             'type' => 'HOST '.$state_text.$hard,
978             'plugin_output' => $data->{'plugin_output'},
979             'class' => $state_text,
980             'in_downtime' => $host_hist->{'in_downtime'},
981             },
982             );
983             }
984             }
985             elsif($data->{'type'} eq 'HOST DOWNTIME ALERT') {
986 5 50       17 next unless $self->{'report_options'}->{'showscheduleddowntime'};
987              
988 5         17 my $last_state_time = $host_hist->{'last_state_time'};
989              
990 5 50       16 $self->_log('_process_log_line() hostdowntime, inserting fake event for all hosts/services') if $verbose;
991             # set an event for all services
992 5         7 for my $service_description (keys %{$self->{'service_data'}->{$data->{'host_name'}}}) {
  5         22  
993 3         18 $last_state_time = $self->{'service_data'}->{$data->{'host_name'}}->{$service_description}->{'last_state_time'};
994 3         18 &_set_service_event($self, $data->{'host_name'}, $service_description, $result, { 'start' => $data->{'start'}, 'end' => $data->{'end'}, 'time' => $data->{'time'} });
995             }
996              
997 5         14 undef $data->{'state'}; # we dont know the current state, so make sure it wont be overwritten
998              
999             # set the host event itself
1000 5         17 &_set_host_event($self,$data->{'host_name'}, $result, $data);
1001              
1002 5         7 my $start;
1003             my $plugin_output;
1004 5 100       15 if($data->{'start'}) {
1005 3         12 $start = "START";
1006 3         7 $plugin_output = 'Start of scheduled downtime';
1007 3         7 $host_hist->{'in_downtime'} = 1;
1008             }
1009             else {
1010 2         5 $start = "STOP";
1011 2         4 $plugin_output = 'End of scheduled downtime';
1012 2         5 $host_hist->{'in_downtime'} = 0;
1013             }
1014              
1015             # set a log entry
1016 5         49 $self->_add_log_entry(
1017             'log' => {
1018             'start' => $data->{'time'},
1019             'type' => 'HOST DOWNTIME '.$start,
1020             'plugin_output' => $plugin_output,
1021             'class' => 'INDETERMINATE',
1022             'in_downtime' => $host_hist->{'in_downtime'},
1023             },
1024             );
1025             }
1026             else {
1027 0 0       0 $self->_log(' -> unknown log type') if $verbose;
1028             }
1029             }
1030             else {
1031 45 50       112 $self->_log(' -> unknown log type') if $verbose;
1032             }
1033              
1034 108         160 return 1;
1035             }
1036              
1037              
1038             ########################################
1039             sub _set_service_event {
1040 124     124   243 my($self, $host_name, $service_description, $result, $data) = @_;
1041              
1042 124 50       273 $self->_log('_set_service_event()') if $verbose;
1043              
1044 124         205 my $host_hist = $self->{'host_data'}->{$host_name};
1045 124         209 my $service_hist = $self->{'service_data'}->{$host_name}->{$service_description};
1046 124         212 my $service_data = $result->{'services'}->{$host_name}->{$service_description};
1047              
1048             # check if we are inside the report time
1049 124 100 66     546 if($report_options_start < $data->{'time'} and $report_options_end >= $data->{'time'}) {
1050             # we got a last state?
1051 86 50       189 if(defined $service_hist->{'last_state'}) {
1052 86         147 my $diff = $data->{'time'} - $service_hist->{'last_state_time'};
1053              
1054             # outside timeperiod
1055 86 100 100     502 if(defined $self->{'in_timeperiod'} and !$self->{'in_timeperiod'}) {
    100          
    100          
    100          
    100          
    100          
    50          
1056 2         12 $self->_add_time($service_data, $data->{'time'}, 'time_indeterminate_outside_timeperiod', $diff);
1057             }
1058              
1059             # ok
1060             elsif($service_hist->{'last_state'} == STATE_OK) {
1061 39   66     207 $self->_add_time($service_data, $data->{'time'}, 'time_ok', $diff, ($service_hist->{'in_downtime'} or $host_hist->{'in_downtime'}));
1062             }
1063              
1064             # warning
1065             elsif($service_hist->{'last_state'} == STATE_WARNING) {
1066 3   33     19 $self->_add_time($service_data, $data->{'time'}, 'time_warning', $diff, ($service_hist->{'in_downtime'} or $host_hist->{'in_downtime'}));
1067             }
1068              
1069             # critical
1070             elsif($service_hist->{'last_state'} == STATE_CRITICAL) {
1071 5   66     44 $self->_add_time($service_data, $data->{'time'}, 'time_critical', $diff, ($service_hist->{'in_downtime'} or $host_hist->{'in_downtime'}));
1072             }
1073              
1074             # unknown
1075             elsif($service_hist->{'last_state'} == STATE_UNKNOWN) {
1076 3   33     22 $self->_add_time($service_data, $data->{'time'}, 'time_unknown', $diff, ($service_hist->{'in_downtime'} or $host_hist->{'in_downtime'}));
1077             }
1078              
1079             # no data yet
1080             elsif($service_hist->{'last_state'} == STATE_UNSPECIFIED) {
1081 33   66     197 $self->_add_time($service_data, $data->{'time'}, 'time_indeterminate_nodata', $diff, ($service_hist->{'in_downtime'} or $host_hist->{'in_downtime'}), 'scheduled_time_indeterminate');
1082             }
1083              
1084             # not running
1085             elsif($service_hist->{'last_state'} == STATE_NOT_RUNNING) {
1086 1         3 $self->_add_time($service_data, $data->{'time'}, 'time_indeterminate_notrunning', $diff);
1087             }
1088              
1089             }
1090             }
1091              
1092             # set last state
1093 124 100       277 if(defined $data->{'state'}) {
1094 119 50       241 $self->_log('_set_service_event() set last state = '.$data->{'state'}) if $verbose;
1095 119         181 $service_hist->{'last_state'} = $data->{'state'};
1096 119 100       314 $service_hist->{'last_known_state'} = $data->{'state'} if $data->{'state'} >= 0;
1097             }
1098              
1099 124         177 $service_hist->{'last_state_time'} = $data->{'time'};
1100              
1101 124         380 return 1;
1102             }
1103              
1104              
1105             ########################################
1106             sub _set_host_event {
1107 52     52   97 my($self, $host_name, $result, $data) = @_;
1108              
1109 52 50       148 $self->_log('_set_host_event()') if $verbose;
1110              
1111 52         95 my $host_hist = $self->{'host_data'}->{$host_name};
1112 52         91 my $host_data = $result->{'hosts'}->{$host_name};
1113              
1114             # check if we are inside the report time
1115 52 100 66     283 if($report_options_start < $data->{'time'} and $report_options_end >= $data->{'time'}) {
1116             # we got a last state?
1117 39 50       135 if(defined $host_hist->{'last_state'}) {
1118 39         74 my $diff = $data->{'time'} - $host_hist->{'last_state_time'};
1119              
1120             # outside timeperiod
1121 39 100 100     408 if(defined $self->{'in_timeperiod'} and !$self->{'in_timeperiod'}) {
    100          
    100          
    100          
    100          
    50          
1122 1         5 $self->_add_time($host_data, $data->{'time'}, 'time_indeterminate_outside_timeperiod', $diff);
1123             }
1124              
1125             # up
1126             elsif($host_hist->{'last_state'} == STATE_UP) {
1127 14         40 $self->_add_time($host_data, $data->{'time'}, 'time_up', $diff, $host_hist->{'in_downtime'});
1128             }
1129              
1130             # down
1131             elsif($host_hist->{'last_state'} == STATE_DOWN) {
1132 3         10 $self->_add_time($host_data, $data->{'time'}, 'time_down', $diff, $host_hist->{'in_downtime'});
1133             }
1134              
1135             # unreachable
1136             elsif($host_hist->{'last_state'} == STATE_UNREACHABLE) {
1137 1         4 $self->_add_time($host_data, $data->{'time'}, 'time_unreachable', $diff, $host_hist->{'in_downtime'});
1138             }
1139              
1140             # no data yet
1141             elsif($host_hist->{'last_state'} == STATE_UNSPECIFIED) {
1142 18         69 $self->_add_time($host_data, $data->{'time'}, 'time_indeterminate_nodata', $diff, $host_hist->{'in_downtime'}, 'scheduled_time_indeterminate');
1143             }
1144              
1145             # not running
1146             elsif($host_hist->{'last_state'} == STATE_NOT_RUNNING) {
1147 2         8 $self->_add_time($host_data, $data->{'time'}, 'time_indeterminate_notrunning', $diff);
1148             }
1149             }
1150             }
1151              
1152             # set last state
1153 52 100       141 if(defined $data->{'state'}) {
1154 47 50       124 $self->_log('_set_host_event() set last state = '.$data->{'state'}) if $verbose;
1155 47         75 $host_hist->{'last_state'} = $data->{'state'};
1156 47 100       129 $host_hist->{'last_known_state'} = $data->{'state'} if $data->{'state'} >= 0;
1157             }
1158 52         98 $host_hist->{'last_state_time'} = $data->{'time'};
1159              
1160 52         245 return 1;
1161             }
1162              
1163             ########################################
1164             sub _add_time {
1165 125     125   375 my($self, $data, $date, $type, $diff, $in_downtime, $scheduled_type) = @_;
1166 125 100       299 $scheduled_type = 'scheduled_'.$type unless defined $scheduled_type;
1167 125 50       259 $self->_log('_add_time() '.$type.' + '.$diff.' seconds ('.$self->_duration($diff).')') if $verbose;
1168 125         226 $data->{$type} += $diff;
1169 125 100       243 if($in_downtime) {
1170 5 50       21 $self->_log('_add_time() '.$type.' scheduled + '.$diff.' seconds') if $verbose;
1171 5         12 $data->{$scheduled_type} += $diff;
1172             }
1173              
1174             # breakdowns?
1175 125 100       380 if($self->{'report_options'}->{'breakdown'} != BREAK_NONE) {
1176 9         19 my $timestr = $self->_get_break_timestr($date-1);
1177 9         20 $data->{'breakdown'}->{$timestr}->{$type} += $diff;
1178 9 50       23 $self->_log('_add_time() breakdown('.$timestr.') '.$type.' + '.$diff.' seconds ('.$self->_duration($diff).')') if $verbose;
1179 9 50       20 if($in_downtime) {
1180 0 0       0 $self->_log('_add_time() breakdown('.$timestr.') '.$type.' scheduled + '.$diff.' seconds ('.$self->_duration($diff).')') if $verbose;
1181 0         0 $data->{'breakdown'}->{$timestr}->{$scheduled_type} += $diff;
1182             }
1183             }
1184 125         279 return;
1185             }
1186              
1187              
1188             ########################################
1189             sub _log {
1190 0     0   0 my($self, $text) = @_;
1191 0 0       0 return 1 unless $verbose;
1192              
1193 0 0       0 if(ref $text ne '') {
1194 0         0 $Data::Dumper::Sortkeys = \&_logging_filter;
1195 0         0 $text = Dumper($text);
1196             }
1197 0         0 $self->{'logger'}->debug($text);
1198              
1199 0         0 return 1;
1200             }
1201              
1202             ########################################
1203             sub _logging_filter {
1204 0     0   0 my ($hash) = @_;
1205 0         0 my @keys = keys %{$hash};
  0         0  
1206             # filter a few keys we don't want to log
1207 0         0 @keys = grep {!/^(state_string_2_int
  0         0  
1208             |logger
1209             |peer_addr
1210             |peer_name
1211             |peer_key
1212             |log_string
1213             |log_livestatus
1214             |log_file
1215             |log_dir
1216             |log_iterator
1217             |current_host_groups
1218             )$/mx} @keys;
1219 0         0 return \@keys;
1220             }
1221             ##############################################
1222             # calculate a duration in the
1223             # format: 0d 0h 29m 43s
1224             sub _duration {
1225 81     81   117 my($self, $duration) = @_;
1226              
1227 81 50       149 croak("undef duration in duration(): ".$duration) unless defined $duration;
1228 81 100       171 $duration = $duration * -1 if $duration < 0;
1229              
1230 81 50       142 if($duration < 0) { $duration = time() + $duration; }
  0         0  
1231              
1232 81         102 my $days = 0;
1233 81         98 my $hours = 0;
1234 81         91 my $minutes = 0;
1235 81         80 my $seconds = 0;
1236 81 100       196 if($duration >= 86400) {
1237 14         26 $days = int($duration/86400);
1238 14         21 $duration = $duration%86400;
1239             }
1240 81 100       190 if($duration >= 3600) {
1241 23         38 $hours = int($duration/3600);
1242 23         34 $duration = $duration%3600;
1243             }
1244 81 100       144 if($duration >= 60) {
1245 37         51 $minutes = int($duration/60);
1246 37         52 $duration = $duration%60;
1247             }
1248 81         124 $seconds = $duration;
1249              
1250 81         425 return($days."d ".$hours."h ".$minutes."m ".$seconds."s");
1251             }
1252              
1253             ########################################
1254             sub _insert_fake_event {
1255 45     45   74 my($self, $result, $time) = @_;
1256              
1257 45 50       114 $self->_log('_insert_fake_event()') if $verbose;
1258 45         58 for my $host (keys %{$result->{'services'}}) {
  45         157  
1259 26         39 for my $service (keys %{$result->{'services'}->{$host}}) {
  26         75  
1260 64         75 my $last_service_state = STATE_UNSPECIFIED;
1261 64 100       222 if(defined $self->{'service_data'}->{$host}->{$service}->{'last_known_state'}) {
    50          
1262 35         66 $last_service_state = $self->{'service_data'}->{$host}->{$service}->{'last_known_state'};
1263             }
1264             elsif(defined $self->{'service_data'}->{$host}->{$service}->{'last_state'}) {
1265 29         59 $last_service_state = $self->{'service_data'}->{$host}->{$service}->{'last_state'};
1266             }
1267 64         258 my $fakedata = {
1268             'service_description' => $service,
1269             'time' => $time,
1270             'host_name' => $host,
1271             'type' => 'INITIAL SERVICE STATE',
1272             'hard' => 1,
1273             'state' => $last_service_state,
1274             };
1275 64         157 $self->_set_service_event($host, $service, $result, $fakedata);
1276             }
1277             }
1278              
1279 45         249 for my $host (keys %{$result->{'hosts'}}) {
  45         190  
1280 21         26 my $last_host_state = STATE_UNSPECIFIED;
1281 21 100       101 if(defined $self->{'host_data'}->{$host}->{'last_known_state'}) {
    50          
1282 11         25 $last_host_state = $self->{'host_data'}->{$host}->{'last_known_state'};
1283             }
1284             elsif(defined $self->{'host_data'}->{$host}->{'last_state'}) {
1285 10         32 $last_host_state = $self->{'host_data'}->{$host}->{'last_state'};
1286             }
1287 21         93 my $fakedata = {
1288             'time' => $time,
1289             'host_name' => $host,
1290             'type' => 'INITIAL HOST STATE',
1291             'hard' => 1,
1292             'state' => $last_host_state,
1293             };
1294 21         55 &_set_host_event($self, $host, $result, $fakedata);
1295             }
1296              
1297 45         99 return 1;
1298             }
1299              
1300             ########################################
1301             sub _set_default_options {
1302 20     20   44 my $self = shift;
1303 20         42 my $options = shift;
1304              
1305 20 100       105 $options->{'backtrack'} = 4 unless defined $options->{'backtrack'};
1306 20 50       69 $options->{'assumeinitialstates'} = 'yes' unless defined $options->{'assumeinitialstates'};
1307 20 100       72 $options->{'assumestateretention'} = 'yes' unless defined $options->{'assumestateretention'};
1308 20 100       73 $options->{'assumestatesduringnotrunning'} = 'yes' unless defined $options->{'assumestatesduringnotrunning'};
1309 20 50       153 $options->{'includesoftstates'} = 'no' unless defined $options->{'includesoftstates'};
1310 20 50       78 $options->{'initialassumedhoststate'} = 'unspecified' unless defined $options->{'initialassumedhoststate'};
1311 20 50       106 $options->{'initialassumedservicestate'} = 'unspecified' unless defined $options->{'initialassumedservicestate'};
1312 20 100       78 $options->{'showscheduleddowntime'} = 'yes' unless defined $options->{'showscheduleddowntime'};
1313 20 100       92 $options->{'timeformat'} = '%s' unless defined $options->{'timeformat'};
1314 20 100       67 $options->{'breakdown'} = BREAK_NONE unless defined $options->{'breakdown'};
1315              
1316 20         55 return $options;
1317             }
1318              
1319             ########################################
1320             sub _verify_options {
1321 38     38   64 my $self = shift;
1322 38         396 my $options = shift;
1323              
1324             # set default backtrack to 4 days
1325 38 100       780 if(defined $options->{'backtrack'}) {
1326 20 50       70 if($options->{'backtrack'} < 0) {
1327 0         0 croak('backtrack has to be a positive integer');
1328             }
1329             }
1330              
1331             # our yes no options
1332 38         91 for my $yes_no (qw/assumeinitialstates
1333             assumestateretention
1334             assumestatesduringnotrunning
1335             includesoftstates
1336             showscheduleddowntime
1337             /) {
1338 190 100       494 if(defined $options->{$yes_no}) {
1339 100 100       351 if(lc $options->{$yes_no} eq 'yes') {
    50          
1340 70         172 $options->{$yes_no} = TRUE;
1341             }
1342             elsif(lc $options->{$yes_no} eq 'no') {
1343 30         72 $options->{$yes_no} = FALSE;
1344             } else {
1345 0         0 croak($yes_no.' unknown, please use \'yes\' or \'no\'. Got: '.$options->{$yes_no});
1346             }
1347             }
1348             }
1349              
1350             # set initial assumed host state
1351 38 100       162 if(defined $options->{'initialassumedhoststate'}) {
1352 20 100       129 if(lc $options->{'initialassumedhoststate'} eq 'unspecified') {
    50          
    50          
    0          
    0          
1353 18         43 $options->{'initialassumedhoststate'} = STATE_UNSPECIFIED;
1354             }
1355             elsif(lc $options->{'initialassumedhoststate'} eq 'current') {
1356 0         0 $options->{'initialassumedhoststate'} = STATE_CURRENT;
1357             }
1358             elsif(lc $options->{'initialassumedhoststate'} eq 'up') {
1359 2         4 $options->{'initialassumedhoststate'} = STATE_UP;
1360             }
1361             elsif(lc $options->{'initialassumedhoststate'} eq 'down') {
1362 0         0 $options->{'initialassumedhoststate'} = STATE_DOWN;
1363             }
1364             elsif(lc $options->{'initialassumedhoststate'} eq 'unreachable') {
1365 0         0 $options->{'initialassumedhoststate'} = STATE_UNREACHABLE;
1366             }
1367             else {
1368 0         0 croak('initialassumedhoststate unknown, please use one of: unspecified, current, up, down or unreachable. Got: '.$options->{'initialassumedhoststate'});
1369             }
1370             }
1371              
1372             # set initial assumed service state
1373 38 100       121 if(defined $options->{'initialassumedservicestate'}) {
1374 20 100       111 if(lc $options->{'initialassumedservicestate'} eq 'unspecified') {
    100          
    100          
    50          
    50          
    0          
1375 16         35 $options->{'initialassumedservicestate'} = STATE_UNSPECIFIED;
1376             }
1377             elsif(lc $options->{'initialassumedservicestate'} eq 'current') {
1378 1         4 $options->{'initialassumedservicestate'} = STATE_CURRENT;
1379             }
1380             elsif(lc $options->{'initialassumedservicestate'} eq 'ok') {
1381 2         4 $options->{'initialassumedservicestate'} = STATE_OK;
1382             }
1383             elsif(lc $options->{'initialassumedservicestate'} eq 'warning') {
1384 0         0 $options->{'initialassumedservicestate'} = STATE_WARNING;
1385             }
1386             elsif(lc $options->{'initialassumedservicestate'} eq 'unknown') {
1387 1         3 $options->{'initialassumedservicestate'} = STATE_UNKNOWN;
1388             }
1389             elsif(lc $options->{'initialassumedservicestate'} eq 'critical') {
1390 0         0 $options->{'initialassumedservicestate'} = STATE_CRITICAL;
1391             }
1392             else {
1393 0         0 croak('initialassumedservicestate unknown, please use one of: unspecified, current, ok, warning, unknown or critical. Got: '.$options->{'initialassumedservicestate'});
1394             }
1395             }
1396              
1397             # set breakdown
1398 38 100       118 if(defined $options->{'breakdown'}) {
1399 20 50 33     449 if(lc $options->{'breakdown'} eq '') {
    50 33        
    50 0        
    100          
    50          
    50          
1400 0         0 $options->{'breakdown'} = BREAK_NONE;
1401             }
1402             elsif(lc $options->{'breakdown'} eq 'months') {
1403 0         0 $options->{'breakdown'} = BREAK_MONTHS;
1404             }
1405             elsif(lc $options->{'breakdown'} eq 'weeks') {
1406 0         0 $options->{'breakdown'} = BREAK_WEEKS;
1407             }
1408             elsif(lc $options->{'breakdown'} eq 'days') {
1409 2         4 $options->{'breakdown'} = BREAK_DAYS;
1410             }
1411             elsif(lc $options->{'breakdown'} eq 'none') {
1412 0         0 $options->{'breakdown'} = BREAK_NONE;
1413             }
1414             elsif( $options->{'breakdown'} == BREAK_NONE
1415             or $options->{'breakdown'} == BREAK_DAYS
1416             or $options->{'breakdown'} == BREAK_WEEKS
1417             or $options->{'breakdown'} == BREAK_MONTHS) {
1418             # ok
1419             }
1420             else {
1421 0         0 croak('breakdown unknown, please use one of: months, weeks, days or none. Got: '.$options->{'breakdown'});
1422             }
1423             }
1424              
1425 38         96 return $options;
1426             }
1427              
1428             ########################################
1429             sub _add_log_entry {
1430 105     105   356 my($self, %opts) = @_;
1431              
1432 105 50       223 $self->_log('_add_log_entry()') if $verbose;
1433              
1434             # do we build up a log?
1435 105 50       355 return if $self->{'report_options'}->{'build_log'} == FALSE;
1436              
1437 105         110 push @{$self->{'full_log_store'}}, \%opts;
  105         469  
1438              
1439 105         243 return 1;
1440             }
1441              
1442             ########################################
1443             sub _calculate_log {
1444 9     9   19 my($self) = @_;
1445              
1446 9 50       43 $self->_log('_calculate_log()') if $verbose;
1447              
1448             # combine outside report range log events
1449 9         19 my $changed = FALSE;
1450 9 50       35 if(defined $self->{'first_known_state_before_report'}) {
1451 0         0 push @{$self->{'full_log_store'}}, $self->{'first_known_state_before_report'};
  0         0  
1452 0         0 $changed = TRUE;
1453             }
1454 9 50       37 if(defined $self->{'first_known_proc_before_report'}) {
1455 0         0 push @{$self->{'full_log_store'}}, $self->{'first_known_proc_before_report'};
  0         0  
1456 0         0 $changed = TRUE;
1457             }
1458              
1459             # sort once more if changed
1460 9 50       29 if($changed) {
1461 0         0 @{$self->{'full_log_store'}} = sort { $a->{'log'}->{'start'} <=> $b->{'log'}->{'start'} } @{$self->{'full_log_store'}};
  0         0  
  0         0  
  0         0  
1462             }
1463              
1464             # insert fakelog service entry when initial state is fixed
1465 9 100 100     64 if($self->{'report_options'}->{'initialassumedservicestate'} != STATE_UNSPECIFIED
  3         17  
1466             and scalar @{$self->{'report_options'}->{'services'}} == 1
1467             ) {
1468 2         3 my $type;
1469 2         5 my $first_state = $self->{'report_options'}->{'initialassumedservicestate'};
1470 2 100       7 if($first_state == STATE_CURRENT) { $first_state = $self->{'report_options'}->{'first_state'}; }
  1         4  
1471 2 50       11 if($first_state == STATE_OK) { $type = 'OK'; }
  0 100       0  
    50          
    0          
1472 1         2 elsif($first_state == STATE_WARNING) { $type = 'WARNING'; }
1473 1         2 elsif($first_state == STATE_UNKNOWN) { $type = 'UNKNOWN'; }
1474 0         0 elsif($first_state == STATE_CRITICAL) { $type = 'CRITICAL'; }
1475 2         17 my $fake_start = $self->{'report_options'}->{'start'};
1476 2 50       9 if(defined $self->{'full_log_store'}->[0]) {
1477 2 50       9 if($fake_start >= $self->{'full_log_store'}->[0]->{'log'}->{'start'}) { $fake_start = $self->{'full_log_store'}->[0]->{'log'}->{'start'} - 1; }
  2         8  
1478             }
1479 2         14 my $fakelog = {
1480             'log' => {
1481             'type' => 'SERVICE '.$type.' (HARD)',
1482             'class' => $type,
1483             'start' => $fake_start,
1484             'plugin_output' => 'First Service State Assumed (Faked Log Entry)',
1485             }
1486             };
1487 2         4 unshift @{$self->{'full_log_store'}}, $fakelog;
  2         9  
1488             }
1489              
1490             # insert fakelog host entry when initial state is fixed
1491 9 100 66     62 if($self->{'report_options'}->{'initialassumedhoststate'} != STATE_UNSPECIFIED
  1         5  
1492             and scalar @{$self->{'report_options'}->{'hosts'}} == 1
1493             ) {
1494 1         2 my $type;
1495 1         3 my $first_state = $self->{'report_options'}->{'initialassumedhoststate'};
1496 1 50       3 if($first_state == STATE_CURRENT) { $first_state = $self->{'report_options'}->{'first_state'}; }
  0         0  
1497 1 50       3 if($first_state == STATE_UP) { $type = 'UP'; }
  1 0       1  
    0          
1498 0         0 elsif($first_state == STATE_DOWN) { $type = 'DOWN'; }
1499 0         0 elsif($first_state == STATE_UNREACHABLE) { $type = 'UNREACHABLE'; }
1500 1         2 my $fake_start = $self->{'report_options'}->{'start'};
1501 1 50 33     12 if(defined $self->{'full_log_store'}->[0] and $fake_start >= $self->{'full_log_store'}->[0]->{'log'}->{'start'}) { $fake_start = $self->{'full_log_store'}->[0]->{'log'}->{'start'} - 1; }
  0         0  
1502 1         5 my $fakelog = {
1503             'log' => {
1504             'type' => 'HOST '.$type.' (HARD)',
1505             'class' => $type,
1506             'start' => $fake_start,
1507             'plugin_output' => 'First Host State Assumed (Faked Log Entry)',
1508             }
1509             };
1510 1         3 unshift @{$self->{'full_log_store'}}, $fakelog;
  1         2  
1511             }
1512              
1513 9 50       30 if($verbose) {
1514 0         0 $self->_log("#################################");
1515 0         0 $self->_log("LOG STORE:");
1516 0         0 $self->_log(\@{$self->{'full_log_store'}});
  0         0  
1517 0         0 $self->_log("#################################");
1518             }
1519              
1520 9         24 for(my $x = 0; $x < scalar @{$self->{'full_log_store'}}; $x++) {
  90         241  
1521 81         132 my $log_entry = $self->{'full_log_store'}->[$x];
1522 81         118 my $next_log_entry = $self->{'full_log_store'}->[$x+1];
1523 81         111 my $log = $log_entry->{'log'};
1524              
1525             # set end date of current log entry
1526 81 100       279 if(defined $next_log_entry->{'log'}->{'start'}) {
1527 72         143 $log->{'end'} = $next_log_entry->{'log'}->{'start'};
1528 72         215 $log->{'duration'} = $self->_duration($log->{'start'} - $log->{'end'});
1529             } else {
1530 9         23 $log->{'end'} = $self->{'report_options'}->{'end'};
1531 9         40 $log->{'duration'} = $self->_duration($log->{'start'} - $log->{'end'}).'+';
1532             }
1533              
1534             # convert time format
1535 81 100       237 if($self->{'report_options'}->{'timeformat'} ne '%s') {
1536 80         1051 $log->{'end'} = strftime $self->{'report_options'}->{'timeformat'}, localtime($log->{'end'});
1537 80         625 $log->{'start'} = strftime $self->{'report_options'}->{'timeformat'}, localtime($log->{'start'});
1538             }
1539              
1540 81 100       232 push @{$self->{'log_output'}}, $log unless defined $log_entry->{'full_only'};
  30         65  
1541 81         90 push @{$self->{'full_log_output'}}, $log;
  81         232  
1542             }
1543              
1544 9         26 $self->{'log_output_calculated'} = TRUE;
1545 9         24 return 1;
1546             }
1547              
1548              
1549             ########################################
1550             sub _state_to_int {
1551 1     1   3 my($self, $string) = @_;
1552              
1553 1 50       4 return unless defined $string;
1554              
1555 1 50       7 if(defined $self->{'state_string_2_int'}->{$string}) {
1556 1         3 return $self->{'state_string_2_int'}->{$string};
1557             }
1558              
1559 0         0 croak("valid values for services are: ok, warning, unknown and critical\nvalues for host: up, down and unreachable");
1560             }
1561              
1562             ########################################
1563             sub _new_service_data {
1564 32     32   68 my($self, $breakdown, $timestamp) = @_;
1565 32         258 my $data = {
1566             time_ok => 0,
1567             time_warning => 0,
1568             time_unknown => 0,
1569             time_critical => 0,
1570              
1571             scheduled_time_ok => 0,
1572             scheduled_time_warning => 0,
1573             scheduled_time_unknown => 0,
1574             scheduled_time_critical => 0,
1575             scheduled_time_indeterminate => 0,
1576              
1577             time_indeterminate_nodata => 0,
1578             time_indeterminate_notrunning => 0,
1579             time_indeterminate_outside_timeperiod => 0,
1580             };
1581 32 50       86 $data->{'timestamp'} = $timestamp if $timestamp;
1582 32 50       76 if($breakdown != BREAK_NONE) {
1583 0         0 $data->{'breakdown'} = {};
1584 0         0 for my $cur (@{$self->{'breakpoints'}}) {
  0         0  
1585 0         0 my $timestr = $self->_get_break_timestr($cur);
1586 0 0       0 next if defined $data->{'breakdown'}->{$timestr};
1587 0         0 $data->{'breakdown'}->{$timestr} = $self->_new_service_data(BREAK_NONE, $cur);
1588             }
1589             }
1590 32         83 return $data;
1591             }
1592              
1593             ########################################
1594             sub _new_host_data {
1595 13     13   24 my($self, $breakdown, $timestamp) = @_;
1596 13         120 my $data = {
1597             time_up => 0,
1598             time_down => 0,
1599             time_unreachable => 0,
1600              
1601             scheduled_time_up => 0,
1602             scheduled_time_down => 0,
1603             scheduled_time_unreachable => 0,
1604             scheduled_time_indeterminate => 0,
1605              
1606             time_indeterminate_nodata => 0,
1607             time_indeterminate_notrunning => 0,
1608             time_indeterminate_outside_timeperiod => 0,
1609             };
1610 13 100       38 $data->{'timestamp'} = $timestamp if $timestamp;
1611 13 100       39 if($breakdown != BREAK_NONE) {
1612 2         4 $data->{'breakdown'} = {};
1613 2         2 for my $cur (@{$self->{'breakpoints'}}) {
  2         5  
1614 5         10 my $timestr = $self->_get_break_timestr($cur);
1615 5 50       14 next if defined $data->{'breakdown'}->{$timestr};
1616 5         17 $data->{'breakdown'}->{$timestr} = $self->_new_host_data(BREAK_NONE, $cur);
1617             }
1618             }
1619 13         38 return $data;
1620             }
1621              
1622             ########################################
1623             sub _get_break_timestr {
1624 14     14   17 my($self, $timestamp) = @_;
1625              
1626 14         64 my @localtime = localtime($timestamp);
1627              
1628 14 50       33 if($self->{'report_options'}->{'breakdown'} == BREAK_DAYS) {
    0          
    0          
1629 14         103 return strftime('%Y-%m-%d', @localtime);
1630             }
1631             elsif($self->{'report_options'}->{'breakdown'} == BREAK_WEEKS) {
1632 0         0 return strftime('%G-WK%V', @localtime);
1633             }
1634             elsif($self->{'report_options'}->{'breakdown'} == BREAK_MONTHS) {
1635 0         0 return strftime('%Y-%m', @localtime);
1636             }
1637 0         0 die('huh?');
1638 0         0 return;
1639             }
1640              
1641             ########################################
1642             sub _set_breakpoints {
1643 20     20   51 my($self) = @_;
1644 20         58 $self->{'breakpoints'} = [];
1645              
1646 20 100       91 return if $self->{'report_options'}->{'breakdown'} == BREAK_NONE;
1647              
1648 2         3 my $cur = $self->{'report_options'}->{'start'};
1649             # round to next 0:00
1650 2         36 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($cur);
1651 2         17 $cur = mktime(0, 0, 0, $mday, $mon, $year, $wday, $yday, $isdst);
1652 2         6 while($cur < $self->{'report_options'}->{'end'}) {
1653 5         6 push @{$self->{'breakpoints'}}, $cur;
  5         11  
1654 5         13 $cur = $cur + 86400;
1655             }
1656 2         3 return;
1657             }
1658              
1659             ########################################
1660              
1661             1;
1662              
1663             =head1 BUGS
1664              
1665             Please report any bugs or feature requests to L.
1666              
1667             =head1 DEBUGING
1668              
1669             You may enable the debug mode by setting MONITORING_AVAILABILITY_DEBUG environment variable.
1670             This will create a logfile: /tmp/Monitoring-Availability-Debug.log which gets overwritten with
1671             every calculation.
1672             You will need the Log4Perl module to create this logfile.
1673              
1674             =head1 SEE ALSO
1675              
1676             You can also look for information at:
1677              
1678             =over 4
1679              
1680             =item * Search CPAN
1681              
1682             L
1683              
1684             =item * Github
1685              
1686             L
1687              
1688             =back
1689              
1690             =head1 AUTHOR
1691              
1692             Sven Nierlein, Enierlein@cpan.orgE
1693              
1694             =head1 COPYRIGHT AND LICENSE
1695              
1696             Copyright (C) 2010 by Sven Nierlein
1697              
1698             This library is free software; you can redistribute it and/or modify
1699             it under the same terms as Perl itself.
1700              
1701             =cut
1702              
1703             __END__