11 |
use CGI qw/:standard *table/; |
use CGI qw/:standard *table/; |
12 |
use CGI::Carp qw(fatalsToBrowser); |
use CGI::Carp qw(fatalsToBrowser); |
13 |
use Data::Sorting qw(:arrays); |
use Data::Sorting qw(:arrays); |
14 |
|
use Time::ParseDate; |
15 |
|
use Time::Available; |
16 |
|
use Cache::FileCache; |
17 |
|
|
18 |
use Data::Dumper; |
use Data::Dumper; |
19 |
|
|
20 |
my $date_fmt = "%Y-%m-%d %H:%M:%S"; |
my $date_fmt = "%Y-%m-%d"; |
21 |
|
my $date_time_fmt = "%Y-%m-%d %H:%M:%S"; |
22 |
|
|
23 |
|
my $from_date = "now - 6 months"; |
24 |
|
my $to_date = "now"; |
25 |
|
|
26 |
# working days definition (1-7; mon=1) |
# working days definition (1-7; mon=1) |
27 |
my $wday_start = 1; |
my $dayMask = Time::Available::DAY_WEEKDAY; |
|
my $wday_end = 5; |
|
28 |
# working hours |
# working hours |
29 |
my $whours_start = "7:00"; |
my $from_time_interval = "7:00"; |
30 |
my $whours_end = "17:00"; |
my $to_time_interval = "17:00"; |
31 |
|
|
32 |
my $debug=0; |
my $debug=1; |
33 |
$debug++ if (grep(/-v/,@ARGV)); |
$debug++ if (grep(/-v/,@ARGV)); |
34 |
$debug++ if (grep(/-d/,@ARGV)); |
$debug++ if (grep(/-d/,@ARGV)); |
35 |
|
|
36 |
|
my %days = ( |
37 |
|
Time::Available::DAY_MONDAY=>'Mo', |
38 |
|
Time::Available::DAY_TUESDAY=>'Tu', |
39 |
|
Time::Available::DAY_WEDNESDAY=>'We', |
40 |
|
Time::Available::DAY_THURSDAY=>'Th', |
41 |
|
Time::Available::DAY_FRIDAY=>'Fr', |
42 |
|
Time::Available::DAY_SATURDAY=>'Sa', |
43 |
|
Time::Available::DAY_SUNDAY=>'Su' |
44 |
|
); |
45 |
|
|
46 |
my $q = new CGI; |
my $q = new CGI; |
47 |
|
|
49 |
my $rep_reset = $q->param('rep_reset') || 0; |
my $rep_reset = $q->param('rep_reset') || 0; |
50 |
my @sg_selected = $q->param('sg_filter'); |
my @sg_selected = $q->param('sg_filter'); |
51 |
|
|
52 |
|
# init misc sort parametars |
53 |
my @sort; |
my @sort; |
54 |
my $order; |
my $order; |
55 |
my %sort_param; |
my %sort_param; |
65 |
@sort = ( -compare => 'numeric', -order=>'reverse', $sort_param{'dsort'} ); |
@sort = ( -compare => 'numeric', -order=>'reverse', $sort_param{'dsort'} ); |
66 |
} |
} |
67 |
|
|
68 |
|
# make interval |
69 |
|
my $working_days; |
70 |
|
if ($q->param('use_time_limit')) { |
71 |
|
$dayMask=0; |
72 |
|
foreach my $dm ($q->param('day_interval')) { |
73 |
|
$dayMask |= $dm; |
74 |
|
} |
75 |
|
$working_days=new Time::Available(start=>$q->param('from_time_interval'),end=>$q->param('to_time_interval'),dayMask=>$dayMask); |
76 |
|
} |
77 |
|
|
78 |
|
# init cache and setup expriration |
79 |
|
my $cache = new Cache::FileCache({ default_expires_in => '10 min' }); |
80 |
|
|
81 |
# |
# |
82 |
# This option (activated via command switch -r) will reset failure duration |
# This option (activated via command switch -r) will reset failure duration |
83 |
# if repeated failure on same group/service happend. |
# if repeated failure on same group/service happend. |
89 |
# pretty format date |
# pretty format date |
90 |
sub d { |
sub d { |
91 |
my $utime = shift || return "?"; |
my $utime = shift || return "?"; |
92 |
return strftime($date_fmt,localtime($utime)); |
if ($debug) { |
93 |
|
return strftime($date_time_fmt." [%s]",localtime($utime)); |
94 |
|
} else { |
95 |
|
return strftime($date_time_fmt,localtime($utime)); |
96 |
|
} |
97 |
} |
} |
98 |
# pretty format duration |
# pretty format duration |
99 |
sub dur { |
sub dur { |
100 |
my $dur = shift || return "?"; |
my $dur = shift || return "0"; |
101 |
my $out = ""; |
my $out = ""; |
102 |
|
|
103 |
my $s = $dur; |
my $s = $dur; |
109 |
$s = $s % 60; |
$s = $s % 60; |
110 |
|
|
111 |
$out .= $d."d " if ($d > 0); |
$out .= $d."d " if ($d > 0); |
112 |
$out .= sprintf("%02d:%02d:%02d [%d]",$h,$m,$s, $dur); |
if ($debug) { |
113 |
# $out .= sprintf("%02d:%02d:%02d",$h,$m,$s); |
$out .= sprintf("%02d:%02d:%02d [%d]",$h,$m,$s, $dur); |
114 |
|
} else { |
115 |
|
$out .= sprintf("%02d:%02d:%02d",$h,$m,$s); |
116 |
|
} |
117 |
|
|
118 |
return $out; |
return $out; |
119 |
} |
} |
122 |
# |
# |
123 |
|
|
124 |
my %fail; |
my %fail; |
125 |
my %downtime; # total downtime |
my $downtime; # total downtime |
126 |
my %sg_filter; # filter for service/group |
my $sg_filter; # filter for service/group |
127 |
|
my $sg_count; # count number of downtimes |
128 |
my $log_file="/home/dpavlin/mon-log/sap.log"; |
|
129 |
|
my $log_file="/var/log/mon/sap.log"; |
130 |
|
|
131 |
|
my $data; |
132 |
|
|
133 |
|
# generate unique key for this data and options |
134 |
|
my $cache_key="monlog".join("",@sg_selected).$print_orphans.$rep_reset; |
135 |
|
|
136 |
|
if (! $debug) { |
137 |
|
$data = $cache->get( $cache_key ); |
138 |
|
$downtime = $cache->get("downtime $cache_key"); |
139 |
|
$sg_filter = $cache->get("sg_filter $cache_key"); |
140 |
|
$sg_count = $cache->get("sg_count $cache_key"); |
141 |
|
} |
142 |
|
|
143 |
my @data; |
if (!$data || !$downtime || !$sg_filter || !$sg_count) { |
144 |
|
|
145 |
open(LOG, $log_file) || die "$log_file: $!"; |
open(LOG, $log_file) || die "$log_file: $!"; |
146 |
|
|
147 |
while(<LOG>) { |
while(<LOG>) { |
148 |
chomp; |
chomp; |
149 |
if (/^(failure|up)\s+(\S+)\s+(\S+)\s+(\d+)\s+\(([^)]+)\)\s+(.+)$/) { |
if (/^(failure|up)\s+(\S+)\s+(\S+)\s+(\d+)\s+\(([^)]+)\)\s+(.+)$/) { |
150 |
my ($status,$group,$service,$utime,$date,$desc) = ($1,$2,$3,$4,$5,$6); |
my ($status,$group,$service,$utime,$date,$desc) = ($1,$2,$3,$4,$5,$6); |
151 |
my $id = "$group/$service"; |
my $id = "$group/$service"; |
152 |
if ($status eq "up" && defined($fail{$id})) { |
if ($status eq "up" && defined($fail{$id})) { |
153 |
if (grep(m;$group/$service;,@sg_selected)) { |
if (grep(m;$group/$service;,@sg_selected)) { |
154 |
push @data, { |
push @$data, { |
155 |
'sg'=>"$group/$service", |
'sg'=>"$group/$service", |
156 |
'from_time'=>$fail{$id}, |
'from'=>$fail{$id}, |
157 |
'to_time'=>$utime, |
'to'=>$utime, |
158 |
'dur_time'=>($utime - $fail{$id}), |
'desc'=>$desc }; |
159 |
'from'=>d($fail{$id}), |
$downtime->{"$group/$service"} += ($utime - $fail{$id}), |
160 |
'to'=>d($utime), |
$sg_count->{"$group/$service"}++; |
161 |
'dur'=>dur($utime - $fail{$id}), |
} |
162 |
'desc'=>$desc }; |
$sg_filter->{"$group/$service"}++; |
163 |
$downtime{"$group/$service"} += ($utime - $fail{$id}), |
delete $fail{$id}; |
164 |
} |
} elsif ($status eq "up") { |
165 |
$sg_filter{"$group/$service"}++; |
if ($print_orphans && grep(m;$group/$service;,@sg_selected)) { |
166 |
delete $fail{$id}; |
push @$data, { |
167 |
} elsif ($status eq "up") { |
'sg'=>"$group/$service", |
168 |
if ($print_orphans) { |
'from'=>-1, |
169 |
push @data, { |
'to'=>$utime, |
170 |
'sg'=>"$group/$service", |
'desc'=>$desc }; |
171 |
'to_time'=>$utime, |
$sg_count->{"$group/$service"}++; |
172 |
'from'=>'unknown', |
} |
173 |
'to'=>d($utime), |
delete $fail{$id}; |
174 |
'dur'=>'unknown', |
$sg_filter->{"$group/$service"}++; |
175 |
'desc'=>$desc }; |
} elsif (defined($fail{$id})) { |
176 |
} |
if ($rep_reset && grep(m;$group/$service;,@sg_selected)) { |
177 |
delete $fail{$id}; |
push @$data, { |
178 |
$sg_filter{"$group/$service"}++; |
'sg'=>"$group/$service", |
179 |
} elsif (defined($fail{$id})) { |
'from'=>$fail{$id}, |
180 |
if ($rep_reset) { |
'to'=>$utime, |
181 |
push @data, { |
'desc'=>'[failure again]'}; |
182 |
'sg'=>"$group/$service", |
$downtime->{"$group/$service"} += ($utime - $fail{$id}), |
183 |
'from_time'=>$fail{$id}, |
$fail{$id} = $utime; |
184 |
'to_time'=>$utime, |
$sg_count->{"$group/$service"}++; |
185 |
'dur_time'=>($utime - $fail{$id}), |
} |
186 |
'from'=>d($fail{$id}), |
$sg_filter->{"$group/$service"}++; |
187 |
'to'=>d($utime), |
} else { |
|
'dur'=>dur($utime - $fail{$id}), |
|
|
'desc'=>'[failure again]'}; |
|
|
$downtime{"$group/$service"} += ($utime - $fail{$id}), |
|
188 |
$fail{$id} = $utime; |
$fail{$id} = $utime; |
189 |
} |
} |
|
$sg_filter{"$group/$service"}++; |
|
|
} else { |
|
|
$fail{$id} = $utime; |
|
190 |
} |
} |
191 |
} |
} |
192 |
|
close(LOG); |
193 |
|
|
194 |
|
$cache->set($cache_key, $data); |
195 |
|
$cache->set("downtime $cache_key", $downtime); |
196 |
|
$cache->set("sg_filter $cache_key", $sg_filter); |
197 |
|
$cache->set("sg_count $cache_key", $sg_count); |
198 |
|
|
199 |
} |
} |
|
close(LOG); |
|
200 |
|
|
201 |
# generate output |
# generate output |
202 |
# |
# |
210 |
Tr(td( |
Tr(td( |
211 |
em("Show just service/group:"),br, |
em("Show just service/group:"),br, |
212 |
checkbox_group(-name=>'sg_filter', |
checkbox_group(-name=>'sg_filter', |
213 |
-values=>[keys %sg_filter], |
-values=>[keys %$sg_filter], |
214 |
-default=>[keys %sg_filter], |
-default=>[keys %$sg_filter], |
215 |
-linebreak=>'true', |
-linebreak=>'true', |
216 |
), |
), |
217 |
),td( |
),td( |
222 |
$q->checkbox(-name=>'print_orphans',-checked=>0, |
$q->checkbox(-name=>'print_orphans',-checked=>0, |
223 |
-label=>"show records which are not complete in this interval"), |
-label=>"show records which are not complete in this interval"), |
224 |
br, |
br, |
225 |
|
$q->checkbox(-name=>'use_date_limit',-checked=>1, |
226 |
|
-label=>"use date limit from:"), |
227 |
|
$q->textfield(-name=>'from_date',-size=>20,-default=>$from_date), |
228 |
|
" to: ", |
229 |
|
$q->textfield(-name=>'to_date',-size=>20,-default=>$to_date), |
230 |
|
small('Using <a href="http://search.cpan.org/search?mode=module&query=Time::ParseDate">Time::ParseDate</a>'), |
231 |
|
br, |
232 |
|
$q->checkbox(-name=>'use_time_limit',-checked=>1, |
233 |
|
-label=>"use time limit for each day:"), |
234 |
|
$q->textfield(-name=>'from_time_interval',-size=>8,-default=>$from_time_interval), |
235 |
|
" to: ", |
236 |
|
$q->textfield(-name=>'to_time_interval',-size=>8,-default=>$to_time_interval), |
237 |
|
br,"Days: ", |
238 |
|
$q->checkbox_group(-name=>'day_interval', |
239 |
|
-values=>[ sort { $a <=> $b } keys %days ], |
240 |
|
-labels=>\%days, |
241 |
|
-defaults=>[ |
242 |
|
Time::Available::DAY_MONDAY, |
243 |
|
Time::Available::DAY_TUESDAY, |
244 |
|
Time::Available::DAY_WEDNESDAY, |
245 |
|
Time::Available::DAY_THURSDAY, |
246 |
|
Time::Available::DAY_FRIDAY, |
247 |
|
] |
248 |
|
), |
249 |
$q->submit(-name=>'show',-value=>'Show report'), |
$q->submit(-name=>'show',-value=>'Show report'), |
250 |
)),end_table; |
)),end_table; |
251 |
|
|
263 |
} |
} |
264 |
} |
} |
265 |
|
|
266 |
print start_table({-border=>1,-cellspacing=>0,-cellpadding=>2,-width=>'100%'}), |
|
267 |
Tr( |
my ($from_time,$to_time,$from_html,$to_html); |
268 |
|
if ($q->param('use_date_limit')) { |
269 |
|
$from_time = parsedate($q->param('from_date'), UK=>1); |
270 |
|
$to_time = parsedate($q->param('to_date'), UK=>1); |
271 |
|
$from_html = strftime($date_fmt,localtime($from_time)); |
272 |
|
$to_html = strftime($date_fmt,localtime($to_time)); |
273 |
|
$from_html .= " [$from_time] " if ($debug); |
274 |
|
$to_html .= " [$to_time] " if ($debug); |
275 |
|
} |
276 |
|
|
277 |
|
# sort data |
278 |
|
# |
279 |
|
my @sorted = sorted_array(@$data, @sort); |
280 |
|
|
281 |
|
print "-- sort: ",Dumper(@sort)," (data: ".@$data." sorted: ".@sorted.") --\n",br,"-- dayMask: $dayMask --\n",br,"-- cache_key: $cache_key --\n",br if ($debug); |
282 |
|
|
283 |
|
print start_table({-border=>1,-cellspacing=>0,-cellpadding=>2,-width=>'100%'}); |
284 |
|
|
285 |
|
print Tr( |
286 |
th("group/service"), |
th("group/service"), |
287 |
th({-bgcolor=>'#f0f0f0'}, |
th({-bgcolor=>'#f0f0f0'},'<nobr>'. |
288 |
&sort_link($q,'from_time','u').' from '. |
&sort_link($q,'from','u').' from '. |
289 |
&sort_link($q,'from_time','d') |
&sort_link($q,'from','d').'</nobr>', |
290 |
|
br,$from_html |
291 |
), |
), |
292 |
th( |
th( '<nobr>'. |
293 |
&sort_link($q,'to_time','u').' to '. |
&sort_link($q,'to','u').' to '. |
294 |
&sort_link($q,'to_time','d') |
&sort_link($q,'to','d').'</nobr>', |
295 |
|
br,$to_html |
296 |
), |
), |
297 |
th({-bgcolor=>'#e0e0e0'}, |
th({-bgcolor=>'#e0e0e0'},'<nobr>'. |
298 |
&sort_link($q,'dur_time','u').' duration '. |
&sort_link($q,'dur','u').' duration '. |
299 |
&sort_link($q,'dur_time','d') |
&sort_link($q,'dur','d').'</nobr>' |
300 |
), |
), |
301 |
th("description") |
th("description") |
302 |
); |
) if (scalar @sorted > 0); |
|
|
|
|
my @sorted = sorted_array(@data, @sort); |
|
|
#my @sorted = @data; |
|
|
|
|
|
print "-- sort: ",Dumper(@sort)," (data: ".@data." sorted: ".@sorted.") --\n"; |
|
303 |
|
|
304 |
foreach my $row (@sorted) { |
foreach my $row (@sorted) { |
305 |
|
next if ($q->param('use_date_limit') && ($row->{from} < $from_time || $row->{to} > $to_time)); |
306 |
|
my ($from,$dur,$int) = ('unknown','unknown','unknown'); |
307 |
|
if ($row->{from} != -1 ) { |
308 |
|
$from = d($row->{from}); |
309 |
|
$dur = dur($row->{to} - $row->{from}); |
310 |
|
$int = dur($working_days->interval($row->{to},$row->{from})); |
311 |
|
} |
312 |
print Tr( |
print Tr( |
313 |
td({-align=>'left',-valign=>'center'},$row->{sg}), |
td({-align=>'left',-valign=>'center'},$row->{sg}), |
314 |
td({-align=>'right',-bgcolor=>'#f0f0f0'},$row->{from}), |
td({-align=>'right',-bgcolor=>'#f0f0f0'},$from), |
315 |
td({-align=>'right'},$row->{to}), |
td({-align=>'right'},d($row->{to})), |
316 |
td({-align=>'center',-bgcolor=>'#e0e0e0'},$row->{dur}), |
td({-align=>'center',-bgcolor=>'#e0e0e0'},$dur), |
317 |
|
td({-align=>'center',-bgcolor=>'#e0e0e0'},$int), |
318 |
td({-align=>'left'},$row->{desc}), |
td({-align=>'left'},$row->{desc}), |
319 |
),"\n"; |
),"\n"; |
320 |
} |
} |
322 |
# dump totals |
# dump totals |
323 |
# |
# |
324 |
|
|
325 |
foreach my $sg (keys %downtime) { |
foreach my $sg (keys %$downtime) { |
326 |
print Tr(th({-colspan=>3,-align=>'right'},"total for $sg:"), |
print Tr(td({-colspan=>3,-align=>'right'},"total for $sg:"), |
327 |
th({-bgcolor=>'#e0e0e0',-align=>'right'},dur($downtime{"$sg"})), |
td({-bgcolor=>'#e0e0e0',-align=>'right'},dur($downtime->{$sg})), |
328 |
th("")),"\n"; |
td(small("in ".$sg_count->{$sg}." failures"))),"\n"; |
329 |
} |
} |
330 |
|
|
331 |
print end_table, |
print end_table, |