| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package App::OpenVZ::BCWatch; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 2 |  |  | 2 |  | 76356 | use strict; | 
|  | 2 |  |  |  |  | 10 |  | 
|  | 2 |  |  |  |  | 60 |  | 
| 4 | 2 |  |  | 2 |  | 18 | use warnings; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 57 |  | 
| 5 | 2 |  |  | 2 |  | 514 | use boolean qw(true false); | 
|  | 2 |  |  |  |  | 3527 |  | 
|  | 2 |  |  |  |  | 9 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 2 |  |  | 2 |  | 157 | use Carp qw(croak); | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 143 |  | 
| 8 | 2 |  |  | 2 |  | 13 | use File::Basename qw(basename); | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 199 |  | 
| 9 | 2 |  |  | 2 |  | 1158 | use File::HomeDir (); | 
|  | 2 |  |  |  |  | 11678 |  | 
|  | 2 |  |  |  |  | 46 |  | 
| 10 | 2 |  |  | 2 |  | 13 | use File::Spec (); | 
|  | 2 |  |  |  |  | 6 |  | 
|  | 2 |  |  |  |  | 61 |  | 
| 11 | 2 |  |  | 2 |  | 1212 | use Mail::Sendmail qw(sendmail); | 
|  | 2 |  |  |  |  | 34391 |  | 
|  | 2 |  |  |  |  | 136 |  | 
| 12 | 2 |  |  | 2 |  | 1335 | use Storable qw(store retrieve); | 
|  | 2 |  |  |  |  | 6841 |  | 
|  | 2 |  |  |  |  | 147 |  | 
| 13 | 2 |  |  | 2 |  | 19 | use Sys::Hostname qw(hostname); | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 824 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | our $VERSION = '0.05'; | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | sub new | 
| 18 |  |  |  |  |  |  | { | 
| 19 | 1 |  |  | 1 | 1 | 700 | my $class = shift; | 
| 20 | 1 |  |  |  |  | 8 | my %args = @_; | 
| 21 |  |  |  |  |  |  |  | 
| 22 | 1 | 100 |  | 3 |  | 11 | my $defined_or = sub { defined $_[0] ? $_[0] : $_[1] }; | 
|  | 3 |  |  |  |  | 36 |  | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | my $self = bless { | 
| 25 |  |  |  |  |  |  | Config => { | 
| 26 |  |  |  |  |  |  | input_file      => $args{input_file} || '/proc/user_beancounters', | 
| 27 |  |  |  |  |  |  | data_file       => $args{data_file}  || File::Spec->catfile(File::HomeDir->my_home, 'vzwatchd.dat'), | 
| 28 |  |  |  |  |  |  | _field_names    => [ qw(uid resource held maxheld barrier limit failcnt) ], | 
| 29 |  |  |  |  |  |  | _exclude_fields => [ qw(uid resource) ], | 
| 30 |  |  |  |  |  |  | monitor_fields  => $args{monitor_fields} || [ qw(failcnt) ], | 
| 31 |  |  |  |  |  |  | mail => { | 
| 32 |  |  |  |  |  |  | from    => $args{mail}->{from}    || 'root@localhost', | 
| 33 |  |  |  |  |  |  | to      => $args{mail}->{to}      || 'root@localhost', | 
| 34 |  |  |  |  |  |  | subject => $args{mail}->{subject} || 'vzwatchd: NOTICE', | 
| 35 |  |  |  |  |  |  | }, | 
| 36 |  |  |  |  |  |  | sleep   => $defined_or->($args{sleep}, 60), | 
| 37 |  |  |  |  |  |  | verbose => $defined_or->($args{verbose}, false), | 
| 38 | 1 |  | 50 |  |  | 36 | _tests  => $defined_or->($args{_tests}, false), | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 50 |  |  |  |  | 
|  |  |  | 50 |  |  |  |  | 
|  |  |  | 50 |  |  |  |  | 
|  |  |  | 50 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 39 |  |  |  |  |  |  | } | 
| 40 |  |  |  |  |  |  | }, ref($class) || $class; | 
| 41 |  |  |  |  |  |  |  | 
| 42 | 1 |  |  |  |  | 8 | $self->_init; | 
| 43 |  |  |  |  |  |  |  | 
| 44 | 1 |  |  |  |  | 8 | return $self; | 
| 45 |  |  |  |  |  |  | } | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | sub process | 
| 48 |  |  |  |  |  |  | { | 
| 49 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 50 |  |  |  |  |  |  |  | 
| 51 | 0 |  |  |  |  | 0 | delete @$self{qw(data stored)}; | 
| 52 |  |  |  |  |  |  |  | 
| 53 | 0 |  |  |  |  | 0 | $self->_get_data_running; | 
| 54 | 0 |  |  |  |  | 0 | $self->_get_data_file; | 
| 55 | 0 |  |  |  |  | 0 | $self->_compare_data; | 
| 56 | 0 |  |  |  |  | 0 | $self->_put_data_file; | 
| 57 |  |  |  |  |  |  | } | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | sub _init | 
| 60 |  |  |  |  |  |  | { | 
| 61 | 1 |  |  | 1 |  | 2 | my $self = shift; | 
| 62 |  |  |  |  |  |  |  | 
| 63 | 1 | 50 |  |  |  | 2 | eval { store({}, $self->{Config}->{data_file}) } | 
|  | 1 |  |  |  |  | 11 |  | 
| 64 |  |  |  |  |  |  | or croak "Cannot store to $self->{Config}->{data_file}: $!"; | 
| 65 |  |  |  |  |  |  |  | 
| 66 | 1 |  |  |  |  | 291 | my $pkg_tmpl = join '::', (__PACKAGE__, '_template'); | 
| 67 | 2 |  |  | 2 |  | 16 | no strict 'refs'; | 
|  | 2 |  |  |  |  | 9 |  | 
|  | 2 |  |  |  |  | 4274 |  | 
| 68 |  |  |  |  |  |  |  | 
| 69 | 1 | 50 |  |  |  | 3 | if (defined ${$pkg_tmpl}) { | 
|  | 1 |  |  |  |  | 9 |  | 
| 70 | 0 |  |  |  |  | 0 | $self->{template} = ${$pkg_tmpl}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 71 |  |  |  |  |  |  | } | 
| 72 |  |  |  |  |  |  | else { | 
| 73 | 1 |  |  |  |  | 3 | ${$pkg_tmpl} = $self->{template} = do { | 
|  | 1 |  |  |  |  | 5 |  | 
| 74 | 1 |  |  |  |  | 5 | local $/ = '__END__'; | 
| 75 | 1 |  |  |  |  | 8 | local $_ = ; | 
| 76 | 1 |  |  |  |  | 4 | chomp; | 
| 77 | 1 |  |  |  |  | 7 | s/^\s+//; | 
| 78 | 1 |  |  |  |  | 14 | s/\s+\z//; | 
| 79 | 1 |  |  |  |  | 6 | $_ | 
| 80 |  |  |  |  |  |  | }; | 
| 81 |  |  |  |  |  |  | } | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | $self->{excluded} = { | 
| 84 |  |  |  |  |  |  | map { | 
| 85 | 7 |  |  |  |  | 26 | my $field = $_; | 
| 86 | 7 | 100 |  |  |  | 9 | (scalar grep $_ eq $field, @{$self->{Config}->{_exclude_fields}}) | 
|  | 7 |  |  |  |  | 28 |  | 
| 87 |  |  |  |  |  |  | ? ($field => true) | 
| 88 |  |  |  |  |  |  | : ($field => false) | 
| 89 | 1 |  |  |  |  | 2 | } @{$self->{Config}->{_field_names}} | 
|  | 1 |  |  |  |  | 4 |  | 
| 90 |  |  |  |  |  |  | }; | 
| 91 |  |  |  |  |  |  |  | 
| 92 | 1 |  |  |  |  | 10 | my $i; | 
| 93 | 1 |  |  |  |  | 4 | $self->{index} = { map { $_ => $i++ } @{$self->{Config}->{_field_names}} }; | 
|  | 7 |  |  |  |  | 16 |  | 
|  | 1 |  |  |  |  | 4 |  | 
| 94 |  |  |  |  |  |  | } | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | sub _get_data_running | 
| 97 |  |  |  |  |  |  | { | 
| 98 | 1 |  |  | 1 |  | 9 | my $self = shift; | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | open(my $fh, '<', $self->{Config}->{input_file}) | 
| 101 | 1 | 50 |  |  |  | 45 | or croak "Cannot read $self->{Config}->{input_file}: $!"; | 
| 102 | 1 |  |  |  |  | 4 | my $output = do { local $/; <$fh> }; | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 44 |  | 
| 103 | 1 |  |  |  |  | 12 | close($fh); | 
| 104 |  |  |  |  |  |  |  | 
| 105 | 1 |  |  |  |  | 4 | my $valid_format = join '\s+', @{$self->{Config}->{_field_names}}; | 
|  | 1 |  |  |  |  | 6 |  | 
| 106 |  |  |  |  |  |  |  | 
| 107 | 1 | 50 |  |  |  | 28 | unless ($output =~ /$valid_format/) { | 
| 108 | 0 |  |  |  |  | 0 | croak "Format of $self->{Config}->{input_file} not recognized"; | 
| 109 |  |  |  |  |  |  | } | 
| 110 |  |  |  |  |  |  |  | 
| 111 | 1 |  |  |  |  | 7 | my $re = qr{ | 
| 112 |  |  |  |  |  |  | \s*? | 
| 113 |  |  |  |  |  |  | (?:\d+?\:)?  \s+? | 
| 114 |  |  |  |  |  |  | (?:\w+?)     \s+? | 
| 115 |  |  |  |  |  |  | (?:(?:\d+?)  \s*?){5} | 
| 116 |  |  |  |  |  |  | }x; | 
| 117 |  |  |  |  |  |  |  | 
| 118 | 1 |  |  |  |  | 3 | my $uid; | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | my @names = grep { | 
| 121 | 7 | 100 |  |  |  | 184 | !$self->{excluded}->{$_} | 
| 122 |  |  |  |  |  |  | ? $_ : () | 
| 123 | 1 |  |  |  |  | 4 | } @{$self->{Config}->{_field_names}}; | 
|  | 1 |  |  |  |  | 4 |  | 
| 124 |  |  |  |  |  |  |  | 
| 125 | 1 |  |  |  |  | 30 | local $1; | 
| 126 | 1 |  |  |  |  | 566 | while ($output =~ /^($re)$/gm) { | 
| 127 | 144 |  |  |  |  | 372 | my $line = $1; | 
| 128 | 144 | 100 |  |  |  | 318 | if ($line =~ /^ \s+? (\d+?)\:/gx) { | 
| 129 | 6 |  |  |  |  | 13 | $uid = $1; | 
| 130 |  |  |  |  |  |  | } | 
| 131 | 144 |  |  |  |  | 187 | my $res; | 
| 132 | 144 | 50 |  |  |  | 496 | if ($line =~ /\G \s+? (\w+)/gx) { | 
| 133 | 144 |  |  |  |  | 288 | $res = $1; | 
| 134 |  |  |  |  |  |  | } | 
| 135 | 144 |  |  |  |  | 192 | my @fields; | 
| 136 | 144 | 50 |  |  |  | 426 | if ($line =~ /\G \s+ (.*) $/x) { | 
| 137 | 144 |  |  |  |  | 644 | @fields = split /\s+/, $1; | 
| 138 |  |  |  |  |  |  | } | 
| 139 | 144 |  |  |  |  | 459 | push @{$self->{data}{$uid}{$res}}, | 
| 140 | 144 |  |  |  |  | 195 | { map { $names[$_] => $fields[$_] } (0 .. $#fields) }; | 
|  | 720 |  |  |  |  | 16853 |  | 
| 141 |  |  |  |  |  |  | } | 
| 142 |  |  |  |  |  |  | } | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | sub _get_data_file | 
| 145 |  |  |  |  |  |  | { | 
| 146 | 1 |  |  | 1 |  | 812 | my $self = shift; | 
| 147 |  |  |  |  |  |  |  | 
| 148 | 1 | 50 |  |  |  | 2 | eval { $self->{stored} = retrieve($self->{Config}->{data_file}) } | 
|  | 1 |  |  |  |  | 6 |  | 
| 149 |  |  |  |  |  |  | or croak "Cannot retrieve from $self->{Config}->{data_file}: $!"; | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | sub _compare_data | 
| 153 |  |  |  |  |  |  | { | 
| 154 | 1 |  |  | 1 |  | 621 | my $self = shift; | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | my $has_changed = sub | 
| 157 |  |  |  |  |  |  | { | 
| 158 | 144 |  |  | 144 |  | 232 | my ($uid, $res, $i) = @_; | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | my ($data, $stored) = map { | 
| 161 | 144 | 100 |  |  |  | 201 | my $type = $_; sub { $self->{$type}{$uid}{$res}->[$i]->{$_[0]} || 0 } | 
|  | 288 |  |  |  |  | 426 |  | 
|  | 288 |  |  |  |  | 1343 |  | 
| 162 | 288 |  |  |  |  | 846 | } qw(data stored); | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 144 |  |  |  |  | 211 | return scalar grep { $data->($_) > $stored->($_) } @{$self->{Config}->{monitor_fields}}; | 
|  | 144 |  |  |  |  | 221 |  | 
|  | 144 |  |  |  |  | 251 |  | 
| 165 | 1 |  |  |  |  | 8 | }; | 
| 166 |  |  |  |  |  |  |  | 
| 167 | 1 |  |  |  |  | 3 | foreach my $uid (sort {$a <=> $b} keys %{$self->{stored}}) { | 
|  | 9 |  |  |  |  | 18 |  | 
|  | 1 |  |  |  |  | 9 |  | 
| 168 | 6 |  |  |  |  | 11 | foreach my $res (sort {$a cmp $b} keys %{$self->{stored}{$uid}}) { | 
|  | 421 |  |  |  |  | 561 |  | 
|  | 6 |  |  |  |  | 36 |  | 
| 169 | 126 |  |  |  |  | 187 | foreach my $index (0 .. $#{$self->{stored}{$uid}{$res}}) { | 
|  | 126 |  |  |  |  | 273 |  | 
| 170 | 144 | 100 |  |  |  | 233 | if ($has_changed->($uid, $res, $index)) { | 
| 171 | 2 |  |  |  |  | 13 | $self->_create_report($uid, $res, $index); | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  | } | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  | } | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | sub _create_report | 
| 179 |  |  |  |  |  |  | { | 
| 180 | 2 |  |  | 2 |  | 5 | my $self = shift; | 
| 181 | 2 |  |  |  |  | 5 | my ($uid, $res, $index) = @_; | 
| 182 |  |  |  |  |  |  |  | 
| 183 | 2 | 50 |  |  |  | 9 | if ($self->{Config}->{_tests}) { | 
| 184 | 2 |  |  |  |  | 20 | push @{$self->{tests}->{report}}, $self->_prepare_report($uid, $res, $index); | 
|  | 2 |  |  |  |  | 10 |  | 
| 185 |  |  |  |  |  |  | } | 
| 186 |  |  |  |  |  |  | else { | 
| 187 | 0 |  |  |  |  | 0 | my $report = $self->_prepare_report($uid, $res, $index); | 
| 188 | 0 |  |  |  |  | 0 | $self->_send_mail($report); | 
| 189 |  |  |  |  |  |  |  | 
| 190 | 0 | 0 |  |  |  | 0 | if ($self->{Config}->{verbose}) { | 
| 191 | 0 |  |  |  |  | 0 | print "Report for \"$uid: $res\" sent to '$self->{Config}->{mail}->{to}'\n"; | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  | } | 
| 194 |  |  |  |  |  |  | } | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | sub _put_data_file | 
| 197 |  |  |  |  |  |  | { | 
| 198 | 1 |  |  | 1 |  | 13 | my $self = shift; | 
| 199 |  |  |  |  |  |  |  | 
| 200 | 1 | 50 |  |  |  | 3 | eval { store($self->{data}, $self->{Config}->{data_file}) } | 
|  | 1 |  |  |  |  | 6 |  | 
| 201 |  |  |  |  |  |  | or croak "Cannot store to $self->{Config}->{data_file}: $!"; | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | sub _prepare_report | 
| 205 |  |  |  |  |  |  | { | 
| 206 | 2 |  |  | 2 |  | 4 | my $self = shift; | 
| 207 | 2 |  |  |  |  | 5 | my ($uid, $res, $index) = @_; | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 2 |  |  |  |  | 12 | my @fixed_fields = ($uid, $res) x 2; | 
| 210 |  |  |  |  |  |  | my @mapping = ( | 
| 211 |  |  |  |  |  |  | [ | 
| 212 |  |  |  |  |  |  | { map { | 
| 213 | 4 |  |  |  |  | 15 | $_ => shift @fixed_fields, | 
| 214 | 2 |  |  |  |  | 6 | } @{$self->{Config}->{_exclude_fields}} }, | 
| 215 |  |  |  |  |  |  | { map { | 
| 216 | 10 |  |  |  |  | 100 | $_ => $self->{stored}{$uid}{$res}->[$index]->{$_}, | 
| 217 | 2 |  |  |  |  | 9 | } grep !$self->{excluded}->{$_}, @{$self->{Config}->{_field_names}} } | 
| 218 |  |  |  |  |  |  | ], | 
| 219 |  |  |  |  |  |  | [ | 
| 220 |  |  |  |  |  |  | { map { | 
| 221 | 4 |  |  |  |  | 10 | $_ => shift @fixed_fields, | 
| 222 | 2 |  |  |  |  | 5 | } @{$self->{Config}->{_exclude_fields}} }, | 
| 223 |  |  |  |  |  |  | { map { | 
| 224 | 10 |  |  |  |  | 126 | $_ => $self->{data}{$uid}{$res}->[$index]->{$_}, | 
| 225 | 2 |  |  |  |  | 4 | } grep !$self->{excluded}->{$_}, @{$self->{Config}->{_field_names}} } | 
|  | 2 |  |  |  |  | 7 |  | 
| 226 |  |  |  |  |  |  | ], | 
| 227 |  |  |  |  |  |  | ); | 
| 228 |  |  |  |  |  |  |  | 
| 229 | 2 |  |  |  |  | 7 | my @values; | 
| 230 | 2 |  |  |  |  | 6 | foreach my $map (@mapping) { | 
| 231 | 4 |  |  |  |  | 5 | my @v; | 
| 232 | 4 |  |  |  |  | 9 | foreach my $entry (@$map) { | 
| 233 |  |  |  |  |  |  | push @v, map $entry->{$_}, sort { | 
| 234 | 8 |  |  |  |  | 37 | $self->{index}->{$a} <=> $self->{index}->{$b} | 
|  | 33 |  |  |  |  | 82 |  | 
| 235 |  |  |  |  |  |  | } keys %$entry; | 
| 236 |  |  |  |  |  |  | } | 
| 237 | 4 |  |  |  |  | 17 | push @values, [ @v ]; | 
| 238 |  |  |  |  |  |  | } | 
| 239 |  |  |  |  |  |  |  | 
| 240 | 2 |  |  |  |  | 6 | my %changed; | 
| 241 | 2 |  |  |  |  | 5 | foreach my $field (keys %{$mapping[0]->[1]}) { | 
|  | 2 |  |  |  |  | 7 |  | 
| 242 | 10 | 100 |  |  |  | 36 | if ($mapping[0]->[1]->{$field} != $mapping[1]->[1]->{$field}) { | 
| 243 | 2 |  |  |  |  | 8 | $changed{$self->{index}->{$field}} = true; | 
| 244 |  |  |  |  |  |  | } | 
| 245 |  |  |  |  |  |  | } | 
| 246 |  |  |  |  |  |  |  | 
| 247 | 2 |  |  |  |  | 6 | my $tmpl = \@values; | 
| 248 | 2 |  |  |  |  | 5 | my $report = $self->{template}; | 
| 249 |  |  |  |  |  |  |  | 
| 250 | 2 |  |  |  |  | 7 | local $1; | 
| 251 |  |  |  |  |  |  |  | 
| 252 | 2 |  |  |  |  | 15 | while (my ($var) = $report =~ /(\$\S+)/) { | 
| 253 | 28 | 100 |  |  |  | 339 | unless ($report =~ /\Q$var\E$/m) { | 
| 254 | 14 |  |  |  |  | 26 | my $len = length($var) - length do { eval $var }; | 
|  | 14 |  |  |  |  | 702 |  | 
| 255 | 14 |  |  |  |  | 376 | $report =~ s/(?<=\Q$var\E)/' ' x $len/e; | 
|  | 14 |  |  |  |  | 59 |  | 
| 256 |  |  |  |  |  |  | } | 
| 257 | 28 |  |  |  |  | 296 | $report =~ s/(\Q$var\E)/$1/ee; | 
|  | 28 |  |  |  |  | 1719 |  | 
| 258 |  |  |  |  |  |  | } | 
| 259 |  |  |  |  |  |  |  | 
| 260 | 2 |  |  |  |  | 13 | while (my ($pos) = $report =~ /\((\d+)\)/) { | 
| 261 | 14 | 100 |  |  |  | 49 | my $marked = $changed{$pos} ? '*' : ' '; | 
| 262 | 14 |  |  |  |  | 179 | $report =~ s/\($pos\)/  $marked/; | 
| 263 |  |  |  |  |  |  | } | 
| 264 |  |  |  |  |  |  |  | 
| 265 | 2 |  |  |  |  | 29 | return $report; | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | sub _send_mail | 
| 269 |  |  |  |  |  |  | { | 
| 270 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 271 | 0 |  |  |  |  |  | my ($report) = @_; | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | my %mail = ( | 
| 274 |  |  |  |  |  |  | From    => $self->{Config}->{mail}->{from}, | 
| 275 |  |  |  |  |  |  | To      => $self->{Config}->{mail}->{to}, | 
| 276 |  |  |  |  |  |  | Subject => $self->{Config}->{mail}->{subject}, | 
| 277 | 0 |  |  |  |  |  | Message => <<"EOT", | 
| 278 | 0 |  |  |  |  |  | ${\hostname} | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | $report | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | -- | 
| 283 | 0 |  |  |  |  |  | ${\basename($0)} v$VERSION - ${\scalar localtime} | 
|  | 0 |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | EOT | 
| 285 |  |  |  |  |  |  | ); | 
| 286 | 0 | 0 |  |  |  |  | sendmail(%mail) | 
| 287 |  |  |  |  |  |  | or croak "Cannot send mail: $Mail::Sendmail::error"; | 
| 288 |  |  |  |  |  |  | } | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | 1; | 
| 291 |  |  |  |  |  |  | __DATA__ |