| 1 | #!/usr/bin/perl |
|---|
| 2 | |
|---|
| 3 | # server-side maintenance script |
|---|
| 4 | # * can be run daily or multiple times per day |
|---|
| 5 | # * best if run (say) every 3 hours or 12 hours |
|---|
| 6 | # (typically no need to run more often - it will still generate per-hour data) |
|---|
| 7 | # |
|---|
| 8 | # - deletes old feedback |
|---|
| 9 | # - updates store of some common statistics (users, failures, duration, time-of-day) etc. |
|---|
| 10 | # - generates pretty graphs and html |
|---|
| 11 | # - generates graphs showing when components have missing data (...Max to add this part. :) ) |
|---|
| 12 | |
|---|
| 13 | |
|---|
| 14 | # settings |
|---|
| 15 | my $days_to_keep = 14; # keep 14 days |
|---|
| 16 | my $missing_interval = (5*60); # record missing data in 5 minute intervals |
|---|
| 17 | my $store_dir = $ENV{HOME} . "/whuffy.com/dev/stats"; # where we retrieve our feedback |
|---|
| 18 | my $graph_dir = $ENV{HOME} . "/whuffy.com/dev/feedback"; # where we store our graphs |
|---|
| 19 | my $template_dir = $ENV{HOME} . "/shepherd/util"; # where we store our templates |
|---|
| 20 | my $stylesheet = '/svgstyles.css'; |
|---|
| 21 | my $debug = 0; |
|---|
| 22 | |
|---|
| 23 | use strict; |
|---|
| 24 | use Data::Dumper; |
|---|
| 25 | use POSIX qw(strftime); |
|---|
| 26 | use SVG::TT::Graph::TimeSeries; |
|---|
| 27 | use SVG::TT::Graph::Bar; |
|---|
| 28 | use SVG::TT::Graph::Pie; |
|---|
| 29 | use Storable; |
|---|
| 30 | use HTML::Template; |
|---|
| 31 | use Time::Local; |
|---|
| 32 | |
|---|
| 33 | my $stats_file = $store_dir . "/stats.config"; |
|---|
| 34 | my $store = { }; |
|---|
| 35 | my $starttime = time; |
|---|
| 36 | |
|---|
| 37 | print "Running at:\n"; |
|---|
| 38 | print strftime "- %a %b %e %H:%M:%S %Y %z (system time)\n", localtime; |
|---|
| 39 | # Set timezone to Australian EST |
|---|
| 40 | $ENV{TZ}="EST-10EDT-11,M10.5.0/02:00,M3.5.0/02:00"; |
|---|
| 41 | strftime("%z", localtime); # This is necessary to force the change |
|---|
| 42 | print strftime "- %a %b %e %H:%M:%S %Y %z (Australian EST)\n", localtime; |
|---|
| 43 | |
|---|
| 44 | $debug = 1 if (grep ($_ eq '--debug', @ARGV)); |
|---|
| 45 | |
|---|
| 46 | &read_config; # read in our existing stats |
|---|
| 47 | &set_default_values; |
|---|
| 48 | |
|---|
| 49 | &rollover_statistics; # rotate statistics |
|---|
| 50 | |
|---|
| 51 | &delete_old_files; # delete old feedback files |
|---|
| 52 | |
|---|
| 53 | $store->{last_run} = $starttime; |
|---|
| 54 | &write_config; # need to do this in case we barf during |
|---|
| 55 | # stats generation -- we've renamed/deleted |
|---|
| 56 | # old stats files so keep config file in |
|---|
| 57 | # sync |
|---|
| 58 | |
|---|
| 59 | &parse_feedback; # parse any (new) feedback files |
|---|
| 60 | |
|---|
| 61 | &aggregate_stats; # use last 7 days to build last week, etc. |
|---|
| 62 | |
|---|
| 63 | if (grep ($_ eq '--dump', @ARGV)) |
|---|
| 64 | { |
|---|
| 65 | print Dumper($store); |
|---|
| 66 | exit; |
|---|
| 67 | } |
|---|
| 68 | |
|---|
| 69 | &generate_reports; # generate reports and graphs |
|---|
| 70 | |
|---|
| 71 | &write_config; # write out our updated stats |
|---|
| 72 | |
|---|
| 73 | print "Done.\n"; |
|---|
| 74 | |
|---|
| 75 | exit(0); |
|---|
| 76 | |
|---|
| 77 | ############################################################################### |
|---|
| 78 | |
|---|
| 79 | sub delete_old_files |
|---|
| 80 | { |
|---|
| 81 | chdir($store_dir) || die "can't chdir $store_dir: $!\n"; |
|---|
| 82 | opendir(DIR, $store_dir) || die "can't opendir $store_dir: $!\n"; |
|---|
| 83 | my @files = readdir(DIR); |
|---|
| 84 | closedir DIR; |
|---|
| 85 | |
|---|
| 86 | foreach my $file (@files) { |
|---|
| 87 | next if (!-f $file); # only work on normal files |
|---|
| 88 | next if ($file !~ /^\d+\.done$/); # only delete processed files |
|---|
| 89 | |
|---|
| 90 | if ((-M $file) > $days_to_keep) { |
|---|
| 91 | if ($debug) { |
|---|
| 92 | print "would have tried to delete $store_dir/$file if debug wasn't set\n"; |
|---|
| 93 | } else { |
|---|
| 94 | if ((unlink "$store_dir/$file") == 0) { |
|---|
| 95 | print "failed to delete $store_dir/$file: $!\n"; |
|---|
| 96 | } |
|---|
| 97 | } |
|---|
| 98 | } |
|---|
| 99 | } |
|---|
| 100 | } |
|---|
| 101 | |
|---|
| 102 | ############################################################################### |
|---|
| 103 | # read in our saved statistics |
|---|
| 104 | |
|---|
| 105 | sub read_config |
|---|
| 106 | { |
|---|
| 107 | if (-r $stats_file) { |
|---|
| 108 | $store = Storable::retrieve($stats_file); |
|---|
| 109 | } else { |
|---|
| 110 | printf "WARNING: no config file $stats_file - ok if this is the first time running!\n"; |
|---|
| 111 | |
|---|
| 112 | # try to write to it - if we can't then this will cause us to barf |
|---|
| 113 | &write_config; |
|---|
| 114 | } |
|---|
| 115 | } |
|---|
| 116 | |
|---|
| 117 | ############################################################################### |
|---|
| 118 | # write out our saved statistics |
|---|
| 119 | |
|---|
| 120 | sub write_config |
|---|
| 121 | { |
|---|
| 122 | Storable::store($store, $stats_file); |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | ############################################################################### |
|---|
| 126 | # define some initial settings to basic values if they weren't already set |
|---|
| 127 | # (have to do this _after_ reading config file, as Storable overwrites) |
|---|
| 128 | |
|---|
| 129 | sub set_default_values |
|---|
| 130 | { |
|---|
| 131 | $store->{last_run} = 0 if (!defined $store->{last_run}); |
|---|
| 132 | |
|---|
| 133 | $store->{num_hours} = 1 if (!defined $store->{num_hours}); |
|---|
| 134 | $store->{num_days} = 0 if (!defined $store->{num_days}); |
|---|
| 135 | $store->{num_weeks} = 0 if (!defined $store->{num_weeks}); |
|---|
| 136 | $store->{num_months} = 0 if (!defined $store->{num_months}); |
|---|
| 137 | |
|---|
| 138 | $store->{max_hours} = 72 if (!defined $store->{max_hours}); # keep per-hour stats for 72 hours (need min 24) |
|---|
| 139 | $store->{max_days} = 30 if (!defined $store->{max_days}); # keep per-day stats for 30 days (need min 30) |
|---|
| 140 | $store->{max_weeks} = 12 if (!defined $store->{max_weeks}); # keep per-week stats for 12 weeks |
|---|
| 141 | $store->{max_months} = 36 if (!defined $store->{max_months}); # keep per-month stats for 36 months |
|---|
| 142 | } |
|---|
| 143 | |
|---|
| 144 | ############################################################################### |
|---|
| 145 | # rollover 'daily' / 'weekly' / 'monthly' statistics if day/week/month is |
|---|
| 146 | # different from the last time we were run |
|---|
| 147 | |
|---|
| 148 | sub rollover_statistics |
|---|
| 149 | { |
|---|
| 150 | |
|---|
| 151 | return unless ($store->{last_run}); |
|---|
| 152 | |
|---|
| 153 | my @llast = localtime($store->{last_run}); |
|---|
| 154 | my @lnow = localtime($starttime); |
|---|
| 155 | |
|---|
| 156 | # Figure out the difference. This is a little trickier than you |
|---|
| 157 | # might think, since we want to know not how much time has |
|---|
| 158 | # elapsed, but the difference in month/week/day/hour number. |
|---|
| 159 | # For example, it may be 2 hours since we last ran, but if |
|---|
| 160 | # we last ran at 11PM and it's now 1AM, we are in a new day |
|---|
| 161 | # number. |
|---|
| 162 | |
|---|
| 163 | my $yrdiff = $lnow[5] - $llast[5]; |
|---|
| 164 | my $modiff = (12 * $yrdiff) + $lnow[4] - $llast[4]; |
|---|
| 165 | my $weekdiff = ($yrdiff * 52) + strftime("%U", @lnow) - strftime("%U", @llast); |
|---|
| 166 | my $daydiff = ($yrdiff * 365) + $lnow[7] - $llast[7]; |
|---|
| 167 | my $hrdiff = int($starttime / 3600) - int($store->{last_run} / 3600); |
|---|
| 168 | |
|---|
| 169 | &rollover_units('month', $modiff); |
|---|
| 170 | &rollover_units('week', $weekdiff); |
|---|
| 171 | &rollover_units('day', $daydiff); |
|---|
| 172 | &rollover_units('hour', $hrdiff); |
|---|
| 173 | } |
|---|
| 174 | |
|---|
| 175 | # Helper function called by rollover_stats; abstracts the code for |
|---|
| 176 | # rolling over months/weeks/days/hours. |
|---|
| 177 | sub rollover_units |
|---|
| 178 | { |
|---|
| 179 | my ($unit, $units_since) = @_; |
|---|
| 180 | |
|---|
| 181 | if ($units_since) |
|---|
| 182 | { |
|---|
| 183 | print "$units_since $unit since last stats rollover\n"; # if ($debug); |
|---|
| 184 | |
|---|
| 185 | my $num_units = 'num_' . $unit . 's'; |
|---|
| 186 | my $max_units = 'max_' . $unit . 's'; |
|---|
| 187 | my $per_unit = 'per_' . $unit; |
|---|
| 188 | for (my $i = ($store->{$num_units}-1); $i >= 0; $i--) |
|---|
| 189 | { |
|---|
| 190 | if ($i + $units_since < $store->{$max_units}) |
|---|
| 191 | { |
|---|
| 192 | $store->{$per_unit}->[$i + $units_since] = $store->{$per_unit}->[$i]; |
|---|
| 193 | print "store -> $per_unit -> [ $i + $units_since ] copied from $i\n"; |
|---|
| 194 | } |
|---|
| 195 | $store->{$per_unit}->[$i] = undef; |
|---|
| 196 | } |
|---|
| 197 | $store->{$num_units} += $units_since; |
|---|
| 198 | $store->{$num_units} = $store->{$max_units} - 1 if ($store->{$num_units} >= $store->{$max_units}); |
|---|
| 199 | } |
|---|
| 200 | } |
|---|
| 201 | |
|---|
| 202 | ############################################################################### |
|---|
| 203 | # aggregate stats: |
|---|
| 204 | # - last 24 hours = day 0 statistics, |
|---|
| 205 | # - last 7 days = week 0 statistics, |
|---|
| 206 | # - last 30 days = month 0 statistics |
|---|
| 207 | |
|---|
| 208 | sub aggregate_stats |
|---|
| 209 | { |
|---|
| 210 | # build day 0 based on last 24 hours |
|---|
| 211 | delete $store->{per_day}->[0]; |
|---|
| 212 | for (my $i = 0; $i < 24; $i++) { |
|---|
| 213 | next if (!defined $store->{per_hour}->[$i]); |
|---|
| 214 | $store->{per_day}->[0] = &merge_in_stats($store->{per_day}->[0], $store->{per_hour}->[$i]); |
|---|
| 215 | } |
|---|
| 216 | |
|---|
| 217 | # build week 0 based on last 7 days |
|---|
| 218 | delete $store->{per_week}->[0]; |
|---|
| 219 | for (my $i = 0; $i < 7; $i++) { |
|---|
| 220 | next if (!defined $store->{per_day}->[$i]); |
|---|
| 221 | $store->{per_week}->[0] = &merge_in_stats($store->{per_week}->[0], $store->{per_day}->[$i]); |
|---|
| 222 | } |
|---|
| 223 | |
|---|
| 224 | # build month 0 based on last 30 days |
|---|
| 225 | delete $store->{per_month}->[0]; |
|---|
| 226 | for (my $i = 0; $i < 30; $i++) { |
|---|
| 227 | next if (!defined $store->{per_day}->[$i]); |
|---|
| 228 | $store->{per_month}->[0] = &merge_in_stats($store->{per_month}->[0], $store->{per_day}->[$i]); |
|---|
| 229 | } |
|---|
| 230 | } |
|---|
| 231 | |
|---|
| 232 | ############################################################################### |
|---|
| 233 | |
|---|
| 234 | sub record_duration |
|---|
| 235 | { |
|---|
| 236 | my ($where, $suffix, $dur) = @_; |
|---|
| 237 | $suffix .= "_" if ($suffix ne ""); |
|---|
| 238 | |
|---|
| 239 | $where->{$suffix."duration"} += $dur; |
|---|
| 240 | $where->{$suffix."duration_count"} ++; |
|---|
| 241 | $where->{$suffix."duration_max"} = $dur if (!defined $where->{$suffix."duration_max"}); |
|---|
| 242 | $where->{$suffix."duration_max"} = $dur if ($dur > $where->{$suffix."duration_max"}); |
|---|
| 243 | $where->{$suffix."duration_min"} = $dur if (!defined $where->{$suffix."duration_min"}); |
|---|
| 244 | $where->{$suffix."duration_min"} = $dur if ($dur < $where->{$suffix."duration_min"}); |
|---|
| 245 | } |
|---|
| 246 | |
|---|
| 247 | ############################################################################### |
|---|
| 248 | # merge statistics in an intelligent manner |
|---|
| 249 | # - can recursively descend HASH lists |
|---|
| 250 | # - accumulates totals |
|---|
| 251 | # - fields with _min/_max suffix take the minimum/maximum value |
|---|
| 252 | |
|---|
| 253 | sub merge_in_stats |
|---|
| 254 | { |
|---|
| 255 | my ($to, $from) = @_; |
|---|
| 256 | |
|---|
| 257 | foreach my $field (keys %{($from)}) { |
|---|
| 258 | if (ref($from->{$field}) eq "HASH") { |
|---|
| 259 | $to->{$field} = &merge_in_stats($to->{$field}, $from->{$field}); |
|---|
| 260 | } elsif (defined $from->{$field}) { |
|---|
| 261 | if (!defined $to->{$field}) { |
|---|
| 262 | $to->{$field} = $from->{$field}; |
|---|
| 263 | } else { |
|---|
| 264 | # fields ending in _min and _max are special |
|---|
| 265 | if ($field =~ /_min$/) { |
|---|
| 266 | $to->{$field} = $from->{$field} if ($from->{$field} < $to->{$field}); |
|---|
| 267 | } elsif ($field =~ /_max$/) { |
|---|
| 268 | $to->{$field} = $from->{$field} if ($from->{$field} > $to->{$field}); |
|---|
| 269 | } else { |
|---|
| 270 | $to->{$field} += $from->{$field}; |
|---|
| 271 | } |
|---|
| 272 | } |
|---|
| 273 | } |
|---|
| 274 | } |
|---|
| 275 | return $to; |
|---|
| 276 | } |
|---|
| 277 | |
|---|
| 278 | ############################################################################### |
|---|
| 279 | |
|---|
| 280 | sub parse_feedback |
|---|
| 281 | { |
|---|
| 282 | chdir($store_dir) || die "can't chdir $store_dir: $!\n"; |
|---|
| 283 | opendir(DIR, $store_dir) || die "can't opendir $store_dir: $!\n"; |
|---|
| 284 | my @files = readdir(DIR); |
|---|
| 285 | closedir DIR; |
|---|
| 286 | |
|---|
| 287 | foreach my $file (@files) { |
|---|
| 288 | next if (!-f $file); # only work on normal files |
|---|
| 289 | next if ($file !~ /^\d+$/); # only look at non-processed files |
|---|
| 290 | next if ((stat(_))[9] < $store->{last_updated}); # only look at newly modified files |
|---|
| 291 | |
|---|
| 292 | my $file_modtime = (stat(_))[9]; |
|---|
| 293 | my $hr_num = int((time - $file_modtime) / 3600); |
|---|
| 294 | $hr_num = $store->{max_hours} if ($hr_num > $store->{max_hours}); |
|---|
| 295 | $hr_num = 0 if ($hr_num < 0); |
|---|
| 296 | |
|---|
| 297 | printf " - gathering statistics from $store_dir/$file ($file_modtime -> hr $hr_num)\n"; # if ($debug); |
|---|
| 298 | |
|---|
| 299 | my $parsed = 0; |
|---|
| 300 | $store->{per_hour}->[$hr_num]->{visits}++; # total # of visits seen |
|---|
| 301 | $store->{total}->{visits}++; |
|---|
| 302 | |
|---|
| 303 | # printf " : visits hr $hr_num: %d\n", $store->{per_hour}->[$hr_num]->{visits}; |
|---|
| 304 | |
|---|
| 305 | my $sid; |
|---|
| 306 | |
|---|
| 307 | open(F,"<$store_dir/$file") || die "could not open $store_dir/$file: $!\n"; |
|---|
| 308 | while(<F>) { |
|---|
| 309 | chop; |
|---|
| 310 | if ($_ =~ /^shepherd\t(SUCCESS|FAIL)\t([0-9\.]+)\t(\d+)\t(\d+)\t(\d+)\t(.*)$/) { |
|---|
| 311 | # shepherd status response |
|---|
| 312 | my ($status, $sysid, $user_starttime, $duration, $region, $components_used) = ($1,$2,$3,$4,$5,$6); |
|---|
| 313 | $parsed = 1; |
|---|
| 314 | $sid = $sysid; |
|---|
| 315 | $sid = undef if ($sid eq ''); |
|---|
| 316 | $status = lc($status); |
|---|
| 317 | |
|---|
| 318 | # |
|---|
| 319 | # usage statistics |
|---|
| 320 | # |
|---|
| 321 | |
|---|
| 322 | # success and failure by unique ID |
|---|
| 323 | if ($status eq 'success' or $status eq 'fail') |
|---|
| 324 | { |
|---|
| 325 | $store->{per_hour}->[$hr_num]->{$status.'_sysid_list'}->{$sid}++; |
|---|
| 326 | $store->{total}->{$status.'_sysid_list'}->{$sid}++; |
|---|
| 327 | } |
|---|
| 328 | |
|---|
| 329 | # shepherd's duration (total count, min, max, average from duration/visits) |
|---|
| 330 | if ($status eq 'success') |
|---|
| 331 | { |
|---|
| 332 | &record_duration($store->{per_hour}->[$hr_num], "", $duration); |
|---|
| 333 | &record_duration($store->{total}, "", $duration); |
|---|
| 334 | } |
|---|
| 335 | |
|---|
| 336 | # list of sysid's seen (used to derive # unique users) |
|---|
| 337 | $store->{per_hour}->[$hr_num]->{seen_sysid_list}->{$sysid}++; |
|---|
| 338 | $store->{total}->{seen_sysid_list}->{$sysid}++; |
|---|
| 339 | |
|---|
| 340 | # where user is |
|---|
| 341 | if ($region > 0 and $region < 200) |
|---|
| 342 | { |
|---|
| 343 | $store->{per_hour}->[$hr_num]->{region_list}->{$region}->{$sysid}++; |
|---|
| 344 | } |
|---|
| 345 | |
|---|
| 346 | # when shepherd was run, normalized to 15 minute buckets |
|---|
| 347 | my $when_run = strftime("%H:%M", localtime(($file_modtime-($file_modtime%(15*60))))); |
|---|
| 348 | $store->{per_hour}->[$hr_num]->{when_run_list}->{"$when_run"}++; |
|---|
| 349 | $store->{total}->{when_run_list}->{"$when_run"}++; |
|---|
| 350 | |
|---|
| 351 | # what components were used (and in what order?) |
|---|
| 352 | $components_used =~ s/\(.*?\)//g; # ignore version numbers |
|---|
| 353 | $store->{per_hour}->[$hr_num]->{components_used_order_list}->{$components_used}++; |
|---|
| 354 | |
|---|
| 355 | } elsif ($_ =~ /^(\S+)\tSUCCESS\t(\S+)\t(\S+)\t(\S+)\t(\S+)\t(\S+)/) { |
|---|
| 356 | # success line |
|---|
| 357 | my ($c, $retcode, $c_start, $c_dur, $c_ver, $c_grab) = ($1, $2, $3, $4, $5, $6); |
|---|
| 358 | |
|---|
| 359 | $c = $1 if ($c =~ /^(.*)-\d+$/); |
|---|
| 360 | my $g = "c_".$c."_success"; |
|---|
| 361 | $store->{per_hour}->[$hr_num]->{'c_'.$c.'_runs'}++; |
|---|
| 362 | $store->{total}->{'c_'.$c.'_runs'}++; |
|---|
| 363 | $store->{per_hour}->[$hr_num]->{$g."_sysid_list"}->{$sid}++ if ($sid); |
|---|
| 364 | |
|---|
| 365 | # duration (total count, min, max, average from duration/counter) |
|---|
| 366 | &record_duration($store->{per_hour}->[$hr_num], $g, $c_dur); |
|---|
| 367 | &record_duration($store->{total}, $g, $c_dur); |
|---|
| 368 | |
|---|
| 369 | # version count |
|---|
| 370 | $store->{per_hour}->[$hr_num]->{$g."_version_list"}->{$c_ver}++; |
|---|
| 371 | |
|---|
| 372 | # data download count |
|---|
| 373 | if ($c_grab) |
|---|
| 374 | { |
|---|
| 375 | $store->{per_hour}->[$hr_num]->{mins_grabbed_list}->{$c} += $c_grab; |
|---|
| 376 | } |
|---|
| 377 | } elsif ($_ =~ /^(\S+)\tFAIL\t(.+)\t(\S+)\t(\S+)\t(\S+)\t(\S+)/) { |
|---|
| 378 | # fail line |
|---|
| 379 | my ($c, $retcode, $c_start, $c_dur, $c_ver, $num_failures) = ($1, $2, $3, $4, $5, $6); |
|---|
| 380 | |
|---|
| 381 | $c = $1 if ($c =~ /^(.*)-\d+$/); |
|---|
| 382 | my $g = "c_".$c."_fail"; |
|---|
| 383 | $store->{per_hour}->[$hr_num]->{'c_'.$c.'_runs'}++; |
|---|
| 384 | $store->{total}->{'c_'.$c.'_runs'}++; |
|---|
| 385 | $store->{per_hour}->[$hr_num]->{$g."_sysid_list"}->{$sid}++ if ($sid); |
|---|
| 386 | if ($retcode =~ /(.*?):(.*)/) |
|---|
| 387 | { |
|---|
| 388 | my ($code, $msg) = ($1, $2); |
|---|
| 389 | my $shortsid = $1 if ($sid =~ /\d+\.(\d+)/); |
|---|
| 390 | unshift @{$store->{failcodes}->{$c}}, { timestamp => $starttime, sysid => $shortsid, file => $file, code => $code, msg => $msg }; |
|---|
| 391 | } |
|---|
| 392 | |
|---|
| 393 | # duration (total count, min, max, average from duration/counter) |
|---|
| 394 | &record_duration($store->{per_hour}->[$hr_num], $g, $c_dur); |
|---|
| 395 | &record_duration($store->{total}, $g, $c_dur); |
|---|
| 396 | |
|---|
| 397 | # version count |
|---|
| 398 | $store->{per_hour}->[$hr_num]->{$g."_version_list"}->{$c_ver}++; |
|---|
| 399 | } elsif ($_ =~ /^(\S+)\tMISSING_DATA\t(.*)$/) { |
|---|
| 400 | # component didn't return as much data as we expected it to |
|---|
| 401 | # record as: $store->{missing_data}->{$channel}->{$component_name}->{$epoch} = count |
|---|
| 402 | # note that old missing_data is aged out in read_config(). |
|---|
| 403 | |
|---|
| 404 | my ($c, $missing) = ($1, $2); |
|---|
| 405 | $c = $1 if ($c =~ /^(.*)-\d+$/); |
|---|
| 406 | my $g = ($c eq 'shepherd' ? '' : 'c_'.$c.'_') . 'missing_sysid_list'; |
|---|
| 407 | $store->{per_hour}->[$hr_num]->{$g}->{$sid}++; |
|---|
| 408 | $store->{total}->{$g}->{$sid}++; |
|---|
| 409 | unless ($c eq 'shepherd') |
|---|
| 410 | { |
|---|
| 411 | foreach my $chgroup (split(/\t/,$missing)) |
|---|
| 412 | { |
|---|
| 413 | my ($ch, $missing_times) = split(/:/,$chgroup); |
|---|
| 414 | foreach my $missing_interval (split(/,/,$missing_times)) |
|---|
| 415 | { |
|---|
| 416 | my ($miss_start, $miss_stop) = split(/\-/,$missing_interval); |
|---|
| 417 | $store->{missing_data}->{$ch}->{"$miss_start-$miss_stop"}->{$c}->{$sid} = $starttime; |
|---|
| 418 | } |
|---|
| 419 | } |
|---|
| 420 | } |
|---|
| 421 | } elsif ($_ =~ /^(\S+)\tstats\t(.*)$/) { |
|---|
| 422 | # component statistics line |
|---|
| 423 | |
|---|
| 424 | # November 11 2007: skipping STATS line because |
|---|
| 425 | # our data file (stats.config) is frigging huge |
|---|
| 426 | # and causing timeouts. Could turn this |
|---|
| 427 | # back on if we're more selective about the data |
|---|
| 428 | # we retain, or if we hunt down some of the more |
|---|
| 429 | # bloated areas. |
|---|
| 430 | next; |
|---|
| 431 | |
|---|
| 432 | # parse stats from line if in the form of: |
|---|
| 433 | # (delim1) (space) (number) (space) (varname) (delim) |
|---|
| 434 | # |
|---|
| 435 | # (delim1) has to be a comma or dot |
|---|
| 436 | # (number) must be a number - can be a float or integer |
|---|
| 437 | # (varname) must be either end-of-line or be followed by a . or , |
|---|
| 438 | # (delim2) must be either end-of-line or be a . or , |
|---|
| 439 | |
|---|
| 440 | # e.g. a STATS line of: |
|---|
| 441 | # sbsnews_website v0.03 completed in 1.23 seconds, 51887 bytes_fetched, 1 http_successful_requests, 236 progs |
|---|
| 442 | # will pick up the statistics: |
|---|
| 443 | # field 'bytes_fetched' with a value of 51887 |
|---|
| 444 | # field 'http_successful_requests' with a value of 1 |
|---|
| 445 | # field 'progs' with a value of 236 |
|---|
| 446 | |
|---|
| 447 | # skip if any '/' or '`' chars in name |
|---|
| 448 | next if (($1 =~ /\//) || ($1 =~ /`/)); |
|---|
| 449 | next if (($2 =~ /\//) || ($2 =~ /`/)); |
|---|
| 450 | |
|---|
| 451 | my $c = $1; |
|---|
| 452 | my $stats_string = $2 . "."; |
|---|
| 453 | |
|---|
| 454 | $c = $1 if ($c =~ /^(.*)-\d+$/); |
|---|
| 455 | |
|---|
| 456 | while ($stats_string ne "") { |
|---|
| 457 | # find delim1 |
|---|
| 458 | while ((substr($stats_string,0,1) !~ /[,\.]/) && (length($stats_string) > 0)) { |
|---|
| 459 | $stats_string = substr($stats_string,1,1000); |
|---|
| 460 | } |
|---|
| 461 | last if (length($stats_string) < 2); |
|---|
| 462 | |
|---|
| 463 | if ($stats_string =~ /^[\,\.]\s([0-9\.]+)\s(\S+)[\,\.]/) { |
|---|
| 464 | my $count = $1; |
|---|
| 465 | my $var = "cs.".$c.".".$2; |
|---|
| 466 | &record_duration($store->{per_hour}->[$hr_num],$var,$count); |
|---|
| 467 | &record_duration($store->{total},$var,$count); |
|---|
| 468 | } |
|---|
| 469 | |
|---|
| 470 | $stats_string = substr($stats_string,2,1000); |
|---|
| 471 | } |
|---|
| 472 | } else { |
|---|
| 473 | print "WARNING: unknown line in $file:\n$_\n"; |
|---|
| 474 | } |
|---|
| 475 | } |
|---|
| 476 | close(F); |
|---|
| 477 | |
|---|
| 478 | if (!$parsed) { |
|---|
| 479 | # anonymous, old version or no details |
|---|
| 480 | $store->{per_hour}->[$hr_num]->{visits_with_no_details}++; |
|---|
| 481 | $store->{total}->{visits_with_no_details}++; |
|---|
| 482 | } |
|---|
| 483 | |
|---|
| 484 | # rename processed files so we don't re-process them |
|---|
| 485 | rename("$file", "$file.done") if (!$debug); |
|---|
| 486 | } |
|---|
| 487 | } |
|---|
| 488 | |
|---|
| 489 | ############################################################################### |
|---|
| 490 | |
|---|
| 491 | sub numvar |
|---|
| 492 | { |
|---|
| 493 | my ($var,$field,$notdef) = @_; |
|---|
| 494 | $notdef = 0 if (!defined $notdef); |
|---|
| 495 | $field = "" if (!defined $field); |
|---|
| 496 | |
|---|
| 497 | return $notdef if (!defined $var); |
|---|
| 498 | |
|---|
| 499 | if ($field =~ /_list$/) { |
|---|
| 500 | # fields which end in _list are HASHes - return # of keys |
|---|
| 501 | my $count = keys %{$var}; |
|---|
| 502 | return $count; |
|---|
| 503 | } else { |
|---|
| 504 | return $var; |
|---|
| 505 | } |
|---|
| 506 | } |
|---|
| 507 | |
|---|
| 508 | ############################################################################### |
|---|
| 509 | |
|---|
| 510 | sub report_count_data |
|---|
| 511 | { |
|---|
| 512 | my ($template, $varname, $field, $reptype, $ignore_zero) = @_; |
|---|
| 513 | $reptype = "all" unless (defined $reptype); |
|---|
| 514 | |
|---|
| 515 | my @ret = (); |
|---|
| 516 | my @loop = ($reptype eq 'all' ? ( 'hour', 'day', 'week', 'month' ) : ( 'day', 'week' )); |
|---|
| 517 | |
|---|
| 518 | foreach my $timeslice (@loop) |
|---|
| 519 | { |
|---|
| 520 | next if ($ignore_zero and !numvar($store->{"per_$timeslice"}->[0]->{$field},$field)); |
|---|
| 521 | my %h; |
|---|
| 522 | $h{'TIMESLICE'} = $timeslice; |
|---|
| 523 | $h{'SEEN'} = numvar($store->{"per_$timeslice"}->[0]->{$field},$field); |
|---|
| 524 | $h{'PREVIOUS'} = numvar($store->{"per_$timeslice"}->[1]->{$field},$field); |
|---|
| 525 | |
|---|
| 526 | push (@ret, \%h); |
|---|
| 527 | } |
|---|
| 528 | |
|---|
| 529 | my $total = numvar($store->{total}->{$field},$field); |
|---|
| 530 | |
|---|
| 531 | if ($template) |
|---|
| 532 | { |
|---|
| 533 | $template->param($varname => \@ret); |
|---|
| 534 | $template->param($varname. '_TOTAL' => $total); |
|---|
| 535 | } |
|---|
| 536 | else |
|---|
| 537 | { |
|---|
| 538 | return (\@ret, $total); |
|---|
| 539 | } |
|---|
| 540 | } |
|---|
| 541 | |
|---|
| 542 | sub calc_success_rate |
|---|
| 543 | { |
|---|
| 544 | my ($c, $timeslice) = @_; |
|---|
| 545 | |
|---|
| 546 | my $s = numvar($store->{"per_$timeslice"}->[0]->{'c_'.$c.'_success_sysid_list'},'c_'.$c.'_success_sysid_list'); |
|---|
| 547 | my $f = numvar($store->{"per_$timeslice"}->[0]->{'c_'.$c.'_fail_sysid_list'},'c_'.$c.'_fail_sysid_list'); |
|---|
| 548 | |
|---|
| 549 | return '-' unless ($s or $f); |
|---|
| 550 | my $n = int(100*$s/($s+$f)); |
|---|
| 551 | my $color = ($n < 75 ? "red" : $n < 90 ? "orange" : "lightgreen"); |
|---|
| 552 | return "<font color=\"$color\">$n%</font>"; |
|---|
| 553 | } |
|---|
| 554 | |
|---|
| 555 | sub report_failure_msgs |
|---|
| 556 | { |
|---|
| 557 | my ($template, $c) = @_; |
|---|
| 558 | |
|---|
| 559 | return unless $store->{failcodes}->{$c}; |
|---|
| 560 | |
|---|
| 561 | my @msgs; |
|---|
| 562 | for (my $i = 0; $i < @{$store->{failcodes}->{$c}}; $i++) |
|---|
| 563 | { |
|---|
| 564 | my %h = %{$store->{failcodes}->{$c}[$i]}; |
|---|
| 565 | |
|---|
| 566 | # Remove any entries older than 1 week |
|---|
| 567 | if ($h{timestamp} + 604800 < $starttime) |
|---|
| 568 | { |
|---|
| 569 | splice(@{$store->{failcodes}->{$c}}, $i); |
|---|
| 570 | last; |
|---|
| 571 | } |
|---|
| 572 | $h{timestamp} = strftime("%a %d %b %H:%M", localtime($h{timestamp})); |
|---|
| 573 | $h{msg} = '(no msg)' unless $h{msg}; |
|---|
| 574 | push @msgs, { %h }; |
|---|
| 575 | } |
|---|
| 576 | |
|---|
| 577 | $template->param(FAILURE_MSGS => \@msgs); |
|---|
| 578 | } |
|---|
| 579 | |
|---|
| 580 | ############################################################################### |
|---|
| 581 | |
|---|
| 582 | sub sec2hms |
|---|
| 583 | { |
|---|
| 584 | my $sec = shift; |
|---|
| 585 | |
|---|
| 586 | return sprintf "%.1f", ($sec/60); |
|---|
| 587 | } |
|---|
| 588 | |
|---|
| 589 | ############################################################################### |
|---|
| 590 | |
|---|
| 591 | sub graph_runtime |
|---|
| 592 | { |
|---|
| 593 | my ($when, $startwhen, $endwhen, $title ) = @_; |
|---|
| 594 | |
|---|
| 595 | my $field = 'when_run_list'; |
|---|
| 596 | my $gname = $field."_".$when."_".$startwhen."_".$endwhen; |
|---|
| 597 | |
|---|
| 598 | # gather up data |
|---|
| 599 | my %data; |
|---|
| 600 | for (my $i = $startwhen; $i < $endwhen; $i++) { |
|---|
| 601 | if (defined $store->{$when}->[$i]->{$field}) { |
|---|
| 602 | foreach my $k (keys %{($store->{$when}->[$i]->{$field})}) { |
|---|
| 603 | $data{"2000-1-1 $k"} += $store->{$when}->[$i]->{$field}->{$k}; |
|---|
| 604 | } |
|---|
| 605 | } |
|---|
| 606 | } |
|---|
| 607 | |
|---|
| 608 | # fill in empty timeslots with zeros |
|---|
| 609 | for (my $i = 0; $i < 24; $i++) |
|---|
| 610 | { |
|---|
| 611 | for (my $j = 0; $j < 60; $j += 15) |
|---|
| 612 | { |
|---|
| 613 | my $key = sprintf "2000-1-1 %02d:%02d", $i, $j; |
|---|
| 614 | $data{$key} = 0 unless defined $data{$key}; |
|---|
| 615 | } |
|---|
| 616 | } |
|---|
| 617 | |
|---|
| 618 | # sort it into key order |
|---|
| 619 | my ($min, $max, @sorted_data); |
|---|
| 620 | foreach my $k (sort keys %data) |
|---|
| 621 | { |
|---|
| 622 | push(@sorted_data, $k, $data{$k}); |
|---|
| 623 | |
|---|
| 624 | $min = $data{$k} if (!defined $min or $data{$k} < $min); |
|---|
| 625 | $max = $data{$k} if (!defined $max or $data{$k} > $max); |
|---|
| 626 | } |
|---|
| 627 | |
|---|
| 628 | # work out our scaling |
|---|
| 629 | my $scalediv = ($max - $min) / 3; |
|---|
| 630 | $scalediv = $max if ($scalediv < 1); |
|---|
| 631 | |
|---|
| 632 | # only graph if we have some data |
|---|
| 633 | return if ($min == 0 and $max == 0); |
|---|
| 634 | |
|---|
| 635 | return graph_one_count_data($gname, $title, '%l:%M %p', '2 hours', [ \@sorted_data ], 0, 505 ); |
|---|
| 636 | } |
|---|
| 637 | |
|---|
| 638 | sub gather_region_data |
|---|
| 639 | { |
|---|
| 640 | my ($when, $startwhen, $endwhen, $long_ids) = @_; |
|---|
| 641 | |
|---|
| 642 | my %short_regions = ( |
|---|
| 643 | 126 => "ACT", 73 => "Sydney", 184 => "NSW", 66 => "NSW", |
|---|
| 644 | 67 => "NSW", 63 => "NSW", 69 => "NSW", 71 => "NSW", |
|---|
| 645 | 106 => "NSW", 74 => "Darwin", 108 => "NT", 75 => "Brisbane", |
|---|
| 646 | 78 => "QLD", 79 => "QLD", 114 => "QLD", 81 => "Adelaide", |
|---|
| 647 | 82 => "SA", 83 => "SA", 85 => "SA", 86 => "SA", 107 => "SA", |
|---|
| 648 | 88 => "Tasmania", 94 => "Melbourne", 93 => "VIC", |
|---|
| 649 | 90 => "VIC", 95 => "VIC", 98 => "VIC", 101 => "Perth", |
|---|
| 650 | 102 => "WA" ); |
|---|
| 651 | my %long_regions = ( |
|---|
| 652 | 126 => "ACT", 73 => "Sydney", 184 => "Newcastle", |
|---|
| 653 | 66 => "Central Coast NSW", 67 => "Griffith", 63 => "Broken Hill", |
|---|
| 654 | 69 => "Northern NSW", 71 => "Southern NSW", 106 => "NSW Remote", |
|---|
| 655 | 74 => "Darwin", 108 => "NT Remote", 75 => "Brisbane", |
|---|
| 656 | 78 => "Gold Coast", 79 => "QLD Regional", 114 => "QLD Remote", |
|---|
| 657 | 81 => "Adelaide", 82 => "Renmark", 83 => "Riverland", |
|---|
| 658 | 85 => "South East SA", 86 => "Spencer Gulf", |
|---|
| 659 | 107 => "SA Remote", 88 => "Tasmania", 94 => "Melbourne", |
|---|
| 660 | 93 => "Geelong", 90 => "Eastern VIC", 95 => "Mildura/Sunraysia", |
|---|
| 661 | 98 => "Western VIC", 101 => "Perth", 102 => "WA: Regional"); |
|---|
| 662 | |
|---|
| 663 | my $data; |
|---|
| 664 | for (my $i = $startwhen; $i < $endwhen; $i++) |
|---|
| 665 | { |
|---|
| 666 | my $h = $store->{$when}->[$i]->{'region_list'}; |
|---|
| 667 | if ($h) |
|---|
| 668 | { |
|---|
| 669 | foreach my $k (keys %$h) |
|---|
| 670 | { |
|---|
| 671 | my $label = ($long_ids ? $long_regions{$k} : $short_regions{$k}); |
|---|
| 672 | foreach my $sysid (keys %{$h->{$k}}) |
|---|
| 673 | { |
|---|
| 674 | push (@{$data->{$label}}, $sysid) unless (grep($_ eq $sysid, @{$data->{$label}})); |
|---|
| 675 | } |
|---|
| 676 | } |
|---|
| 677 | } |
|---|
| 678 | } |
|---|
| 679 | |
|---|
| 680 | # convert to # of unique sysids |
|---|
| 681 | foreach (keys %$data) |
|---|
| 682 | { |
|---|
| 683 | $data->{$_} = scalar(@{$data->{$_}}); |
|---|
| 684 | } |
|---|
| 685 | return $data; |
|---|
| 686 | } |
|---|
| 687 | |
|---|
| 688 | sub graph_regions |
|---|
| 689 | { |
|---|
| 690 | my ($when, $startwhen, $endwhen, $title) = @_; |
|---|
| 691 | |
|---|
| 692 | my $gname = "region_list_".$when."_".$startwhen."_".$endwhen; |
|---|
| 693 | |
|---|
| 694 | my $rdata = &gather_region_data($when, $startwhen, $endwhen, 0); |
|---|
| 695 | return unless ($rdata); |
|---|
| 696 | |
|---|
| 697 | my $gheight = 220; |
|---|
| 698 | my $gwidth = 360; |
|---|
| 699 | print "Generating pie graph $gname ..\n" if ($debug); |
|---|
| 700 | my $graph = SVG::TT::Graph::Pie->new({ |
|---|
| 701 | 'show_graph_title' => 1, |
|---|
| 702 | 'graph_title' => $title, |
|---|
| 703 | 'height' => $gheight, |
|---|
| 704 | 'width' => $gwidth, |
|---|
| 705 | 'show_shadow' => 1, |
|---|
| 706 | 'shadow_offset' => 5, |
|---|
| 707 | 'shadow_size' => 2, |
|---|
| 708 | 'expanded' => 0, |
|---|
| 709 | 'show_percent' => 0, |
|---|
| 710 | 'show_data_labels' => 1, |
|---|
| 711 | 'fields' => [ keys %$rdata ], |
|---|
| 712 | 'style_sheet' => $stylesheet |
|---|
| 713 | }); |
|---|
| 714 | $graph->add_data({ |
|---|
| 715 | 'data' => [ values %$rdata ], |
|---|
| 716 | }); |
|---|
| 717 | |
|---|
| 718 | open(SVG,">$graph_dir/$gname".".svg") || die "could not create $graph_dir/$gname".".svg: $!\n"; |
|---|
| 719 | print SVG $graph->burn(); |
|---|
| 720 | close(SVG); |
|---|
| 721 | |
|---|
| 722 | return sprintf "<object type='image/svg+xml' data='%s.svg' height='%d' width='%d'></object>\n",$gname,$gheight,$gwidth; |
|---|
| 723 | } |
|---|
| 724 | |
|---|
| 725 | # Pretty nasty stuff in here... sorry! |
|---|
| 726 | sub graph_missing_data |
|---|
| 727 | { |
|---|
| 728 | my ($template, $varname, $component) = @_; |
|---|
| 729 | |
|---|
| 730 | my $day = 60*60*24; # 1 day |
|---|
| 731 | my $interval = 60 * 60; # 1 hour |
|---|
| 732 | |
|---|
| 733 | # Figure out what "start of today" to "end of the 7th day from now" |
|---|
| 734 | # in localtime is in epoch seconds |
|---|
| 735 | my @now = localtime(); |
|---|
| 736 | @now = (0, 0, 0, splice(@now, 3)); # reset to start of today |
|---|
| 737 | my $start = timelocal(@now); |
|---|
| 738 | my $end = $start + (7 * $day); |
|---|
| 739 | |
|---|
| 740 | my $data; # missing by channel |
|---|
| 741 | my $gdata; # missing by grabber |
|---|
| 742 | foreach my $ch (keys %{$store->{missing_data}}) |
|---|
| 743 | { |
|---|
| 744 | foreach my $slice (keys %{$store->{missing_data}->{$ch}}) |
|---|
| 745 | { |
|---|
| 746 | my ($s, $f) = split(/-/,$slice); |
|---|
| 747 | $s = $s - $s%(60*60); |
|---|
| 748 | |
|---|
| 749 | # We treat each unique combination of grabber and sysid ("csid") as |
|---|
| 750 | # 1 unit. That is, if 5 unique users all have a yahoo7widget hole, that's |
|---|
| 751 | # 5, and so is 1 unique user who has 5 different grabbers with a hole. |
|---|
| 752 | my @csids; |
|---|
| 753 | foreach my $c (keys %{$store->{missing_data}->{$ch}->{$slice}}) |
|---|
| 754 | { |
|---|
| 755 | next if ($component and $component ne $c); |
|---|
| 756 | foreach my $sysid (keys %{$store->{missing_data}->{$ch}->{$slice}->{$c}}) |
|---|
| 757 | { |
|---|
| 758 | # Age out missing_data received more than 24 hours ago, since it's |
|---|
| 759 | # probably stale; we want to allow newer information to shine through |
|---|
| 760 | if ($store->{missing_data}->{$ch}->{$slice}->{$c}->{$sysid} + $day < $starttime) |
|---|
| 761 | { |
|---|
| 762 | delete $store->{missing_data}->{$ch}->{$slice}->{$c}->{$sysid}; |
|---|
| 763 | next; |
|---|
| 764 | } |
|---|
| 765 | my $csid = $c.'_'.$sysid; |
|---|
| 766 | push @csids, $csid unless (grep ($_ eq $csid, @csids)); |
|---|
| 767 | } |
|---|
| 768 | } |
|---|
| 769 | for (my $i = $s; $i < $f; $i += $interval) |
|---|
| 770 | { |
|---|
| 771 | next if ($i < $start or $i > $end); |
|---|
| 772 | my $n = ((($i - $start) / $interval) * 2) + 1; |
|---|
| 773 | |
|---|
| 774 | foreach my $csid (@csids) |
|---|
| 775 | { |
|---|
| 776 | push @{$data->{$ch}->[$n]}, $csid unless (grep ($_ eq $csid, @{$data->{$ch}->[$n]})); |
|---|
| 777 | if (!$component and $csid =~ /(.+)_(.+)/) |
|---|
| 778 | { |
|---|
| 779 | my $c = $1; |
|---|
| 780 | my $sid = $2; |
|---|
| 781 | push @{$gdata->{$c}->[$n]}, $sid unless (grep $_ eq $sid, @{$gdata->{$c}->[$n]}); |
|---|
| 782 | } |
|---|
| 783 | } |
|---|
| 784 | } |
|---|
| 785 | } |
|---|
| 786 | } |
|---|
| 787 | |
|---|
| 788 | my @graphs; |
|---|
| 789 | &pad_missing_data_hash($data, $start, $end, $interval); |
|---|
| 790 | my $gname = ($component ? 'c_'.$component.'_' : '') . 'missing_by_channel'; |
|---|
| 791 | my $gtitle = 'Missing ' . ($component ? "$component " : '') . 'Data by Channel'; |
|---|
| 792 | push @graphs, { GRAPH => &graph_one_count_data($gname, $gtitle, "%a %H:%M", '12 hours', [ values %$data ], 220, 1010, 0, [ keys %$data ]) }; |
|---|
| 793 | |
|---|
| 794 | unless ($component) |
|---|
| 795 | { |
|---|
| 796 | &pad_missing_data_hash($gdata, $start, $end, $interval); |
|---|
| 797 | push @graphs, { GRAPH => &graph_one_count_data('missing_by_grabber', 'Missing Data by Grabber', "%a %H:%M", '12 hours', [ values %$gdata ], 220, 1010, 0, [ keys %$gdata ]) }; |
|---|
| 798 | } |
|---|
| 799 | |
|---|
| 800 | $template->param($varname => \@graphs); |
|---|
| 801 | } |
|---|
| 802 | |
|---|
| 803 | # Pad out the hash of 'missing data' with timestamps and 0s to make |
|---|
| 804 | # a timeseries array suitable for graphing |
|---|
| 805 | sub pad_missing_data_hash |
|---|
| 806 | { |
|---|
| 807 | my ($h, $start, $end, $interval) = @_; |
|---|
| 808 | |
|---|
| 809 | foreach my $key (keys %$h) |
|---|
| 810 | { |
|---|
| 811 | for (my $n = 0; $n <= @{$h->{$key}}; $n += 2) |
|---|
| 812 | { |
|---|
| 813 | my $t = $start + ($n / 2 * $interval); |
|---|
| 814 | last if ($t > $end); |
|---|
| 815 | $h->{$key}->[$n] = strftime("%Y-%m-%d %H:%M:%S", localtime($t)); |
|---|
| 816 | if (!defined $h->{$key}->[$n+1]) |
|---|
| 817 | { |
|---|
| 818 | $h->{$key}->[$n+1] = 0; |
|---|
| 819 | } |
|---|
| 820 | else |
|---|
| 821 | { |
|---|
| 822 | $h->{$key}->[$n+1] = scalar(@{$h->{$key}->[$n+1]}); |
|---|
| 823 | } |
|---|
| 824 | } |
|---|
| 825 | } |
|---|
| 826 | } |
|---|
| 827 | |
|---|
| 828 | ############################################################################### |
|---|
| 829 | |
|---|
| 830 | sub graph_mins_grabbed |
|---|
| 831 | { |
|---|
| 832 | my ($when, $startwhen, $endwhen, $title, $gheight, $gwidth) = @_; |
|---|
| 833 | |
|---|
| 834 | my $gname = "mins_grabbed_".$when."_".$startwhen."_".$endwhen; |
|---|
| 835 | |
|---|
| 836 | # gather up data |
|---|
| 837 | my %data; |
|---|
| 838 | for (my $i = $startwhen; $i < $endwhen; $i++) |
|---|
| 839 | { |
|---|
| 840 | foreach my $c (keys %{$store->{$when}->[$i]->{mins_grabbed_list}}) |
|---|
| 841 | { |
|---|
| 842 | $data{$c} += $store->{$when}->[$i]->{mins_grabbed_list}->{$c}; |
|---|
| 843 | } |
|---|
| 844 | } |
|---|
| 845 | |
|---|
| 846 | # sort it into key order |
|---|
| 847 | my $min, my $max; |
|---|
| 848 | my @sorted_keys, my @sorted_data; |
|---|
| 849 | foreach my $k (sort { $data{$b} <=> $data{$a} } keys %data) |
|---|
| 850 | { |
|---|
| 851 | push(@sorted_keys,$k); |
|---|
| 852 | push(@sorted_data, int($data{$k}/60)); |
|---|
| 853 | |
|---|
| 854 | $min = $data{$k} if (!defined $min); |
|---|
| 855 | $max = $data{$k} if (!defined $max); |
|---|
| 856 | $min = $data{$k} if ($data{$k} < $min); |
|---|
| 857 | $max = $data{$k} if ($data{$k} > $max); |
|---|
| 858 | } |
|---|
| 859 | |
|---|
| 860 | # work out our scaling |
|---|
| 861 | my $scalediv = ($max - $min) / 3; |
|---|
| 862 | $scalediv = $max if ($scalediv < 1); |
|---|
| 863 | |
|---|
| 864 | # only graph if we have some data |
|---|
| 865 | return if (($min == 0) && ($max == 0)); |
|---|
| 866 | |
|---|
| 867 | print "Generating bar graph $gname ..\n" if ($debug); |
|---|
| 868 | my $graph = SVG::TT::Graph::Bar->new({ |
|---|
| 869 | 'show_graph_title' => 1, |
|---|
| 870 | 'graph_title' => $title, |
|---|
| 871 | 'show_data_values' => 1, |
|---|
| 872 | 'height' => $gheight, |
|---|
| 873 | 'width' => $gwidth, |
|---|
| 874 | 'stagger_x_labels' => 1, |
|---|
| 875 | 'show_x_labels' => 1, |
|---|
| 876 | 'show_y_labels' => 0, |
|---|
| 877 | 'show_data_values' => 1, |
|---|
| 878 | 'scale_divisions' => $scalediv, |
|---|
| 879 | 'style_sheet' => $stylesheet, |
|---|
| 880 | 'fields' => \@sorted_keys, |
|---|
| 881 | }); |
|---|
| 882 | $graph->add_data({ |
|---|
| 883 | 'data' => \@sorted_data, |
|---|
| 884 | }); |
|---|
| 885 | |
|---|
| 886 | open(SVG,">$graph_dir/$gname".".svg") || die "could not create $graph_dir/$gname".".svg: $!\n"; |
|---|
| 887 | print SVG $graph->burn(); |
|---|
| 888 | close(SVG); |
|---|
| 889 | |
|---|
| 890 | return sprintf "<object type='image/svg+xml' data='%s.svg' height='%d' width='%d'></object>\n",$gname,$gheight,$gwidth; |
|---|
| 891 | } |
|---|
| 892 | |
|---|
| 893 | ############################################################################### |
|---|
| 894 | |
|---|
| 895 | sub graph_one_count_data |
|---|
| 896 | { |
|---|
| 897 | my ($gname, $title, $time_format, $timediv, $data, $gheight, $gwidth, $nofill, $keys) = @_; |
|---|
| 898 | |
|---|
| 899 | $gheight ||= 120; |
|---|
| 900 | $gwidth ||= 250; |
|---|
| 901 | $nofill = 0 unless $nofill; |
|---|
| 902 | |
|---|
| 903 | print "Generating graph $gname ($title)...\n" if ($debug); |
|---|
| 904 | # work out scale.. |
|---|
| 905 | my $min, my $max; |
|---|
| 906 | foreach my $dset (@$data) { |
|---|
| 907 | my $valnum = 0; |
|---|
| 908 | foreach my $val (@$dset) { |
|---|
| 909 | $valnum++; |
|---|
| 910 | next if ($valnum % 2 == 1); |
|---|
| 911 | |
|---|
| 912 | $min = $val if (!defined $min); |
|---|
| 913 | $max = $val if (!defined $max); |
|---|
| 914 | $min = $val if ($val < $min); |
|---|
| 915 | $max = $val if ($val > $max); |
|---|
| 916 | } |
|---|
| 917 | } |
|---|
| 918 | my $scalediv = ($max-$min)/3; |
|---|
| 919 | $scalediv = 1 if ($scalediv < 1); |
|---|
| 920 | |
|---|
| 921 | return if (($min == 0) && ($max == 0)); |
|---|
| 922 | |
|---|
| 923 | my $graph = SVG::TT::Graph::TimeSeries->new({ |
|---|
| 924 | 'show_graph_title' => 1, |
|---|
| 925 | 'graph_title' => $title, |
|---|
| 926 | 'height' => $gheight, |
|---|
| 927 | 'width' => $gwidth, |
|---|
| 928 | 'stagger_x_labels' => 1, |
|---|
| 929 | 'area_fill' => !$nofill, |
|---|
| 930 | 'rollover_values' => 1, |
|---|
| 931 | 'show_data_values' => 1, |
|---|
| 932 | 'x_label_format' => $time_format, |
|---|
| 933 | 'scale_divisions' => $scalediv, |
|---|
| 934 | 'timescale_divisions' => $timediv, |
|---|
| 935 | 'key' => ($keys ? 1 : 0), |
|---|
| 936 | 'key_position' => 'bottom', |
|---|
| 937 | 'style_sheet' => $stylesheet |
|---|
| 938 | }); |
|---|
| 939 | |
|---|
| 940 | foreach my $dset (@$data) { |
|---|
| 941 | my $key = shift @$keys; |
|---|
| 942 | $graph->add_data({ |
|---|
| 943 | 'data' => \@$dset, |
|---|
| 944 | 'title' => $key |
|---|
| 945 | }); |
|---|
| 946 | } |
|---|
| 947 | |
|---|
| 948 | open(SVG,">$graph_dir/$gname".".svg") || die "could not create $graph_dir/$gname".".svg: $!\n"; |
|---|
| 949 | print SVG $graph->burn(); |
|---|
| 950 | close(SVG); |
|---|
| 951 | |
|---|
| 952 | return sprintf "<object type='image/svg+xml' data='%s.svg' height='%d' width='%d'></object>\n",$gname,$gheight,$gwidth; |
|---|
| 953 | } |
|---|
| 954 | |
|---|
| 955 | ############################################################################### |
|---|
| 956 | # produces sets of graphs, from requests like "day week 6month". |
|---|
| 957 | |
|---|
| 958 | sub graph_count_data |
|---|
| 959 | { |
|---|
| 960 | my ($template, $varname, $request, $gheight, $gwidth, $show_key, @fields) = @_; |
|---|
| 961 | |
|---|
| 962 | my @requests = split(/ /, $request); |
|---|
| 963 | my @graphs; |
|---|
| 964 | |
|---|
| 965 | my $tdata = { |
|---|
| 966 | hour => { n => 3600, tformat => "%a %H:%M", abbrev => "hr" }, |
|---|
| 967 | day => { n => 86400, tformat => "%a %e %b", abbrev => "day", section => "24hour" }, |
|---|
| 968 | week => { n => 604800, tformat => "%e %b %y", abbrev => "wk", section => "7day" }, |
|---|
| 969 | month =>{ n => 2592000, tformat => "%b %y", abbrev => "mn" }, |
|---|
| 970 | year => { section => "12month" } |
|---|
| 971 | }; |
|---|
| 972 | |
|---|
| 973 | foreach my $r (@requests) |
|---|
| 974 | { |
|---|
| 975 | my $title; |
|---|
| 976 | if ($tdata->{$r}) |
|---|
| 977 | { |
|---|
| 978 | $title = "Last $r"; |
|---|
| 979 | $r = $tdata->{$r}->{section}; |
|---|
| 980 | } |
|---|
| 981 | next unless ($r =~ /(\d+)(\w+)/); |
|---|
| 982 | my $max = $1; |
|---|
| 983 | my $timeslice = $2; |
|---|
| 984 | $title = "Last $max $timeslice" . 's' unless ($title); |
|---|
| 985 | |
|---|
| 986 | my $fieldnum = 0, my $d, my $fielddesc = "", my @keys; |
|---|
| 987 | foreach my $field (@fields) |
|---|
| 988 | { |
|---|
| 989 | my $anydata = 0; |
|---|
| 990 | my @data; |
|---|
| 991 | for (my $i = ($max-1); $i >= 0; $i--) |
|---|
| 992 | { |
|---|
| 993 | my $val = numvar($store->{"per_$timeslice"}->[$i]->{$field},$field,0); |
|---|
| 994 | if ($field =~ /duration/) |
|---|
| 995 | { |
|---|
| 996 | $val /= 60; # convert to minutes |
|---|
| 997 | if ($field =~ /duration$/) # average |
|---|
| 998 | { |
|---|
| 999 | $val /= numvar($store->{"per_$timeslice"}->[$i]->{duration_count},$field,1); |
|---|
| 1000 | } |
|---|
| 1001 | $val = int($val + 0.49); |
|---|
| 1002 | } |
|---|
| 1003 | my $time_offset = $starttime - ($i * $tdata->{$timeslice}->{n}); |
|---|
| 1004 | push(@data, strftime("%Y-%m-%d %H:%M:%S", localtime($time_offset)), $val); |
|---|
| 1005 | $anydata = 1 if ($val); |
|---|
| 1006 | } |
|---|
| 1007 | $fielddesc .= $field."_"; |
|---|
| 1008 | |
|---|
| 1009 | # Prevent unnecessary entries in keyed graphs |
|---|
| 1010 | next if ($show_key and !$anydata); |
|---|
| 1011 | |
|---|
| 1012 | push (@{$d->[$fieldnum]}, @data); |
|---|
| 1013 | $fieldnum++; |
|---|
| 1014 | if ($show_key) |
|---|
| 1015 | { |
|---|
| 1016 | my $k = $field; |
|---|
| 1017 | $k = $1 if ($k =~ /^c_(.+)_fail/); |
|---|
| 1018 | push (@keys, $k); |
|---|
| 1019 | } |
|---|
| 1020 | } |
|---|
| 1021 | $fielddesc = substr($fielddesc, 0, 150) . "-more-" if (length($fielddesc) > 160); |
|---|
| 1022 | my $ts = int($max > 20 ? $max / 4 : $max > 6 ? 3 : 2); |
|---|
| 1023 | push @graphs, { GRAPH => &graph_one_count_data($fielddesc.$tdata->{$timeslice}->{abbrev}, |
|---|
| 1024 | $title, |
|---|
| 1025 | $tdata->{$timeslice}->{tformat}, |
|---|
| 1026 | "$ts $timeslice" . 's', |
|---|
| 1027 | $d, |
|---|
| 1028 | $gheight, |
|---|
| 1029 | $gwidth, |
|---|
| 1030 | ($varname =~ /duration/i ? 1 : 0), |
|---|
| 1031 | ($show_key ? \@keys : undef) ) }; |
|---|
| 1032 | } |
|---|
| 1033 | |
|---|
| 1034 | if ($template) |
|---|
| 1035 | { |
|---|
| 1036 | $template->param($varname => \@graphs); |
|---|
| 1037 | } |
|---|
| 1038 | else |
|---|
| 1039 | { |
|---|
| 1040 | return \@graphs; |
|---|
| 1041 | } |
|---|
| 1042 | } |
|---|
| 1043 | |
|---|
| 1044 | ############################################################################### |
|---|
| 1045 | |
|---|
| 1046 | sub generate_reports |
|---|
| 1047 | { |
|---|
| 1048 | my $template = HTML::Template->new(filename => "$template_dir/server_daily_maint_main.tmpl"); |
|---|
| 1049 | $template->param(NOW => strftime "%a %b %e %H:%M:%S %Y %z", localtime($starttime)); |
|---|
| 1050 | |
|---|
| 1051 | &report_count_data($template, 'SUCCESS', 'success_sysid_list', 'all'); |
|---|
| 1052 | &report_count_data($template, 'MISSING', 'missing_sysid_list', 'all'); |
|---|
| 1053 | &report_count_data($template, 'FAILURE', 'fail_sysid_list', 'all'); |
|---|
| 1054 | &graph_count_data($template, 'GRAPHS_USAGE', 'day week 12week year', 0, 0, 0, 'success_sysid_list', 'missing_sysid_list', 'fail_sysid_list'); |
|---|
| 1055 | |
|---|
| 1056 | &graph_missing_data($template, 'GRAPHS_MISSING'); |
|---|
| 1057 | |
|---|
| 1058 | &report_count_data($template, 'RUNS_SEEN', "visits", "all"); |
|---|
| 1059 | &graph_count_data($template, 'GRAPHS_RUNS', "day week 12week year", 0, 0, 0, "visits"); |
|---|
| 1060 | |
|---|
| 1061 | my @graphs = (); |
|---|
| 1062 | push @graphs, { GRAPH => &graph_runtime("per_day", 0, 7, "Last week") }; |
|---|
| 1063 | push @graphs, { GRAPH => &graph_runtime("per_month", 0, 1, "Last month") }; |
|---|
| 1064 | $template->param('GRAPHS_RUNTIME', \@graphs); |
|---|
| 1065 | |
|---|
| 1066 | my @duration; |
|---|
| 1067 | foreach my $timeslice ( 'week', 'month' ) |
|---|
| 1068 | { |
|---|
| 1069 | my $h; |
|---|
| 1070 | $h->{'TIMESLICE'} = $timeslice; |
|---|
| 1071 | $h->{'AVG'} = sec2hms(numvar($store->{"per_$timeslice"}->[0]->{duration},"",0) / $store->{"per_$timeslice"}->[0]->{duration_count}) if ($store->{"per_$timeslice"}->[0]->{duration_count}); |
|---|
| 1072 | $h->{'MIN'} = sec2hms(numvar($store->{"per_$timeslice"}->[0]->{duration_min},"",0)); |
|---|
| 1073 | $h->{'MAX'} = sec2hms(numvar($store->{"per_$timeslice"}->[0]->{duration_max},"",0)); |
|---|
| 1074 | push @duration, $h; |
|---|
| 1075 | } |
|---|
| 1076 | $template->param('DURATION' => \@duration); |
|---|
| 1077 | |
|---|
| 1078 | &graph_count_data($template, 'GRAPHS_DURATION', "day week 12week year", 0, 0, 0, "duration_min", "duration", "duration_max"); |
|---|
| 1079 | |
|---|
| 1080 | @graphs = ( ); |
|---|
| 1081 | my $rdata = &gather_region_data("per_day", 0, 7, 1); |
|---|
| 1082 | my @regions = (); |
|---|
| 1083 | my $top = 10; # list top 5 |
|---|
| 1084 | foreach (sort { $rdata->{$b} <=> $rdata->{$a} } keys %$rdata) |
|---|
| 1085 | { |
|---|
| 1086 | push @regions, { REGION => $_, SEEN => $rdata->{$_} }; |
|---|
| 1087 | $top--; |
|---|
| 1088 | last if ($top < 1); |
|---|
| 1089 | } |
|---|
| 1090 | $template->param('REGIONS', \@regions); |
|---|
| 1091 | push @graphs, { GRAPH => graph_regions("per_day", 0, 7, "Last week") }; |
|---|
| 1092 | push @graphs, { GRAPH => graph_regions("per_month", 0, 6, "Last 6 months") }; |
|---|
| 1093 | $template->param('GRAPHS_REGION', \@graphs); |
|---|
| 1094 | |
|---|
| 1095 | my @components_used; |
|---|
| 1096 | foreach my $timeslice ( 'day', 'week' ) |
|---|
| 1097 | { |
|---|
| 1098 | my $h; |
|---|
| 1099 | my @cu_timeslices; |
|---|
| 1100 | $top = 5; # only show top 5 |
|---|
| 1101 | $h->{'TIMESLICE'} = $timeslice; |
|---|
| 1102 | foreach my $c ( |
|---|
| 1103 | sort { $store->{"per_$timeslice"}->[0]->{components_used_order_list}->{$b} <=> $store->{"per_$timeslice"}->[0]->{components_used_order_list}->{$a} } |
|---|
| 1104 | keys %{($store->{"per_$timeslice"}->[0]->{components_used_order_list})}) |
|---|
| 1105 | { |
|---|
| 1106 | my $h2; |
|---|
| 1107 | my $val = $store->{"per_$timeslice"}->[0]->{components_used_order_list}->{$c}; |
|---|
| 1108 | $h2->{'TIMES'} = $val . " time" . ($val != 1 ? "s" : ""); |
|---|
| 1109 | $h2->{'DETAIL'} = $c; |
|---|
| 1110 | push @cu_timeslices, $h2; |
|---|
| 1111 | $top--; |
|---|
| 1112 | last if ($top < 1); |
|---|
| 1113 | } |
|---|
| 1114 | $h->{'COMPONENTS_USED_TIMESLICES'} = \@cu_timeslices; |
|---|
| 1115 | push @components_used, $h; |
|---|
| 1116 | } |
|---|
| 1117 | $template->param('COMPONENTS_USED' => \@components_used); |
|---|
| 1118 | |
|---|
| 1119 | # build a component list |
|---|
| 1120 | my %clist; |
|---|
| 1121 | foreach my $c (sort keys %{($store->{total})}) { |
|---|
| 1122 | if ($c =~ /^c_(.*)_runs$/) { |
|---|
| 1123 | # skip if any '/' or '`' chars in name |
|---|
| 1124 | next if (($1 =~ /\//) || ($1 =~ /`/)); |
|---|
| 1125 | $clist{$1}++; |
|---|
| 1126 | } |
|---|
| 1127 | } |
|---|
| 1128 | |
|---|
| 1129 | my (@components, @cfailures, @counters); |
|---|
| 1130 | my $ccount = 0; |
|---|
| 1131 | foreach my $c (sort keys %clist) { |
|---|
| 1132 | my $h; |
|---|
| 1133 | my @details; |
|---|
| 1134 | $h->{'NAME'} = $c; |
|---|
| 1135 | $h->{'FOURTH'} = 1 if (($ccount % 4 == 0) && ($ccount > 0)); |
|---|
| 1136 | my @ret = report_count_data(0, 0, "c_".$c."_success_sysid_list", "all"); |
|---|
| 1137 | $h->{'COMPONENT_SUCCESS_RUNS'} = $ret[0]; |
|---|
| 1138 | @ret = report_count_data(0, 0, "c_".$c."_fail_sysid_list", "all", "ignore-zero"); |
|---|
| 1139 | $h->{'COMPONENT_FAIL_RUNS'} = $ret[0]; |
|---|
| 1140 | |
|---|
| 1141 | $h->{'SUCCESS_RATE_DAY'} = calc_success_rate($c, "day"); |
|---|
| 1142 | $h->{'SUCCESS_RATE_WEEK'} = calc_success_rate($c, "week"); |
|---|
| 1143 | |
|---|
| 1144 | push @components, $h; |
|---|
| 1145 | my @uniques = report_count_data(0, 0, "c_".$c."_fail_sysid_list", "brief", "ignore-zero"); |
|---|
| 1146 | push @cfailures, { NAME => $c, TIMESLICES => $uniques[0] } if (@{$uniques[0]}); |
|---|
| 1147 | push @counters, "c_".$c."_fail_sysid_list"; |
|---|
| 1148 | |
|---|
| 1149 | $ccount++; |
|---|
| 1150 | } |
|---|
| 1151 | $template->param('COMPONENTS', \@components); |
|---|
| 1152 | $template->param('CFAILURES', \@cfailures); |
|---|
| 1153 | &graph_count_data($template, 'CFAILURE_GRAPHS', 'day week', 220, 1010, 1, @counters); |
|---|
| 1154 | |
|---|
| 1155 | @graphs = (); |
|---|
| 1156 | push @graphs, { GRAPH => &graph_mins_grabbed('per_hour', 0, 24, "Last day", 120, 505) }; |
|---|
| 1157 | push @graphs, { GRAPH => &graph_mins_grabbed('per_day', 0, 7, "Last week", 120, 505) }; |
|---|
| 1158 | $template->param('MINS_GRABBED_GRAPHS', \@graphs); |
|---|
| 1159 | |
|---|
| 1160 | print "Generating index.html...\n\n" if ($debug); |
|---|
| 1161 | open(F,">$graph_dir/index.html") || die "couldn't open $graph_dir/index.html for writing: $!\n"; |
|---|
| 1162 | $template->output(print_to => *F); |
|---|
| 1163 | close(F); |
|---|
| 1164 | |
|---|
| 1165 | # |
|---|
| 1166 | # iterate through every component we have seen, sorted alphabetically |
|---|
| 1167 | # generate per-component statistics |
|---|
| 1168 | # |
|---|
| 1169 | |
|---|
| 1170 | foreach my $c (sort keys %clist) |
|---|
| 1171 | { |
|---|
| 1172 | |
|---|
| 1173 | next if ($c eq 'shepherd'); # shouldn't be there; is sneaking in via parsing error |
|---|
| 1174 | |
|---|
| 1175 | $template = HTML::Template->new(filename => "$template_dir/server_daily_maint_component.tmpl"); |
|---|
| 1176 | $template->param(NOW => "". localtime($starttime)); |
|---|
| 1177 | $template->param('NAME', $c); |
|---|
| 1178 | |
|---|
| 1179 | &report_count_data($template, 'SUCCESS', 'c_'.$c.'_success_sysid_list', 'all'); |
|---|
| 1180 | &report_count_data($template, 'MISSING', 'c_'.$c.'_missing_sysid_list', 'all'); |
|---|
| 1181 | &report_count_data($template, 'FAILURE', 'c_'.$c.'_fail_sysid_list', 'all'); |
|---|
| 1182 | &graph_count_data($template, 'GRAPHS_USAGE', 'day week 12week year', 0, 0, 0, 'c_'.$c.'_success_sysid_list', 'c_'.$c.'_missing_sysid_list', 'c_'.$c.'_fail_sysid_list'); |
|---|
| 1183 | |
|---|
| 1184 | &graph_missing_data($template, 'GRAPHS_MISSING', $c); |
|---|
| 1185 | |
|---|
| 1186 | &report_count_data($template, 'RUNS_SEEN', 'c_'.$c.'_runs', "all"); |
|---|
| 1187 | &graph_count_data($template, 'GRAPHS_RUNS', "day week 12week year", 0, 0, 0, 'c_'.$c.'_runs'); |
|---|
| 1188 | |
|---|
| 1189 | &report_failure_msgs($template, $c); |
|---|
| 1190 | |
|---|
| 1191 | my @sf; |
|---|
| 1192 | foreach my $w ("success", "failure") |
|---|
| 1193 | { |
|---|
| 1194 | my $h; |
|---|
| 1195 | $h->{NAME} = $w; |
|---|
| 1196 | |
|---|
| 1197 | my $g = "c_".$c."_".$w; |
|---|
| 1198 | |
|---|
| 1199 | my @timeslices; |
|---|
| 1200 | foreach my $timeslice ("day", "month") |
|---|
| 1201 | { |
|---|
| 1202 | my @versions; |
|---|
| 1203 | foreach my $c |
|---|
| 1204 | ( |
|---|
| 1205 | sort { $store->{"per_$timeslice"}->[0]->{$g."_version_list"}->{$b} <=> $store->{"per_$timeslice"}->[0]->{$g."_version_list"}->{$a} } |
|---|
| 1206 | keys %{($store->{"per_$timeslice"}->[0]->{$g."_version_list"})}) { |
|---|
| 1207 | my $val = $store->{"per_$timeslice"}->[0]->{$g."_version_list"}->{$c}; |
|---|
| 1208 | my $h2; |
|---|
| 1209 | $h2->{'TIMES'} = sprintf("%d time%s", $val, ($val != 1 ? "s" : "")); |
|---|
| 1210 | $h2->{'VERSION'} = $c; |
|---|
| 1211 | push @versions, $h2; |
|---|
| 1212 | } |
|---|
| 1213 | push @timeslices, { TIMESLICE => $timeslice, VERSIONS => \@versions }; |
|---|
| 1214 | } |
|---|
| 1215 | $h->{'SFV_TIMESLICES'} = \@timeslices; |
|---|
| 1216 | |
|---|
| 1217 | my @durations = ( ); |
|---|
| 1218 | foreach my $timeslice ("day", "month") |
|---|
| 1219 | { |
|---|
| 1220 | if ((defined $store->{"per_$timeslice"}->[0]->{$g."_duration_count"}) && ($store->{"per_$timeslice"}->[0]->{$g."_duration_count"} > 0)) |
|---|
| 1221 | { |
|---|
| 1222 | push @durations, { AVG => sec2hms(numvar($store->{"per_$timeslice"}->[0]->{$g."_duration"},"",0) / $store->{"per_$timeslice"}->[0]->{$g."_duration_count"}), |
|---|
| 1223 | MIN => sec2hms(numvar($store->{"per_$timeslice"}->[0]->{$g."_duration_min"},"",0)), |
|---|
| 1224 | MAX => sec2hms(numvar($store->{"per_$timeslice"}->[0]->{$g."_duration_max"},"",0)) }; |
|---|
| 1225 | } |
|---|
| 1226 | } |
|---|
| 1227 | $h->{'DURATIONS'} = \@durations; |
|---|
| 1228 | |
|---|
| 1229 | $h->{'GRAPHS_DURATIONS'} = &graph_count_data(undef, undef, "day week 12week year", 0, 0, 0, $g."_duration", $g."_duration_min", $g."_duration_max"); |
|---|
| 1230 | push @sf, $h; |
|---|
| 1231 | } |
|---|
| 1232 | $template->param(SUCCESS_FAILURE => \@sf); |
|---|
| 1233 | |
|---|
| 1234 | # iterate across every component statistic seen from this component |
|---|
| 1235 | my @statistics; |
|---|
| 1236 | |
|---|
| 1237 | # Nov 11 2007: removed individual stats to reduce load |
|---|
| 1238 | if (0) { |
|---|
| 1239 | |
|---|
| 1240 | foreach my $s (sort keys %{($store->{total})}) { |
|---|
| 1241 | next if ($s !~ /^cs\.(.*)\.(.*)_duration$/); |
|---|
| 1242 | next if ($1 ne $c); |
|---|
| 1243 | my $sname = $2; |
|---|
| 1244 | my $svar = "cs\.".$c.".".$sname; |
|---|
| 1245 | |
|---|
| 1246 | my @timeslices = (); |
|---|
| 1247 | foreach my $timeslice ("day", "month") |
|---|
| 1248 | { |
|---|
| 1249 | if ((defined $store->{"per_$timeslice"}->[0]->{$svar."_duration_count"}) && ($store->{"per_$timeslice"}->[0]->{$svar."_duration_count"} > 0)) |
|---|
| 1250 | { |
|---|
| 1251 | my $h; |
|---|
| 1252 | $h->{TIMESLICE} = $timeslice; |
|---|
| 1253 | $h->{AVG} = nformat(numvar($store->{"per_$timeslice"}->[0]->{$svar."_duration"},"",0) / $store->{"per_$timeslice"}->[0]->{$svar."_duration_count"}); |
|---|
| 1254 | $h->{MIN} = nformat(numvar($store->{"per_$timeslice"}->[0]->{$svar."_duration_min"},"",0)); |
|---|
| 1255 | $h->{MAX} = nformat(numvar($store->{"per_$timeslice"}->[0]->{$svar."_duration_max"},"",0)); |
|---|
| 1256 | push @timeslices, $h; |
|---|
| 1257 | } |
|---|
| 1258 | } |
|---|
| 1259 | push @statistics, { STATNAME => $sname, TIMESLICES => [ @timeslices ], GRAPHS_STATISTICS => &graph_count_data(undef, undef, "72hour 12week", 0, 0, 0, $svar."_duration", $svar."_duration_min", $svar."_duration_max")}; |
|---|
| 1260 | } |
|---|
| 1261 | |
|---|
| 1262 | } |
|---|
| 1263 | |
|---|
| 1264 | $template->param(STATISTICS => \@statistics); |
|---|
| 1265 | |
|---|
| 1266 | open(F,">$graph_dir/$c.html") || die "couldn't open $graph_dir/$c for writing: $!\n"; |
|---|
| 1267 | $template->output(print_to => *F); |
|---|
| 1268 | close(F); |
|---|
| 1269 | } |
|---|
| 1270 | } |
|---|
| 1271 | |
|---|
| 1272 | sub nformat |
|---|
| 1273 | { |
|---|
| 1274 | my $n = shift; |
|---|
| 1275 | return sprintf("%0.2f", $n); |
|---|
| 1276 | } |
|---|
| 1277 | |
|---|
| 1278 | ############################################################################### |
|---|
| 1279 | |
|---|