File Coverage

blib/lib/CPAN/Testers/WWW/Statistics/Pages.pm
Criterion Covered Total %
statement 48 1284 3.7
branch 0 364 0.0
condition 0 164 0.0
subroutine 16 54 29.6
pod 14 14 100.0
total 78 1880 4.1


line stmt bran cond sub pod time code
1             package CPAN::Testers::WWW::Statistics::Pages;
2              
3 15     15   53195 use warnings;
  15         32  
  15         432  
4 15     15   71 use strict;
  15         31  
  15         281  
5 15     15   64 use vars qw($VERSION);
  15         35  
  15         706  
6              
7             $VERSION = '1.23';
8              
9             #----------------------------------------------------------------------------
10              
11             =head1 NAME
12              
13             CPAN::Testers::WWW::Statistics::Pages - CPAN Testers Statistics pages.
14              
15             =head1 SYNOPSIS
16              
17             my %hash = { config => 'options' };
18             my $obj = CPAN::Testers::WWW::Statistics->new(%hash);
19             my $ct = CPAN::Testers::WWW::Statistics::Pages->new(parent => $obj);
20              
21             $ct->update_full(); # updates statistics data and web pages
22              
23             # alternatively called individual processes
24              
25             $ct->update_data(); # updates statistics data
26             $ct->build_basics(); # updates basic web pages
27             $ct->build_matrices(); # updates matrix style web pages
28             $ct->build_stats(); # updates stats style web pages
29              
30             =head1 DESCRIPTION
31              
32             Using the cpanstats database, this module extracts all the data and generates
33             all the HTML pages needed for the CPAN Testers Statistics website. In addition,
34             also generates the data files in order generate the graphs that appear on the
35             site.
36              
37             Note that this package should not be called directly, but via its parent as:
38              
39             my %hash = { config => 'options' };
40             my $obj = CPAN::Testers::WWW::Statistics->new(%hash);
41              
42             $obj->make_pages(); # updates statistics data and web pages
43              
44             # alternatively called individual processes
45              
46             $obj->update(); # updates statistics data
47             $obj->make_basics(); # updates basic web pages
48             $obj->make_matrix(); # updates matrix style web pages
49             $obj->make_stats(); # updates stats style web pages
50              
51             =cut
52              
53             # -------------------------------------
54             # Library Modules
55              
56 15     15   6966 use Data::Dumper;
  15         75383  
  15         943  
57 15     15   10105 use DateTime;
  15         6628194  
  15         712  
58 15     15   137 use File::Basename;
  15         34  
  15         1295  
59 15     15   7998 use File::Copy;
  15         24902  
  15         802  
60 15     15   104 use File::Path;
  15         43  
  15         659  
61 15     15   5931 use File::Slurp;
  15         44931  
  15         1022  
62 15     15   744 use HTML::Entities;
  15         5100  
  15         753  
63 15     15   590 use IO::File;
  15         6752  
  15         1887  
64 15     15   8189 use JSON;
  15         114932  
  15         113  
65 15     15   8151 use Sort::Versions;
  15         7529  
  15         1603  
66 15     15   6252 use Template;
  15         225645  
  15         478  
67             #use Time::HiRes qw ( time );
68 15     15   7177 use Time::Piece;
  15         100644  
  15         95  
69 15     15   1276 use Try::Tiny;
  15         45  
  15         174633  
70              
71             # -------------------------------------
72             # Variables
73              
74             my %month = (
75             0 => 'January', 1 => 'February', 2 => 'March', 3 => 'April',
76             4 => 'May', 5 => 'June', 6 => 'July', 7 => 'August',
77             8 => 'September', 9 => 'October', 10 => 'November', 11 => 'December'
78             );
79              
80             my @months = map { $month{$_} } keys %month;
81             my @days = qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday);
82              
83             my $ADAY = 86400;
84              
85             my %matrix_limits = (
86             all => [ 1000, 5000 ],
87             month => [ 100, 500 ]
88             );
89              
90             # -------------------------------------
91             # Subroutines
92              
93             =head1 INTERFACE
94              
95             =head2 The Constructor
96              
97             =over 4
98              
99             =item * new
100              
101             Page creation object. Allows the user to turn or off the progress tracking.
102              
103             new() takes an option hash as an argument, which may contain 'progress => 1'
104             to turn on the progress tracker.
105              
106             =back
107              
108             =cut
109              
110             sub new {
111 0     0 1   my $class = shift;
112 0           my %hash = @_;
113              
114 0 0         die "Must specify the parent statistics object\n" unless(defined $hash{parent});
115              
116 0           my $self = {parent => $hash{parent}};
117 0           bless $self, $class;
118              
119 0           $self->setdates();
120 0           return $self;
121             }
122              
123             =head2 Public Methods
124              
125             =over 4
126              
127             =item * setdates
128              
129             Prime all key date variable.
130              
131             =item * update_full
132              
133             Full update of data and pages.
134              
135             =item * update_data
136              
137             Update data and store in JSON format.
138              
139             =item * build_basics
140              
141             Create the basic set of pages,those require no statistical calculation.
142              
143             =item * build_matrices
144              
145             Create the matrices pages and distribution list pages.
146              
147             =item * build_stats
148              
149             Create all other statistical pages; monthly tables, interesting stats, etc.
150              
151             =item * build_leaders
152              
153             Create all OS Leaderboards.
154              
155             =item * build_cpan
156              
157             Create/update the CPAN specific statistics data files and pages.
158              
159             =item * build_performance
160              
161             Create/update the builder performance data file.
162              
163             =item * build_noreports
164              
165             Create all OS no report pages.
166              
167             =back
168              
169             =cut
170              
171             sub setdates {
172 0     0 1   my $self = shift;
173 0   0       my $time = shift || time;
174              
175 0           $self->{parent}->_log("init");
176              
177 0           Time::Piece::day_list(@days);
178 0           Time::Piece::mon_list(@months);
179              
180             # timestamp for now
181 0           my $t = localtime($time);
182 0           $self->{dates}{RUNTIME} = $t->strftime("%a, %e %b %Y %T %Z");
183              
184             # todays date
185 0           my @datetime = localtime($time);
186 0           my $THISYEAR = ($datetime[5] + 1900);
187 0           my $THISMONTH = ($datetime[4]);
188 0           $self->{dates}{RUNDATE} = sprintf "%d%s %s %d", $datetime[3], _ext($datetime[3]), $month{$THISMONTH}, $THISYEAR;
189              
190             # THISMONTH is the last date for all data
191 0           $self->{dates}{THISMONTH} = ($THISYEAR) * 100 + $THISMONTH + 1;
192 0           $self->{dates}{THISDATE} = sprintf "%s %d", $month{int($THISMONTH)}, $THISYEAR;
193              
194 0           my $THATMONTH = $THISMONTH - 1;
195 0           my $THATYEAR = $THISYEAR;
196 0 0         if($THATMONTH < 0) {
197 0           $THATMONTH = 11;
198 0           $THATYEAR--;
199             }
200              
201             # LASTMONTH is the Month/Year stats are run for
202 0           $self->{dates}{LASTMONTH} = sprintf "%04d%02d", $THATYEAR, int($THATMONTH+1);
203 0           $self->{dates}{LASTDATE} = sprintf "%s %d", $month{int($THATMONTH)}, $THATYEAR;
204 0           $self->{dates}{PREVMONTH} = sprintf "%02d/%02d", int($THATMONTH+1), $THATYEAR - 2000;
205              
206 0           $THATMONTH--;
207 0 0         if($THATMONTH < 0) {
208 0           $THATMONTH = 11;
209 0           $THATYEAR--;
210             }
211              
212             # THATMONTH is the previous Month/Year for a full matrix
213 0           $self->{dates}{THATMONTH} = sprintf "%04d%02d", $THATYEAR, int($THATMONTH+1);
214            
215 0           $self->{parent}->_log( "THISYEAR=[$THISYEAR]" );
216 0           $self->{parent}->_log( "THATYEAR=[$THATYEAR]" );
217 0           $self->{parent}->_log( "DATES=" . Dumper( $self->{dates} ) );
218              
219             # calculate database metrics
220 0           my @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT fulldate FROM cpanstats ORDER BY id DESC LIMIT 1");
221 0           my @time = $rows[0]->[0] =~ /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})/;
222 0           $self->{dates}{RUNDATE2} = sprintf "%d%s %s %d", $time[2],_ext($time[2]),$month{$time[1]-1},$time[0];
223 0           $self->{dates}{RUNDATE3} = sprintf "%d%s %s %d, %02d:%02d", $time[2],_ext($time[2]),$month{$time[1]-1},$time[0],$time[3],$time[4];
224             }
225              
226             sub update_full {
227 0     0 1   my $self = shift;
228              
229 0           $self->{parent}->_log("start update_full");
230 0           $self->build_basics();
231 0           $self->build_data();
232 0           $self->build_matrices();
233 0           $self->build_stats();
234 0           $self->build_leaders();
235 0           $self->{parent}->_log("finish update_full");
236             }
237              
238             sub update_data {
239 0     0 1   my $self = shift;
240              
241 0           $self->{parent}->_log("start update_data");
242 0           $self->build_data();
243 0           $self->{parent}->_log("finish update_data");
244             }
245              
246             sub build_basics {
247 0     0 1   my $self = shift;
248              
249 0           $self->{parent}->_log("start build_basics");
250              
251             ## BUILD INFREQUENT PAGES
252 0           $self->_write_basics();
253 0           $self->_missing_in_action();
254              
255 0           $self->{parent}->_log("finish build_basics");
256             }
257              
258             sub build_matrices {
259 0     0 1   my $self = shift;
260              
261 0           $self->{parent}->_log("start build_matrices");
262 0           $self->storage_read();
263 0 0         if($self->{perls}) {
264 0           $self->{parent}->_log("building dist hash from storage");
265              
266 0           my @versions = sort {versioncmp($b,$a)} keys %{$self->{perls}};
  0            
  0            
267 0           $self->{versions} = \@versions;
268              
269 0           $self->_build_osname_matrix();
270 0           $self->_build_platform_matrix();
271             }
272 0           $self->{parent}->_log("finish build_matrices");
273             }
274              
275             sub build_stats {
276 0     0 1   my $self = shift;
277              
278 0           $self->{parent}->_log("stats start");
279              
280 0           $self->{parent}->_log("building dist hash from storage");
281 0           $self->storage_read();
282 0           my $testers = $self->storage_read('testers');
283 0           $self->{parent}->_log("dist hash from storage built");
284              
285 0 0         if($testers) {
286 0           for my $tester (keys %$testers) {
287 0           $self->{counts}{$testers->{$tester}{first}}{first}++;
288 0           $self->{counts}{$testers->{$tester}{last}}{last}++;
289             }
290              
291 0           $testers = {}; # save memory
292 0           $self->{parent}->_log("tester counts built");
293              
294 0           my @versions = sort {versioncmp($b,$a)} keys %{$self->{perls}};
  0            
  0            
295 0           $self->{versions} = \@versions;
296              
297             ## BUILD INDEPENDENT STATS
298 0           $self->_build_sizes();
299 0           $self->_report_cpan();
300              
301             ## BUILD MONTHLY STATS
302 0           $self->_build_monthly_stats();
303              
304             ## BUILD STATS PAGES
305 0           $self->_report_interesting();
306 0           $self->_build_monthly_stats_files();
307 0           $self->_build_failure_rates();
308 0           $self->_build_performance_stats();
309              
310             ## BUILD INDEX PAGE
311 0           $self->_write_index();
312             }
313              
314 0           $self->{parent}->_log("stats finish");
315             }
316              
317             sub build_cpan {
318 0     0 1   my $self = shift;
319              
320 0           $self->{parent}->_log("cpan stats start");
321              
322             ## BUILD INDEPENDENT STATS
323 0           $self->_build_sizes();
324 0           $self->_report_cpan();
325              
326 0           $self->{parent}->_log("cpan stats finish");
327             }
328              
329             sub build_performance {
330 0     0 1   my $self = shift;
331              
332 0           $self->{parent}->_log("performance start");
333 0           $self->{build} = $self->storage_read('build');
334              
335             ## BUILD PERFORMANCE FILES
336 0           $self->_build_performance_stats();
337              
338 0           $self->{parent}->_log("performance finish");
339             }
340              
341             sub build_leaders {
342 0     0 1   my $self = shift;
343              
344 0           $self->{parent}->_log("leaders start");
345              
346             ## BUILD OS LEADERBOARDS
347 0           $self->_build_osname_leaderboards();
348              
349 0           $self->{parent}->_log("leaders finish");
350             }
351              
352             sub build_noreports {
353 0     0 1   my $self = shift;
354              
355 0           $self->{parent}->_log("noreports start");
356              
357 0           $self->_build_noreports();
358              
359 0           $self->{parent}->_log("noreports finish");
360             }
361              
362             =head2 Private Methods
363              
364             =head3 Data Methods
365              
366             =over 4
367              
368             =item * build_data
369              
370             =item * storage_read
371              
372             =item * storage_write
373              
374             =back
375              
376             =cut
377              
378             sub build_data {
379 0     0 1   my $self = shift;
380              
381 0           $self->{parent}->_log("building rate hash");
382              
383 0           my ($d1,$d2) = (time(), time() - $ADAY);
384 0           my @date = localtime($d2);
385 0           my $date = sprintf "%04d%02d%02d", $date[5]+1900, $date[4]+1, $date[3];
386 0           my @tday = localtime($d1);
387 0           my $tday = sprintf "%04d%02d%02d", $tday[5]+1900, $tday[4]+1, $tday[3];
388              
389 0   0       my $lastid = $self->storage_read('lastid') || 0;
390 0           my $testers = {};
391              
392 0 0         if($lastid) {
393 0           $self->{parent}->_log("building dist hash from storage");
394              
395 0           $self->storage_read();
396 0           $testers = $self->storage_read('testers');
397              
398             # only remember the latest release for 'dists' hash
399 0           my $iterator = $self->{parent}->{CPANSTATS}->iterator('hash',"SELECT dist,version FROM ixlatest");
400 0           while(my $row = $iterator->()) {
401 0 0 0       next if($self->{dists}{$row->{dist}} && $self->{dists}{$row->{dist}}->{VER} eq $row->{version});
402 0           $self->{dists}{$row->{dist}} = { ALL => 0, IXL => 0, VER => $row->{version}};
403             }
404              
405             } else {
406 0           $self->{parent}->_log("building dist hash from scratch");
407              
408 0           my $iterator = $self->{parent}->{CPANSTATS}->iterator('hash',"SELECT dist,version FROM ixlatest");
409 0           while(my $row = $iterator->()) {
410 0           $self->{dists}{$row->{dist}}->{ALL} = 0;
411 0           $self->{dists}{$row->{dist}}->{IXL} = 0;
412 0           $self->{dists}{$row->{dist}}->{VER} = $row->{version};
413             }
414              
415 0           $self->{parent}->_log("building stats hash");
416              
417 0   0       $self->{count}{$_} ||= 0 for(qw(posters entries reports distros));
418             $self->{xrefs} = { posters => {}, entries => {}, reports => {} },
419 0           $self->{xlast} = { posters => [], entries => [], reports => [] },
420             }
421              
422             # clear old month entries
423 0           for my $key (qw(platform osys osname)) {
424 0           for my $name (keys %{$self->{$key}}) {
  0            
425 0           for my $perl (keys %{$self->{$key}{$name}}) {
  0            
426 0           for my $month (keys %{$self->{$key}{$name}{$perl}{month}}) {
  0            
427 0 0 0       next if($month =~ /^\d+$/ && $month > $self->{dates}{THATMONTH});
428 0           delete $self->{$key}{$name}{$perl}{month}{$month};
429             }
430             }
431             }
432             }
433              
434             #$self->{parent}->_log("build:1.".Dumper($self->{build}));
435              
436             # reports builder performance stats
437 0           for my $d (keys %{$self->{build}}) {
  0            
438 0           $self->{build}{$d}->{old} = 0;
439             }
440 0           my $file = $self->{parent}->builder();
441 0 0 0       if($file && -f $file) {
442 0 0         if(my $fh = IO::File->new($file,'r')) {
443 0           while(<$fh>) {
444 0           my ($d,$r,$p) = /(\d+),(\d+),(\d+)/;
445 0 0         next unless($d);
446 0           $self->{build}{$d}->{webtotal} = $r;
447 0           $self->{build}{$d}->{webunique} = $p;
448 0           $self->{build}{$d}->{old} = 1;
449             }
450 0           $fh->close;
451             }
452             }
453 0           $self->{build}{$date}->{old} = 1; # keep the tally for yesterday
454 0           $self->{build}{$tday}->{old} = 2; # keep the tally for today, but don't use
455 0           for my $d (keys %{$self->{build}}) {
  0            
456 0 0         delete $self->{build}{$d} unless($self->{build}{$d}->{old});
457             }
458             #$self->{parent}->_log("build:2.".Dumper($self->{build}));
459              
460              
461             # load pass matrices, for all or just the last full month
462 0           $self->{parent}->_log("building pass reports matrices from database");
463 0           my $count = 0;
464 0           my $iterator = $self->{parent}->{CPANSTATS}->iterator('hash','SELECT * FROM passreports');
465 0           while(my $row = $iterator->()) {
466 0           $self->{pass}{$row->{platform}}{$row->{perl}}{all}{$row->{dist}} = 1;
467 0 0         next if($row->{postdate} <= $self->{dates}{THATMONTH});
468 0           $self->{pass}{$row->{platform}}{$row->{perl}}{month}{$row->{postdate}}{$row->{dist}} = 1;
469             }
470              
471              
472             # 0, 1, 2, 3, 4, 5 6, 7, 8, 9, 10 11 12
473             # id, guid, state, postdate, tester, dist, version, platform, perl, osname, osvers, fulldate, type
474              
475 0           $self->{parent}->_log("building dist hash from $lastid");
476 0           $iterator = $self->{parent}->{CPANSTATS}->iterator('hash',"SELECT * FROM cpanstats WHERE type = 2 AND id > $lastid ORDER BY id LIMIT 1000000");
477 0           while(my $row = $iterator->()) {
478 0           $row->{perl} =~ s/\s.*//; # only need to know the main release
479 0           $lastid = $row->{id};
480              
481             {
482 0           my $osname = $self->{parent}->osname($row->{osname});
  0            
483 0           my ($name) = $self->{parent}->tester($row->{tester});
484              
485 0           $self->{stats}{$row->{postdate}}{reports}++;
486 0           $self->{stats}{$row->{postdate}}{state }{$row->{state}}++;
487             #$self->{stats}{$row->{postdate}}{dist }{$row->{dist}}++;
488             #$self->{stats}{$row->{postdate}}{version }{$row->{version}}++;
489              
490             # check distribution tallies
491 0 0         if(defined $self->{dists}{$row->{dist}}) {
492 0           $self->{dists}{$row->{dist}}{ALL}++;
493              
494 0 0         if($self->{dists}{$row->{dist}}->{VER} eq $row->{version}) {
495 0           $self->{dists}{$row->{dist}}{IXL}++;
496              
497             # check failure rates
498 0 0         $self->{fails}{$row->{dist}}{$row->{version}}{fail}++ if($row->{state} eq 'fail');
499 0 0         $self->{fails}{$row->{dist}}{$row->{version}}{pass}++ if($row->{state} eq 'pass');
500 0           $self->{fails}{$row->{dist}}{$row->{version}}{total}++;
501             }
502             }
503              
504             # build matrix stats
505 0           my $perl = $row->{perl};
506 0           $perl =~ s/\s.*//; # only need to know the main release
507 0           $self->{perls}{$perl} = 1;
508              
509             # $self->{pass} {$row->{platform}}{$perl}{all}{$row->{dist}} = 1;
510 0           $self->{platform}{$row->{platform}}{$perl}{all}++;
511 0           $self->{osys} {$osname} {$perl}{all}{$row->{dist}} = 1;
512 0           $self->{osname} {$osname} {$perl}{all}++;
513              
514 0 0         if($row->{postdate} > $self->{dates}{THATMONTH}) {
515             # $self->{pass} {$row->{platform}}{$perl}{month}{$row->{postdate}}{$row->{dist}} = 1;
516 0           $self->{platform}{$row->{platform}}{$perl}{month}{$row->{postdate}}++;
517 0           $self->{osys} {$osname} {$perl}{month}{$row->{postdate}}{$row->{dist}} = 1;
518 0           $self->{osname} {$osname} {$perl}{month}{$row->{postdate}}++;
519             }
520              
521             # record tester activity
522 0   0       $testers->{$name}{first} ||= $row->{postdate};
523 0           $testers->{$name}{last} = $row->{postdate};
524 0           $self->{counts}{$row->{postdate}}{testers}{$name} = 1;
525              
526 0           my $day = substr($row->{fulldate},0,8);
527 0 0         $self->{build}{$day}{reports}++ if(defined $self->{build}{$day});
528             }
529              
530 0           my @row = (0, map {$row->{$_}} qw(id guid state postdate tester dist version platform perl osname osvers fulldate type));
  0            
531              
532 0           $self->{count}{posters} = $row[1];
533 0           $self->{count}{entries}++;
534 0           $self->{count}{reports}++;
535              
536 0           my $type = 'reports';
537 0 0         $self->{parent}->_log("checkpoint: count=$self->{count}{$type}, lastid=$lastid") if($self->{count}{$type} % 10000 == 0);
538              
539 0 0         if($self->{count}->{$type} % 100000 == 0) {
540             # due to the large data structures used, long runs (eg starting from
541             # scratch) should save the current state periodically.
542 0           $self->storage_write();
543 0           $self->storage_write('testers',$testers);
544 0           $self->storage_write('lastid',$lastid);
545             }
546              
547 0 0 0       if($self->{count}{$type} == 1 || ($self->{count}->{$type} % 500000) == 0) {
548 0           $self->{xrefs}{$type}->{$self->{count}->{$type}} = \@row;
549             } else {
550 0           $self->{xlast}{$type} = \@row;
551             }
552             }
553             #$self->{parent}->_log("build:3.".Dumper($self->{build}));
554             #$self->{parent}->_log("build:4.".Dumper($testers));
555              
556 0           $self->storage_write();
557 0           $self->storage_write('testers',$testers);
558 0           $self->storage_write('lastid',$lastid);
559              
560 0           for my $tester (keys %$testers) {
561 0           $self->{counts}{$testers->{$tester}{first}}{first}++;
562 0           $self->{counts}{$testers->{$tester}{last}}{last}++;
563             }
564             #$self->{parent}->_log("build:5.".Dumper($self->{counts}));
565              
566 0           my @versions = sort {versioncmp($b,$a)} keys %{$self->{perls}};
  0            
  0            
567 0           $self->{versions} = \@versions;
568              
569 0           $self->{parent}->_log("stats hash built");
570             }
571              
572             sub storage_read {
573 0     0 1   my ($self,$type) = @_;
574              
575 0 0         if($type) {
576 0           my $storage = sprintf $self->{parent}->mainstore(), $type;
577 0 0         return unless(-f $storage);
578 0           my $data = read_file($storage);
579 0           my $store = decode_json($data);
580 0           return $store->{$type};
581             }
582              
583             # for $type (qw(stats dists fails perls pass platform osys osname build counts count xrefs xlast)) {
584 0           for $type (qw(stats dists fails perls platform osys osname build counts count xrefs xlast)) {
585 0           $self->{parent}->_log("storage_read:1.type=$type");
586 0           my $storage = sprintf $self->{parent}->mainstore(), $type;
587 0 0         next unless(-f $storage);
588 0           $self->{parent}->_log("storage_read:2.storage=$storage");
589             try {
590 0     0     my $data = read_file($storage);
591 0           my $store = decode_json($data);
592 0           $self->{$type} = $store->{$type};
593             } catch {
594 0     0     $self->{parent}->_log("storage_read:3.failed to read data storage=$storage");
595 0           };
596             }
597             }
598              
599             sub storage_write {
600 0     0 1   my ($self,$type,$store) = @_;
601              
602 0 0         if($type) {
603 0 0         return unless($store);
604 0           my $data = encode_json({$type => $store});
605              
606 0           my $storage = sprintf $self->{parent}->mainstore(), $type;
607 0           my $dir = dirname($storage);
608 0 0 0       mkpath($dir) if($dir && !-e $dir);
609 0           overwrite_file($storage,$data);
610 0           return;
611             }
612              
613             # for $type (qw(stats dists fails perls pass platform osys osname build counts count xrefs xlast)) {
614 0           for $type (qw(stats dists fails perls platform osys osname build counts count xrefs xlast)) {
615 0 0         next unless($self->{$type});
616 0           my $data = encode_json({$type => $self->{$type}});
617              
618 0           my $storage = sprintf $self->{parent}->mainstore(), $type;
619 0           my $dir = dirname($storage);
620 0 0 0       mkpath($dir) if($dir && !-e $dir);
621 0           overwrite_file($storage,$data);
622             }
623             }
624              
625             =head3 Page Creation Methods
626              
627             =over 4
628              
629             =item * _write_basics
630              
631             Write out basic pages, all of which are simply built from the templates,
632             without any data processing required.
633              
634             =cut
635              
636             sub _write_basics {
637 0     0     my $self = shift;
638 0           my $directory = $self->{parent}->directory;
639 0           my $templates = $self->{parent}->templates;
640 0           my $results = "$directory/stats";
641 0           mkpath($results);
642              
643 0           $self->{parent}->_log("writing basic files");
644              
645 0           my $ranges1 = $self->{parent}->ranges('TEST_RANGES');
646 0           my $ranges2 = $self->{parent}->ranges('CPAN_RANGES');
647              
648             # additional pages not requiring metrics
649 0           my %pages = (
650             cpanmail => {},
651             response => {},
652             perform => {},
653             terms => {},
654             graphs => {},
655             graphs1 => {RANGES => $ranges1, template=>'archive', PREFIX=>'stats1' ,TITLE=>'Monthly Report Counts'},
656             graphs2 => {RANGES => $ranges1, template=>'archive', PREFIX=>'stats2' ,TITLE=>'Testers, Platforms and Perls'},
657             graphs3 => {RANGES => $ranges1, template=>'archive', PREFIX=>'stats3' ,TITLE=>'Monthly Non-Passing Reports Counts'},
658             graphs4 => {RANGES => $ranges1, template=>'archive', PREFIX=>'stats4' ,TITLE=>'Monthly Tester Fluctuations'},
659             graphs5 => {RANGES => $ranges1, template=>'archive', PREFIX=>'pcent1' ,TITLE=>'Monthly Report Percentages'},
660             graphs6 => {RANGES => $ranges2, template=>'archive', PREFIX=>'stats6' ,TITLE=>'All Distribution Uploads per Month'},
661             graphs12 => {RANGES => $ranges2, template=>'archive', PREFIX=>'stats12',TITLE=>'New Distribution Uploads per Month'}
662             );
663              
664 0           $self->{parent}->_log("building support pages");
665 0           $self->_writepage($_,$pages{$_}) for(keys %pages);
666              
667             # copy files
668 0           $self->{parent}->_log("copying static files");
669 0           my $tocopy = $self->{parent}->tocopy;
670 0           $self->{parent}->_log("files to copy = " . scalar(@$tocopy));
671 0           for my $filename (@$tocopy) {
672 0           my $source = $templates . "/$filename";
673 0 0         if(-f $source) {
674 0           my $target = $directory . "/$filename";
675 0 0         next if(-f $target);
676              
677 0           mkpath( dirname($target) );
678 0 0         if(-d dirname($target)) {
679 0           $self->{parent}->_log("copying '$source' to '$target'");
680 0           copy( $source, $target );
681             } else {
682 0           $self->{parent}->_log("copy error: Missing directory: $target");
683 0           warn "Missing directory: $target\n";
684             }
685             } else {
686 0           $self->{parent}->_log("copy error: Missing file: $source");
687 0           warn "Missing file: $source\n";
688             }
689             }
690              
691             #link files
692 0           $self->{parent}->_log("linking static files");
693 0           my $tolink = $self->{parent}->tolink;
694 0           for my $filename (keys %$tolink) {
695 0           my $source = $directory . "/$filename";
696 0           my $target = $directory . '/'.$tolink->{$filename};
697              
698 0 0         next if(-f $target);
699 0 0         if(-f $source) {
700 0           link($target,$source);
701             } else {
702 0           warn "Missing file: $source\n";
703             }
704             }
705              
706             # wget
707 0           my $cmd = sprintf "wget -O %s/sponsors.json http://iheart.cpantesters.org/home/sponsors?images=1 2>/dev/null", $directory;
708 0           $self->{parent}->_log("sponsors: '$cmd'");
709 0           system($cmd);
710             }
711              
712             =item * _write_index
713              
714             Writes out the main index page, after all stats have been calculated.
715              
716             =cut
717              
718             sub _write_index {
719 0     0     my $self = shift;
720 0           my $directory = $self->{parent}->directory;
721 0           my $templates = $self->{parent}->templates;
722              
723 0           $self->{parent}->_log("writing index file");
724              
725             # calculate growth rates
726 0           my ($d1,$d2) = (time(), time() - $ADAY);
727 0           my @date = localtime($d2);
728 0           my $date = sprintf "%04d%02d%02d", $date[5]+1900, $date[4]+1, $date[3];
729              
730 0           my @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(*) FROM cpanstats WHERE type = 2 AND fulldate like '$date%'");
731 0 0         $self->{rates}{report} = $rows[0]->[0] ? $ADAY / $rows[0]->[0] * 1000 : $ADAY / 10000 * 1000;
732 0           @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(*) FROM uploads WHERE released > $d2 and released < $d1");
733 0 0         $self->{rates}{distro} = $rows[0]->[0] ? $ADAY / $rows[0]->[0] * 1000 : $ADAY / 60 * 1000;
734              
735 0 0         $self->{rates}{report} = 1000 if($self->{rates}{report} < 1000);
736 0 0         $self->{rates}{distro} = 1000 if($self->{rates}{distro} < 1000);
737              
738             # index page
739             my %pages = (
740             index => {
741             LASTMONTH => $self->{dates}{LASTMONTH},
742             report_count => $self->{count}{reports},
743             distro_count => $self->{count}{distros},
744             report_rate => $self->{rates}{report},
745             distro_rate => $self->{rates}{distro}
746             },
747 0           );
748              
749 0           $self->_writepage($_,$pages{$_}) for(keys %pages);
750             }
751              
752             =item * _report_interesting
753              
754             Generates the interesting stats page
755              
756             =cut
757              
758             sub _report_interesting {
759 0     0     my $self = shift;
760 0           my %tvars;
761              
762 0           $self->{parent}->_log("building interesting page");
763              
764 0           $tvars{sizes}{reports} = $self->{sizes}{dir_reports};
765              
766 0           my (@bydist,@byvers);
767 0           my $inx = 20;
768 0 0         for my $dist (sort {$self->{dists}{$b}{ALL} <=> $self->{dists}{$a}{ALL} || $a cmp $b} keys %{$self->{dists}}) {
  0            
  0            
769 0           push @bydist, [$self->{dists}{$dist}{ALL},$dist];
770 0 0         last if(--$inx <= 0);
771             }
772 0           $inx = 20;
773 0 0         for my $dist (sort {$self->{dists}{$b}{IXL} <=> $self->{dists}{$a}{IXL} || $a cmp $b} keys %{$self->{dists}}) {
  0            
  0            
774 0           push @byvers, [$self->{dists}{$dist}{IXL},$dist,$self->{dists}{$dist}{VER}];
775 0 0         last if(--$inx <= 0);
776             }
777              
778 0           $tvars{BYDIST} = \@bydist;
779 0           $tvars{BYVERS} = \@byvers;
780              
781 0           my $type = 'reports';
782 0   0       $self->{count}{$type} ||= 0;
783 0 0         $self->{xrefs}{$type}{$self->{count}{$type}} = $self->{xlast} ? $self->{xlast}{$type} : [];
784              
785 0           for my $key (sort {$b <=> $a} keys %{ $self->{xrefs}{$type} }) {
  0            
  0            
786 0           my @row = @{ $self->{xrefs}{$type}{$key} };
  0            
787              
788 0           $row[0] = $key;
789 0 0         $row[3] = uc $row[3] if($row[3]);
790 0 0 0       ($row[5]) = $self->{parent}->tester($row[5]) if($row[5] && $row[5] =~ /\@/);
791 0           push @{ $tvars{ uc($type) } }, \@row;
  0            
792             }
793              
794 0           my @headings = qw( count grade postdate tester dist version platform perl osname osvers fulldate );
795 0           $tvars{HEADINGS} = \@headings;
796 0           $self->_writepage('interest',\%tvars);
797             }
798              
799             =item * _report_cpan
800              
801             Generates the statistic pages that relate specifically to CPAN.
802              
803             =cut
804              
805             sub _report_cpan {
806 0     0     my $self = shift;
807 0           my (%authors,%distros,%tvars);
808              
809 0           $self->{parent}->_log("building cpan trends page");
810              
811 0           my $directory = $self->{parent}->directory;
812 0           my $results = "$directory/stats";
813 0           mkpath($results);
814              
815 0           my $next = $self->{parent}->{CPANSTATS}->iterator('hash',"SELECT * FROM uploads ORDER BY released");
816 0           while(my $row = $next->()) {
817 0 0         next if($row->{dist} eq 'perl');
818              
819 0           my $date = _parsedate($row->{released});
820 0           $authors{$row->{author}}{count}++;
821 0           $distros{$row->{dist}}{count}++;
822 0           $authors{$row->{author}}{dist}{$row->{dist}}++;
823 0 0         $authors{$row->{author}}{dists}++ if($authors{$row->{author}}{dist}{$row->{dist}} == 1);
824              
825 0           $self->{counts}{$date}{authors}{$row->{author}}++;
826 0           $self->{counts}{$date}{distros}{$row->{dist}}++;
827              
828 0 0         $self->{counts}{$date}{newauthors}++ if($authors{$row->{author}}{count} == 1);
829 0 0         $self->{counts}{$date}{newdistros}++ if($distros{$row->{dist}}{count} == 1);
830              
831 0           $self->{pause}{$date}++;
832             }
833              
834 0 0         my $stat6 = IO::File->new("$results/stats6.txt",'w+') or die "Cannot write to file [$results/stats6.txt]: $!\n";
835 0           print $stat6 "#DATE,AUTHORS,DISTROS\n";
836 0 0         my $stat12 = IO::File->new("$results/stats12.txt",'w+') or die "Cannot write to file [$results/stats12.txt]: $!\n";
837 0           print $stat12 "#DATE,AUTHORS,DISTROS\n";
838              
839 0           for my $date (sort keys %{ $self->{counts} }) {
  0            
840 0           my $authors = scalar(keys %{ $self->{counts}{$date}{authors} });
  0            
841 0           my $distros = scalar(keys %{ $self->{counts}{$date}{distros} });
  0            
842              
843 0   0       $self->{counts}{$date}{newauthors} ||= 0;
844 0   0       $self->{counts}{$date}{newdistros} ||= 0;
845              
846 0           print $stat6 "$date,$authors,$distros\n";
847 0           print $stat12 "$date,$self->{counts}{$date}{newauthors},$self->{counts}{$date}{newdistros}\n";
848              
849             # print $stat6 "$date,$authors\n";
850             # print $stat7 "$date,$distros\n";
851             # print $stat12 "$date,$self->{counts}{$date}{newauthors}\n";
852             # print $stat13 "$date,$self->{counts}{$date}{newdistros}\n";
853             }
854              
855 0           $stat6->close;
856             # $stat7->close;
857 0           $stat12->close;
858             # $stat13->close;
859              
860 0           $tvars{maxyear} = DateTime->now->year;
861 0           $self->_writepage('trends',\%tvars);
862              
863 0           $self->_report_new_distros();
864 0           $self->_report_submissions();
865              
866 0           $self->{parent}->_log("building cpan leader page");
867              
868 0           my $query = 'SELECT x.author,COUNT(x.dist) AS count FROM ixlatest AS x '.
869             'INNER JOIN uploads AS u ON u.dist=x.dist AND u.version=x.version '.
870             "WHERE u.type != 'backpan' GROUP BY x.author";
871 0           my @latest = $self->{parent}->{CPANSTATS}->get_query('hash',$query);
872 0           my (@allcurrent,@alluploads,@allrelease,@alldistros);
873 0           my $inx = 1;
874 0           for my $latest (sort {$b->{count} <=> $a->{count}} @latest) {
  0            
875 0           push @allcurrent, {inx => $inx++, count => $latest->{count}, name => $latest->{author}};
876 0 0         last if($inx > 20);
877             }
878              
879 0           $inx = 1;
880 0 0         for my $author (sort {$authors{$b}{dists} <=> $authors{$a}{dists} || $a cmp $b} keys %authors) {
  0            
881 0           push @alluploads, {inx => $inx++, count => $authors{$author}{dists}, name => $author};
882 0 0         last if($inx > 20);
883             }
884              
885 0           $inx = 1;
886 0 0         for my $author (sort {$authors{$b}{count} <=> $authors{$a}{count} || $a cmp $b} keys %authors) {
  0            
887 0           push @allrelease, {inx => $inx++, count => $authors{$author}{count}, name => $author};
888 0 0         last if($inx > 20);
889             }
890              
891 0           $inx = 1;
892 0 0         for my $distro (sort {$distros{$b}{count} <=> $distros{$a}{count} || $a cmp $b} keys %distros) {
  0            
893 0           push @alldistros, {inx => $inx++, count => $distros{$distro}{count}, name => $distro};
894 0 0         last if($inx > 20);
895             }
896              
897 0           $tvars{allcurrent} = \@allcurrent;
898 0           $tvars{alluploads} = \@alluploads;
899 0           $tvars{allrelease} = \@allrelease;
900 0           $tvars{alldistros} = \@alldistros;
901              
902 0           $self->_writepage('leadercpan',\%tvars);
903              
904              
905 0           $self->{parent}->_log("building cpan interesting stats page (part 1)");
906              
907 0           $tvars{sizes}{cpan} = $self->{sizes}{dir_cpan};
908 0           $tvars{sizes}{backpan} = $self->{sizes}{dir_backpan};
909              
910 0           $tvars{authors}{total} = $self->_count_mailrc();
911 0           my @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(distinct author) FROM uploads");
912 0           $tvars{authors}{active} = $rows[0]->[0];
913 0           $tvars{authors}{inactive} = $tvars{authors}{total} - $rows[0]->[0];
914              
915 0           @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(distinct dist) FROM uploads WHERE type != 'backpan'");
916 0           $tvars{distros}{uploaded1} = $rows[0]->[0];
917 0           $self->{count}{distros} = $rows[0]->[0];
918 0           @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(distinct dist) FROM uploads");
919 0           $tvars{distros}{uploaded2} = $rows[0]->[0];
920 0           $tvars{distros}{uploaded3} = $tvars{distros}{uploaded2} - $tvars{distros}{uploaded1};
921              
922 0           @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(*) FROM uploads WHERE type != 'backpan'");
923 0           $tvars{distros}{uploaded4} = $rows[0]->[0];
924 0           @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(*) FROM uploads");
925 0           $tvars{distros}{uploaded5} = $rows[0]->[0];
926 0           $tvars{distros}{uploaded6} = $tvars{distros}{uploaded5} - $tvars{distros}{uploaded4};
927              
928              
929 0           $self->{parent}->_log("building cpan interesting stats page (part 2)");
930              
931 0           my (%stats,%dists,%pause,%last);
932 0           $next = $self->{parent}->{CPANSTATS}->iterator('hash','SELECT * FROM uploads ORDER BY released');
933 0           while(my $row = $next->()) {
934 0           $stats{vcounter}++;
935 0 0         if($stats{vcounter} % 10000 == 0) {
936 0           $stats{'uploads'}{$stats{vcounter}}{dist} = $row->{dist};
937 0           $stats{'uploads'}{$stats{vcounter}}{vers} = $row->{version};
938 0           $stats{'uploads'}{$stats{vcounter}}{date} = $row->{released};
939 0           $stats{'uploads'}{$stats{vcounter}}{name} = $row->{author};
940             }
941              
942 0           $last{'uploads'}{counter} = $stats{vcounter};
943 0           $last{'uploads'}{dist} = $row->{dist};
944 0           $last{'uploads'}{vers} = $row->{version};
945 0           $last{'uploads'}{date} = $row->{released};
946 0           $last{'uploads'}{name} = $row->{author};
947              
948 0 0         unless($pause{$row->{author}}) {
949 0           $pause{$row->{author}} = 1;
950 0           $stats{pcounter}++;
951 0 0         if($stats{pcounter} % 1000 == 0) {
952 0           $stats{'uploaders'}{$stats{pcounter}}{dist} = $row->{dist};
953 0           $stats{'uploaders'}{$stats{pcounter}}{vers} = $row->{version};
954 0           $stats{'uploaders'}{$stats{pcounter}}{date} = $row->{released};
955 0           $stats{'uploaders'}{$stats{pcounter}}{name} = $row->{author};
956             }
957              
958 0           $last{'uploaders'}{counter} = $stats{pcounter};
959 0           $last{'uploaders'}{dist} = $row->{dist};
960 0           $last{'uploaders'}{vers} = $row->{version};
961 0           $last{'uploaders'}{date} = $row->{released};
962 0           $last{'uploaders'}{name} = $row->{author};
963             }
964              
965 0 0         next if($dists{$row->{dist}});
966              
967 0           $dists{$row->{dist}} = 1;
968 0           $stats{dcounter}++;
969 0 0         if($stats{dcounter} % 5000 == 0) {
970 0           $stats{'distributions'}{$stats{dcounter}}{dist} = $row->{dist};
971 0           $stats{'distributions'}{$stats{dcounter}}{vers} = $row->{version};
972 0           $stats{'distributions'}{$stats{dcounter}}{date} = $row->{released};
973 0           $stats{'distributions'}{$stats{dcounter}}{name} = $row->{author};
974             }
975              
976 0           $last{'distributions'}{counter} = $stats{dcounter};
977 0           $last{'distributions'}{dist} = $row->{dist};
978 0           $last{'distributions'}{vers} = $row->{version};
979 0           $last{'distributions'}{date} = $row->{released};
980 0           $last{'distributions'}{name} = $row->{author};
981             }
982              
983 0           for my $type (qw(distributions uploads uploaders)) {
984 0           my @list;
985 0           $stats{$type}{$last{$type}{counter}} = $last{$type};
986 0           for my $count (sort {$a <=> $b} keys %{$stats{$type}}) {
  0            
  0            
987 0           my @date = localtime($stats{$type}{$count}{date});
988 0           my $date = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $date[5]+1900, $date[4]+1, $date[3], $date[2], $date[1], $date[0] ;
989 0           $stats{$type}{$count}{counter} = $count;
990 0           $stats{$type}{$count}{date} = $date;
991 0           push @list, $stats{$type}{$count};
992             }
993 0 0         $tvars{$type} = \@list if(@list);
994             }
995              
996 0           $self->_writepage('statscpan',\%tvars);
997              
998              
999 0           $self->{parent}->_log("building cpan/backpan 100s");
1000              
1001             # calculate CPAN 100 data
1002 0           $self->_count_mailrc();
1003 0           @rows = $self->{parent}->{CPANSTATS}->get_query('hash',"SELECT t.author,t.count FROM (SELECT author,count(distinct dist) AS count FROM uploads WHERE type!='backpan' GROUP BY author ORDER BY count DESC LIMIT 100) AS t WHERE t.count >= 100");
1004 0           my $fh = IO::File->new(">$results/cpan100.csv");
1005 0           printf $fh "# DATE: %s\n", DateTime->now->datetime;
1006 0           print $fh "#Pause,Count,Name\n";
1007 0           for my $row (@rows) {
1008 0   0       printf $fh "%s,%d,%s\n", $row->{author}, $row->{count}, $self->{alias}{$row->{author}}||'???';
1009             }
1010 0           $fh->close;
1011              
1012             # calculate BACKCPAN 100 data
1013 0           @rows = $self->{parent}->{CPANSTATS}->get_query('hash',"SELECT t.author,t.count FROM (SELECT author,count(distinct dist) AS count FROM uploads GROUP BY author ORDER BY count DESC LIMIT 100) AS t WHERE t.count >= 100");
1014 0           $fh = IO::File->new(">$results/backpan100.csv");
1015 0           printf $fh "# DATE: %s\n", DateTime->now->datetime;
1016 0           print $fh "#Pause,Count,Name\n";
1017 0           for my $row (@rows) {
1018 0   0       printf $fh "%s,%d,%s\n", $row->{author}, $row->{count}, $self->{alias}{$row->{author}}||'???';
1019             }
1020 0           $fh->close;
1021             }
1022              
1023             sub _report_new_distros {
1024 0     0     my $self = shift;
1025              
1026 0           $self->{parent}->_log("building new distro pages");
1027              
1028 0           my (%seen,%allversions,%newversions);
1029 0           my $start_year = 1995;
1030 0           my $start_month = 8;
1031 0           my $this_year = DateTime->now->year;
1032 0           my $sql = 'select author,dist,version,from_unixtime(released) as reldate from uploads where released >= ? AND released < ? order by released';
1033              
1034 0           for my $year (1995 .. $this_year) {
1035 0           my $tvars = { template => 'newdistros', year => $year };
1036              
1037 0           for my $month (1 .. 12) {
1038 0 0 0       next if($year == $start_year && $month < $start_month);
1039              
1040 0           my $thismon = DateTime->new( year => $year, month => $month, day => 1, hour => 0, minute => 0, second => 0);
1041 0           my $nextmon = DateTime->new( year => $thismon->clone->add( months => 1 )->year, month => $thismon->clone->add( months => 1 )->month, day => 1, hour => 0, minute => 0, second => 0);
1042              
1043 0 0         last if($thismon > DateTime->now);
1044              
1045 0           $tvars->{newdistros}{$month}{month} = $thismon->month_name;
1046 0           $tvars->{newdistros}{$month}{counter} = 0;
1047              
1048 0           my @rows = $self->{parent}->{CPANSTATS}->get_query('hash',$sql,$thismon->epoch(),$nextmon->epoch());
1049 0           for my $row (@rows) {
1050 0           $allversions{$row->{version}}++;
1051              
1052 0 0         next if($seen{$row->{dist}});
1053              
1054 0           $seen{$row->{dist}} = 1;
1055 0           push @{$tvars->{newdistros}{$month}{dists}},
1056             {
1057             author => $row->{author},
1058             dist => $row->{dist},
1059             version => $row->{version},
1060             reldate => $row->{reldate}
1061 0           };
1062              
1063 0           $tvars->{newdistros}{$month}{counter}++;
1064 0           $newversions{$row->{version}}++;
1065             }
1066             }
1067              
1068 0           $self->_writepage("newdistros/$year",$tvars);
1069             }
1070              
1071 0           $self->{parent}->_log("building new distro versions page");
1072              
1073 0           my (@allversions,@newversions);
1074 0 0         for my $v (sort {$allversions{$b} <=> $allversions{$a} || $a cmp $b} keys %allversions) {
  0            
1075 0           push @allversions, { version => $v, count => $allversions{$v} };
1076             }
1077 0           my $tvars = { template => 'versions', type => 'All', versions => \@allversions };
1078 0           $self->_writepage("newdistros/allversions",$tvars);
1079              
1080 0 0         for my $v (sort {$newversions{$b} <=> $newversions{$a} || $a cmp $b} keys %newversions) {
  0            
1081 0           push @newversions, { version => $v, count => $newversions{$v} };
1082             }
1083 0           $tvars = { template => 'versions', type => 'New', versions => \@newversions };
1084 0           $self->_writepage("newdistros/newversions",$tvars);
1085             }
1086              
1087             sub _report_submissions {
1088 0     0     my $self = shift;
1089              
1090 0           $self->{parent}->_log("building submission data files");
1091              
1092 0           my $sql = 'select from_unixtime(released) as reldate from uploads';
1093              
1094 0           my $now = DateTime->now;
1095 0           my (%hours,%days,%months,%dotw,%tvars);
1096              
1097 0           my $next = $self->{parent}->{CPANSTATS}->iterator('hash',$sql);
1098 0           while( my $row = $next->() ) {
1099 0 0 0       next unless($row->{reldate} && $row->{reldate} =~ /^(\d+)\-(\d+)\-(\d+).(\d+):(\d+):(\d+)/);
1100 0           my ($year,$month,$day,$hour,$minute,$second) = ($1,$2,$3,$4,$5,$6);
1101              
1102 0           my $date = DateTime->new( year => $year, month => $month, day => $day, hour => $hour, minute => $minute, second => $second );
1103 0           my $dotw = $date->day_of_week;
1104              
1105 0           $months{that}{$month}++;
1106 0           $dotw{that}{$dotw}++;
1107 0           $days{that}{$day}++;
1108 0           $hours{that}{$hour}++;
1109              
1110 0 0         if($year != $now->year) {
    0          
1111 0           $months{this}{$month}++;
1112 0           $dotw{this}{$dotw}++;
1113             } elsif($date->week_number != $now->week_number) {
1114 0           $dotw{this}{$dotw}++;
1115             }
1116              
1117 0 0 0       if(( $year != $now->year) ||
      0        
1118             ( $year == $now->year && $month != $now->month) ) {
1119 0           $days{this}{$day}++;
1120             }
1121              
1122 0 0 0       if(( $year != $now->year) ||
      0        
      0        
      0        
      0        
1123             ( $year == $now->year && $month != $now->month) ||
1124             ( $year == $now->year && $month == $now->month && $day != $now->day) ) {
1125 0           $hours{this}{$hour}++;
1126             }
1127             }
1128              
1129 0           my $directory = $self->{parent}->directory;
1130 0           my $results = "$directory/rates";
1131 0           mkpath($results);
1132              
1133 0           $self->{parent}->_log("writing $results/submit1.txt");
1134 0           my $fh = IO::File->new(">$results/submit1.txt");
1135 0           print $fh "#INDEX,EXCLUSIVE,INCLUSIVE\n";
1136 0           for my $month (sort {$a <=> $b} keys %{$months{this}}) {
  0            
  0            
1137 0           printf $fh "%d,%d,%d\n", $month, $months{this}{$month}, $months{that}{$month};
1138             }
1139 0           $fh->close;
1140              
1141 0           $self->{parent}->_log("writing $results/submit2.txt");
1142 0           $fh = IO::File->new(">$results/submit2.txt");
1143 0           print $fh "#INDEX,EXCLUSIVE,INCLUSIVE\n";
1144 0           for my $dotw (sort {$a <=> $b} keys %{$dotw{this}}) {
  0            
  0            
1145 0           printf $fh "%d,%d,%d\n", $dotw, $dotw{this}{$dotw}, $dotw{that}{$dotw};
1146             }
1147 0           $fh->close;
1148              
1149 0           $self->{parent}->_log("writing $results/submit3.txt");
1150 0           $fh = IO::File->new(">$results/submit3.txt");
1151 0           print $fh "#INDEX,EXCLUSIVE,INCLUSIVE\n";
1152 0           for my $day (sort {$a <=> $b} keys %{$days{this}}) {
  0            
  0            
1153 0           printf $fh "%d,%d,%d\n", $day, $days{this}{$day}, $days{that}{$day};
1154             }
1155 0           $fh->close;
1156              
1157 0           $self->{parent}->_log("writing $results/submit4.txt");
1158 0           $fh = IO::File->new(">$results/submit4.txt");
1159 0           print $fh "#INDEX,EXCLUSIVE,INCLUSIVE\n";
1160 0           for my $hour (sort {$a <=> $b} keys %{$hours{this}}) {
  0            
  0            
1161 0           printf $fh "%d,%d,%d\n", $hour, $hours{this}{$hour}, $hours{that}{$hour};
1162             }
1163 0           $fh->close;
1164              
1165 0           $self->_writepage('rates',\%tvars);
1166             }
1167              
1168             sub _build_noreports {
1169 0     0     my $self = shift;
1170 0           my $grace = time - 2419200;
1171            
1172 0           my %phrasebook = (
1173             'LATEST' => 'SELECT x.* FROM ixlatest AS x WHERE oncpan=1',
1174             'NOREPORTS1' => 'SELECT x.* FROM ixlatest AS x LEFT JOIN stats_store AS s ON x.dist=s.dist WHERE oncpan=1 AND s.dist IS NULL ORDER BY x.dist',
1175             'NOREPORTS2' => 'SELECT dist,osname FROM stats_store'
1176             );
1177              
1178             # load the distributions we can ignore from these lists
1179 0           my $noreports = $self->{parent}->noreports();
1180              
1181             # load the current list of known OSes
1182 0           my $osnames = $self->{parent}->osnames();
1183 0           my @osnames = map { { osname => $_, ostitle => $osnames->{$_} } } sort {$osnames->{$a} cmp $osnames->{$b}} keys %$osnames;
  0            
  0            
1184              
1185             # load all the latest distributions currently on CPAN
1186 0           my (@rows,%dists,%osmap);
1187 0           my $next = $self->{parent}->{CPANSTATS}->iterator('hash',$phrasebook{LATEST});
1188 0           while(my $row = $next->()) {
1189 0 0 0       next if($noreports && $row->{dist} =~ /^$noreports$/);
1190 0 0         next if($dists{$row->{dist}});
1191 0 0         next if($row->{released} > $grace); # ignore dists released within the grace period
1192              
1193 0           my @dt = localtime($row->{released});
1194 0           $row->{datetime} = sprintf "%04d-%02d-%02d", $dt[5]+1900,$dt[4]+1,$dt[3];
1195 0           $dists{$row->{dist}} = $row;
1196              
1197 0           $osmap{$row->{dist}}{$_->{osname}} = 1 for(@osnames);
1198             }
1199              
1200             # load all the dists with no OS reports at all
1201 0           $next = $self->{parent}->{CPANSTATS}->iterator('hash',$phrasebook{NOREPORTS1});
1202 0           while(my $row = $next->()) {
1203 0 0         next unless($dists{$row->{dist}});
1204              
1205 0           push @rows, $dists{$row->{dist}};
1206             }
1207              
1208             # write HTML & CSV files for dists with no reports at all
1209 0           my $tvars = { rows => \@rows, rowcount => scalar(@rows), template => 'noreports', osnames => \@osnames, ostitle => 'ALL' };
1210 0           $self->_writepage('noreports/all',$tvars);
1211 0           $tvars = { rows => \@rows, rowcount => scalar(@rows), template => 'noreports', extension => 'csv', osnames => \@osnames, ostitle => 'ALL' };
1212 0           $self->_writepage('noreports/all',$tvars);
1213              
1214              
1215             # load all the dist to osname report mappings from the stats_store
1216 0           $next = $self->{parent}->{CPANSTATS}->iterator('hash',$phrasebook{NOREPORTS2});
1217 0           while(my $row = $next->()) {
1218 0 0         next unless($dists{$row->{dist}});
1219              
1220 0           delete $osmap{$row->{dist}}{$row->{osname}};
1221             }
1222              
1223             # loop for each OS
1224 0           for my $os (@osnames) {
1225 0           @rows = ();
1226 0           for my $dist (sort keys %dists) {
1227 0 0         next unless($osmap{$dist}{$os->{osname}});
1228              
1229 0           push @rows, $dists{$dist};
1230             }
1231              
1232             # write HTML & CSV files for dists without reports for specific OS
1233 0           $tvars = { rows => \@rows, rowcount => scalar(@rows), template => 'noreports', osnames => \@osnames, ostitle => $os->{ostitle}, osname => $os->{osname} };
1234 0           $self->_writepage('noreports/'.$os->{osname},$tvars);
1235 0           $tvars = { rows => \@rows, rowcount => scalar(@rows), template => 'noreports', extension => 'csv', osnames => \@osnames, ostitle => $os->{ostitle} };
1236 0           $self->_writepage('noreports/'.$os->{osname},$tvars);
1237             }
1238             }
1239              
1240             sub _missing_in_action {
1241 0     0     my $self = shift;
1242 0           my (%tvars,%missing,@missing);
1243              
1244 0           $self->{parent}->_log("building missing in action page");
1245              
1246 0           my $missing = $self->{parent}->missing();
1247 0 0         return unless(-f $missing);
1248 0 0         my $fh = IO::File->new($missing) or return;
1249 0           while(<$fh>) {
1250 0           chomp;
1251 0           my ($pauseid,$timestamp,$reason) = /^([a-z]+)[ \t]+([^+]+\+0[01]00) (.*)/i;
1252 0 0         next unless($pauseid);
1253 0           $reason =~ s/</&lt;/g;
1254 0           $reason =~ s/>/&gt;/g;
1255 0           $missing{$pauseid}{timestamp} = $timestamp;
1256 0           $missing{$pauseid}{reason} = $reason;
1257             }
1258 0           $fh->close;
1259              
1260 0           for my $pauseid (sort keys %missing) {
1261 0           push @missing, { pauseid => $pauseid, timestamp => $missing{$pauseid}{timestamp}, reason => $missing{$pauseid}{reason} };
1262             }
1263              
1264 0 0         $tvars{missing} = \@missing if(@missing);
1265 0           $self->_writepage('missing',\%tvars);
1266             }
1267              
1268             sub _build_osname_matrix {
1269 0     0     my $self = shift;
1270              
1271 0           my %tvars = (template => 'osmatrix', FULL => 1, MONTH => 0);
1272 0           $self->{parent}->_log("building OS matrix - 1");
1273 0           my $CONTENT = $self->_osname_matrix($self->{versions},'all',1);
1274 0           $tvars{CONTENT} = $CONTENT;
1275 0           $self->_writepage('osmatrix-full',\%tvars);
1276              
1277 0           %tvars = (template => 'osmatrix', FULL => 1, MONTH => 0, layout => 'layout-wide');
1278 0           $tvars{CONTENT} = $CONTENT;
1279 0           $self->{parent}->_log("building OS matrix - 2");
1280 0           $self->_writepage('osmatrix-full-wide',\%tvars);
1281              
1282 0           %tvars = (template => 'osmatrix', FULL => 1, MONTH => 1);
1283 0           $self->{parent}->_log("building OS matrix - 3");
1284 0           $CONTENT = $self->_osname_matrix($self->{versions},'month',1);
1285 0           $tvars{CONTENT} = $CONTENT;
1286 0           $self->_writepage('osmatrix-full-month',\%tvars);
1287              
1288 0           %tvars = (template => 'osmatrix', FULL => 1, MONTH => 1, layout => 'layout-wide');
1289 0           $tvars{CONTENT} = $CONTENT;
1290 0           $self->{parent}->_log("building OS matrix - 4");
1291 0           $self->_writepage('osmatrix-full-month-wide',\%tvars);
1292              
1293 0           my @vers = grep {!/^5\.(11|9|7)\./} @{$self->{versions}};
  0            
  0            
1294              
1295 0           %tvars = (template => 'osmatrix', FULL => 0, MONTH => 0);
1296 0           $self->{parent}->_log("building OS matrix - 5");
1297 0           $CONTENT = $self->_osname_matrix(\@vers,'all',0);
1298 0           $tvars{CONTENT} = $CONTENT;
1299 0           $self->_writepage('osmatrix',\%tvars);
1300              
1301 0           %tvars = (template => 'osmatrix', FULL => 0, MONTH => 0, layout => 'layout-wide');
1302 0           $tvars{CONTENT} = $CONTENT;
1303 0           $self->{parent}->_log("building OS matrix - 6");
1304 0           $self->_writepage('osmatrix-wide',\%tvars);
1305              
1306 0           %tvars = (template => 'osmatrix', FULL => 0, MONTH => 1);
1307 0           $self->{parent}->_log("building OS matrix - 7");
1308 0           $CONTENT = $self->_osname_matrix(\@vers,'month',0);
1309 0           $tvars{CONTENT} = $CONTENT;
1310 0           $self->_writepage('osmatrix-month',\%tvars);
1311              
1312 0           %tvars = (template => 'osmatrix', FULL => 0, MONTH => 1, layout => 'layout-wide');
1313 0           $tvars{CONTENT} = $CONTENT;
1314 0           $self->{parent}->_log("building OS matrix - 8");
1315 0           $self->_writepage('osmatrix-month-wide',\%tvars);
1316             }
1317              
1318             sub _osname_matrix {
1319 0     0     my $self = shift;
1320 0 0         my $vers = shift or return '';
1321 0           my $type = shift;
1322 0   0       my $full = shift || 0;
1323 0 0         return '' unless(@$vers);
1324              
1325 0           my %totals;
1326 0           for my $osname (sort keys %{$self->{osys}}) {
  0            
1327 0 0         if($type eq 'month') {
1328 0           my $check = 0;
1329 0 0         for my $perl (@$vers) { $check++ if(defined $self->{osys}{$osname}{$perl}{month}{$self->{dates}{LASTMONTH}}) }
  0            
1330 0 0         next if($check == 0);
1331             }
1332 0           for my $perl (@$vers) {
1333             my $count = defined $self->{osys}{$osname}{$perl}{$type}
1334             ? ($type eq 'month'
1335 0           ? scalar(keys %{$self->{osys}{$osname}{$perl}{month}{$self->{dates}{LASTMONTH}}})
1336 0 0         : scalar(keys %{$self->{osys}{$osname}{$perl}{$type}}))
  0 0          
1337             : 0;
1338 0   0       $count ||= 0;
1339 0           $totals{os}{$osname} += $count;
1340 0           $totals{perl}{$perl} += $count;
1341             }
1342             }
1343              
1344 0           my $index = 0;
1345             my $content =
1346             "\n"
1347             . '<table class="matrix" summary="OS/Perl Matrix">'
1348             . "\n"
1349             . '<tr><th>OS/Perl</th><th></th><th>'
1350             . join( "</th><th>", @$vers )
1351             . '</th><th></th><th>OS/Perl</th></tr>'
1352             . "\n"
1353             . '<tr><th></th><th class="totals">Totals</th><th class="totals">'
1354 0 0         . join( '</th><th class="totals">', map {$totals{perl}{$_}||0} @$vers )
  0            
1355             . '</th><th class="totals">Totals</th><th></th></tr>';
1356              
1357 0 0         for my $osname (sort {$totals{os}{$b} <=> $totals{os}{$a} || $a cmp $b} keys %{$totals{os}}) {
  0            
  0            
1358 0 0         if($type eq 'month') {
1359 0           my $check = 0;
1360 0 0         for my $perl (@$vers) { $check++ if(defined $self->{osys}{$osname}{$perl}{month}{$self->{dates}{LASTMONTH}}) }
  0            
1361 0 0         next if($check == 0);
1362             }
1363 0           $content .= "\n" . '<tr><th>' . $osname . '</th><th class="totals">' . $totals{os}{$osname} . '</th>';
1364 0           for my $perl (@$vers) {
1365             my $count = defined $self->{osys}{$osname}{$perl}{$type}
1366             ? ($type eq 'month'
1367 0           ? scalar(keys %{$self->{osys}{$osname}{$perl}{month}{$self->{dates}{LASTMONTH}}})
1368 0 0         : scalar(keys %{$self->{osys}{$osname}{$perl}{$type}}))
  0 0          
1369             : 0;
1370 0   0       $count ||= 0;
1371 0 0         if($count) {
1372 0 0         if($self->{list}{osname}{$osname}{$perl}{$type}) {
1373 0           $index = $self->{list}{osname}{$osname}{$perl}{$type};
1374             } else {
1375 0 0         my %tvars = (template => 'distlist', OS => 1, MONTH => ($type eq 'month' ? 1 : 0), FULL => $full);
1376 0           my @list = sort keys %{$self->{osys}{$osname}{$perl}{$type}};
  0            
1377 0           $tvars{dists} = \@list;
1378 0           $tvars{vplatform} = $osname;
1379 0           $tvars{vperl} = $perl;
1380 0           $tvars{count} = $count;
1381              
1382 0           $index = join('-','osys', $type, $osname, $perl);
1383 0           $index =~ s/[^-.\w]/-/g;
1384 0           $index = 'matrix/' . $index;
1385 0           $self->{list}{osname}{$osname}{$perl}{$type} = $index;
1386 0           $self->_writepage($index,\%tvars);
1387             }
1388             }
1389              
1390 0 0         my $number = ($type eq 'month' ? $self->{osname}{$osname}{$perl}{month}{$self->{dates}{LASTMONTH}} : $self->{osname}{$osname}{$perl}{$type});
1391 0   0       $number ||= 0;
1392 0           my $class = 'none';
1393 0 0         $class = 'some' if($number > 0);
1394 0 0         $class = 'more' if($number > $matrix_limits{$type}->[0]);
1395 0 0         $class = 'lots' if($number > $matrix_limits{$type}->[1]);
1396              
1397             # count = number of distributions with a pass
1398             # number = number of reports submitted for that platform/perl
1399 0 0         $content .= qq{<td class="$class">}
1400             . ($count ? qq|<a href="$index.html" title="Distribution List for $osname/$perl">$count</a><br />$number| : '-')
1401             . '</td>';
1402             }
1403 0           $content .= '<th class="totals">' . $totals{os}{$osname} . '</th><th>' . $osname . '</th>';
1404 0           $content .= '</tr>';
1405             }
1406              
1407             $content .=
1408             "\n"
1409             . '<tr><th></th><th class="totals">Totals</th><th class="totals">'
1410 0 0         . join( '</th><th class="totals">', map {$totals{perl}{$_}||0} @$vers )
  0            
1411             . '</th><th class="totals">Totals</th><th></th></tr>'
1412             . "\n"
1413             . '<tr><th>OS/Perl</th><th></th><th>'
1414             . join( "</th><th>", @$vers )
1415             . '</th><th></th><th>OS/Perl</th></tr>'
1416             . "\n" .
1417             '</table>';
1418              
1419 0           return $content;
1420             }
1421              
1422             sub _build_platform_matrix {
1423 0     0     my $self = shift;
1424              
1425 0           my %tvars = (template => 'pmatrix', FULL => 1, MONTH => 0);
1426 0           $self->{parent}->_log("building platform matrix - 1");
1427 0           my $CONTENT = $self->_platform_matrix($self->{versions},'all',1);
1428 0           $tvars{CONTENT} = $CONTENT;
1429 0           $self->_writepage('pmatrix-full',\%tvars);
1430              
1431 0           %tvars = (template => 'pmatrix', FULL => 1, MONTH => 0, layout => 'layout-wide');
1432 0           $tvars{CONTENT} = $CONTENT;
1433 0           $self->{parent}->_log("building platform matrix - 2");
1434 0           $self->_writepage('pmatrix-full-wide',\%tvars);
1435              
1436 0           %tvars = (template => 'pmatrix', FULL => 1, MONTH => 1);
1437 0           $self->{parent}->_log("building platform matrix - 3");
1438 0           $CONTENT = $self->_platform_matrix($self->{versions},'month',1);
1439 0           $tvars{CONTENT} = $CONTENT;
1440 0           $self->_writepage('pmatrix-full-month',\%tvars);
1441              
1442 0           %tvars = (template => 'pmatrix', FULL => 1, MONTH => 1, layout => 'layout-wide');
1443 0           $tvars{CONTENT} = $CONTENT;
1444 0           $self->{parent}->_log("building platform matrix - 4");
1445 0           $self->_writepage('pmatrix-full-month-wide',\%tvars);
1446              
1447 0           my @vers = grep {!/^5\.(11|9|7)\./} @{$self->{versions}};
  0            
  0            
1448              
1449 0           %tvars = (template => 'pmatrix', FULL => 0, MONTH => 0);
1450 0           $self->{parent}->_log("building platform matrix - 5");
1451 0           $CONTENT = $self->_platform_matrix(\@vers,'all',0);
1452 0           $tvars{CONTENT} = $CONTENT;
1453 0           $self->_writepage('pmatrix',\%tvars);
1454              
1455 0           %tvars = (template => 'pmatrix', FULL => 0, MONTH => 0, layout => 'layout-wide');
1456 0           $tvars{CONTENT} = $CONTENT;
1457 0           $self->{parent}->_log("building platform matrix - 6");
1458 0           $self->_writepage('pmatrix-wide',\%tvars);
1459              
1460 0           %tvars = (template => 'pmatrix', FULL => 0, MONTH => 1);
1461 0           $self->{parent}->_log("building platform matrix - 7");
1462 0           $CONTENT = $self->_platform_matrix(\@vers,'month',0);
1463 0           $tvars{CONTENT} = $CONTENT;
1464 0           $self->_writepage('pmatrix-month',\%tvars);
1465              
1466 0           %tvars = (template => 'pmatrix', FULL => 0, MONTH => 1, layout => 'layout-wide');
1467 0           $tvars{CONTENT} = $CONTENT;
1468 0           $self->{parent}->_log("building platform matrix - 8");
1469 0           $self->_writepage('pmatrix-month-wide',\%tvars);
1470             }
1471              
1472             sub _platform_matrix {
1473 0     0     my $self = shift;
1474 0 0         my $vers = shift or return '';
1475 0           my $type = shift;
1476 0   0       my $full = shift || 0;
1477 0 0         return '' unless(@$vers);
1478              
1479 0           my %totals;
1480 0           for my $platform (sort keys %{$self->{pass}}) {
  0            
1481 0 0         if($type eq 'month') {
1482 0           my $check = 0;
1483 0 0         for my $perl (@$vers) { $check++ if(defined $self->{pass}{$platform}{$perl}{month}{$self->{dates}{LASTMONTH}}) }
  0            
1484 0 0         next if($check == 0);
1485             }
1486 0           for my $perl (@$vers) {
1487             my $count = defined $self->{pass}{$platform}{$perl}{$type}
1488             ? ($type eq 'month'
1489 0           ? scalar(keys %{$self->{pass}{$platform}{$perl}{month}{$self->{dates}{LASTMONTH}}})
1490 0 0         : scalar(keys %{$self->{pass}{$platform}{$perl}{$type}}))
  0 0          
1491             : 0;
1492 0   0       $count ||= 0;
1493 0           $totals{platform}{$platform} += $count;
1494 0           $totals{perl}{$perl} += $count;
1495             }
1496             }
1497              
1498 0           my $index = 0;
1499             my $content =
1500             "\n"
1501             . '<table class="matrix" summary="Platform/Perl Matrix">'
1502             . "\n"
1503             . '<tr><th>Platform/Perl</th><th></th><th>'
1504             . join( "</th><th>", @$vers )
1505             . '</th><th></th><th>Platform/Perl</th></tr>'
1506             . "\n"
1507             . '<tr><th></th><th class="totals">Totals</th><th class="totals">'
1508 0 0         . join( '</th><th class="totals">', map {$totals{perl}{$_}||0} @$vers )
  0            
1509             . '</th><th class="totals">Totals</th><th></th></tr>';
1510              
1511 0 0         for my $platform (sort {$totals{platform}{$b} <=> $totals{platform}{$a} || $a cmp $b} keys %{$totals{platform}}) {
  0            
  0            
1512 0 0         if($type eq 'month') {
1513 0           my $check = 0;
1514 0 0         for my $perl (@$vers) { $check++ if(defined $self->{pass}{$platform}{$perl}{month}{$self->{dates}{LASTMONTH}}) }
  0            
1515 0 0         next if($check == 0);
1516             }
1517 0           $content .= "\n" . '<tr><th>' . $platform . '</th><th class="totals">' . $totals{platform}{$platform} . '</th>';
1518 0           for my $perl (@$vers) {
1519             my $count = defined $self->{pass}{$platform}{$perl}{$type}
1520             ? ($type eq 'month'
1521 0           ? scalar(keys %{$self->{pass}{$platform}{$perl}{month}{$self->{dates}{LASTMONTH}}})
1522 0 0         : scalar(keys %{$self->{pass}{$platform}{$perl}{$type}}))
  0 0          
1523             : 0;
1524 0   0       $count ||= 0;
1525 0 0         if($count) {
1526 0 0         if($self->{list}{platform}{$platform}{$perl}{$type}) {
1527 0           $index = $self->{list}{platform}{$platform}{$perl}{$type};
1528             } else {
1529 0 0         my %tvars = (template => 'distlist', OS => 0, MONTH => ($type eq 'month' ? 1 : 0), FULL => $full);
1530 0           my @list = sort keys %{$self->{pass}{$platform}{$perl}{$type}};
  0            
1531 0           $tvars{dists} = \@list;
1532 0           $tvars{vplatform} = $platform;
1533 0           $tvars{vperl} = $perl;
1534 0           $tvars{count} = $count;
1535              
1536 0           $index = join('-','platform', $type, $platform, $perl);
1537 0           $index =~ s/[^-.\w]/-/g;
1538 0           $index = 'matrix/' . $index;
1539 0           $self->{list}{platform}{$platform}{$perl}{$type} = $index;
1540 0           $self->_writepage($index,\%tvars);
1541             }
1542             }
1543              
1544 0 0         my $number = ($type eq 'month' ? $self->{platform}{$platform}{$perl}{month}{$self->{dates}{LASTMONTH}} : $self->{platform}{$platform}{$perl}{$type});
1545 0   0       $number ||= 0;
1546 0           my $class = 'none';
1547 0 0         $class = 'some' if($number > 0);
1548 0 0         $class = 'more' if($number > $matrix_limits{$type}->[0]);
1549 0 0         $class = 'lots' if($number > $matrix_limits{$type}->[1]);
1550              
1551             # count = number of distributions with a pass
1552             # number = number of reports submitted for that platform/perl
1553 0 0         $content .= qq{<td class="$class">}
1554             . ($count ? qq|<a href="$index.html" title="Distribution List for $platform/$perl">$count</a><br />$number| : '-')
1555             . '</td>';
1556             }
1557 0           $content .= '<th class="totals">' . $totals{platform}{$platform} . '</th><th>' . $platform . '</th>';
1558 0           $content .= '</tr>';
1559             }
1560             $content .=
1561             "\n"
1562             . '<tr><th></th><th class="totals">Totals</th><th class="totals">'
1563 0 0         . join( '</th><th class="totals">', map {$totals{perl}{$_}||0} @$vers )
  0            
1564             . '</th><th class="totals">Totals</th><th></th></tr>'
1565             . "\n"
1566             . '<tr><th>Platform/Perl</th><th></th><th>'
1567             . join( "</th><th>", @$vers )
1568             . '</th><th></th><th>Platform/Perl</th></tr>'
1569             . "\n"
1570             . '</table>';
1571              
1572 0           return $content;
1573             }
1574              
1575             # Notes:
1576             #
1577             # * use a JSON store (e.g. cpanstats-platform.json)
1578             # * find the last month stored
1579             # * rebuild from last month to current month
1580             # * store JSON data
1581              
1582             sub _build_monthly_stats {
1583 0     0     my $self = shift;
1584 0           my (%tvars,%stats,%testers,%monthly);
1585 0           my %templates = (
1586             platform => 'mplatforms',
1587             osname => 'mosname',
1588             perl => 'mperls',
1589             tester => 'mtesters'
1590             );
1591              
1592 0           $self->{parent}->_log("building monthly tables");
1593              
1594 0           my $query = q!SELECT postdate,%s,count(id) AS count FROM cpanstats ! .
1595             q!WHERE type = 2 %s ! .
1596             q!GROUP BY postdate,%s ORDER BY postdate,count DESC,%s!;
1597              
1598 0           for my $type (qw(platform osname perl)) {
1599 0           $self->{parent}->_log("building monthly $type table");
1600 0           (%tvars,%stats,%monthly) = ();
1601 0           my $postdate = '';
1602              
1603 0           my $json = $self->storage_read($type);
1604 0 0         if($json) {
1605 0           my $last = 0;
1606 0           for my $date (keys %{ $json->{monthly} }) {
  0            
1607 0 0         $last = $date if($date > $last);
1608             }
1609              
1610 0           delete $json->{$_}{$last} for(qw(monthly stats));
1611              
1612 0           %monthly = %{ $json->{monthly} };
  0            
1613 0           %stats = %{ $json->{stats} };
  0            
1614              
1615 0 0         $postdate = "AND postdate >= '$last'" if($last);
1616             }
1617              
1618 0           my $sql = sprintf $query, $type, $postdate, $type, $type;
1619 0           my $next = $self->{parent}->{CPANSTATS}->iterator('hash',$sql);
1620 0           while(my $row = $next->()) {
1621 0           $monthly{$row->{postdate}}{$type}{$row->{$type}} = 1;
1622 0 0         $row->{$type} = $self->{parent}->osname($row->{$type}) if($type eq 'osname');
1623 0           push @{$stats{$row->{postdate}}{list}}, "[$row->{count}] $row->{$type}";
  0            
1624             }
1625              
1626 0           for my $date (sort {$b <=> $a} keys %stats) {
  0            
1627 0           $stats{$date}{count} = scalar(@{$stats{$date}{list}});
  0            
1628 0           push @{$tvars{STATS}}, [$date,$stats{$date}{count},join(', ',@{$stats{$date}{list}})];
  0            
  0            
1629             }
1630 0           $self->_writepage($templates{$type},\%tvars);
1631              
1632             # remember monthly counts for monthly files later
1633 0           for my $date (keys %monthly) {
1634 0           $self->{monthly}{$date}{$type} = keys %{ $monthly{$date}{$type} };
  0            
1635             }
1636              
1637             # store data
1638 0           my $hash = { monthly => \%monthly, stats => \%stats };
1639 0           $self->storage_write($type,$hash);
1640             }
1641              
1642             {
1643 0           my $type = 'tester';
  0            
1644 0           $self->{parent}->_log("building monthly $type table");
1645 0           (%tvars,%stats,%monthly) = ();
1646 0           my $postdate = '';
1647              
1648 0           my $json = $self->storage_read($type);
1649 0 0         if($json) {
1650 0           my $last = 0;
1651 0           for my $date (keys %{ $json->{monthly} }) {
  0            
1652 0 0         $last = $date if($date > $last);
1653             }
1654              
1655 0           delete $json->{$_}{$last} for(qw(monthly stats));
1656              
1657 0           %monthly = %{ $json->{monthly} };
  0            
1658 0           %stats = %{ $json->{stats} };
  0            
1659              
1660 0 0         $postdate = "AND postdate >= '$last'" if($last);
1661             }
1662              
1663 0           my $sql = sprintf $query, $type, $postdate, $type, $type;
1664 0           my $next = $self->{parent}->{CPANSTATS}->iterator('hash',$sql);
1665 0           while(my $row = $next->()) {
1666 0           my ($name) = $self->{parent}->tester($row->{tester});
1667 0           $testers{$name} += $row->{count};
1668 0           $stats{$row->{postdate}}{list}{$name} += $row->{count};
1669 0           $monthly{$row->{postdate}}{$type}{$name} = 1;
1670             }
1671              
1672 0           for my $date (sort {$b <=> $a} keys %stats) {
  0            
1673 0           $stats{$date}{count} = keys %{$stats{$date}{list}};
  0            
1674 0           push @{$tvars{STATS}}, [$date,$stats{$date}{count},
1675             join(', ',
1676 0           map {"[$stats{$date}{list}{$_}] $_"}
1677 0 0         sort {$stats{$date}{list}{$b} <=> $stats{$date}{list}{$a} || $a cmp $b}
1678 0           keys %{$stats{$date}{list}})];
  0            
1679             }
1680 0           $self->_writepage($templates{$type},\%tvars);
1681              
1682             # remember monthly counts for monthly files later
1683 0           for my $date (keys %monthly) {
1684 0           $self->{monthly}{$date}{$type} = keys %{ $monthly{$date}{$type} };
  0            
1685             }
1686              
1687             # store data
1688 0           my $hash = { monthly => \%monthly, stats => \%stats };
1689 0           $self->storage_write($type,$hash);
1690             }
1691             }
1692              
1693             sub _build_osname_leaderboards {
1694 0     0     my $self = shift;
1695              
1696 0           $self->{parent}->_log("building osname leaderboards");
1697              
1698             # set dates
1699 0           my $post0 = '999999';
1700 0           my $post1 = $self->{dates}{THATMONTH};
1701 0           my $post2 = $self->{dates}{LASTMONTH};
1702 0           my $post3 = $self->{dates}{THISMONTH};
1703              
1704 0           my @dates = ($post0, $post1, $post2, $post3);
1705 0           my %dates = map {$_ => 1} @dates;
  0            
1706              
1707 0           $self->{parent}->_log("1.post0=$post0");
1708 0           $self->{parent}->_log("2.post1=$post1");
1709 0           $self->{parent}->_log("3.post2=$post2");
1710 0           $self->{parent}->_log("4.post3=$post3");
1711              
1712             # load data
1713 0           my $data = $self->{parent}->leaderboard( results => \@dates );
1714 0           $self->{parent}->tester( 'test' );
1715              
1716 0           my @posts = sort keys %$data;
1717 0           $self->{parent}->_log("5.posts[0]=$posts[0]");
1718              
1719             # store data for the last 3 months, and in total
1720 0           my %oses;
1721 0           for my $post (keys %$data) {
1722 0 0         if($dates{$post}) {
1723 0           for my $os (keys %{$data->{$post}}) {
  0            
1724 0 0         next unless($os);
1725 0           $oses{$os} = 1;
1726 0           for my $tester (keys %{$data->{$post}{$os}}) {
  0            
1727 0   0       $data->{$post0}{$os}{$tester} ||= 0; # make sure we include all testers
1728             }
1729             }
1730             } else {
1731 0           for my $os (keys %{$data->{$post}}) {
  0            
1732 0 0         next unless($os);
1733 0           $oses{$os} = 1;
1734 0           for my $tester (keys %{$data->{$post}{$os}}) {
  0            
1735 0           $data->{$post0}{$os}{$tester} += $data->{$post}{$os}{$tester};
1736             }
1737             }
1738 0           delete $data->{$post};
1739             }
1740             }
1741              
1742             #$self->{parent}->_log("6.data=".Dumper($data));
1743              
1744             # reorganise data
1745 0           my %hash;
1746 0           for my $os (keys %oses) {
1747 0           for my $tester (keys %{$data->{$post0}{$os}}) {
  0            
1748 0   0       $hash{$os}{$tester}{this} = $data->{$post3}{$os}{$tester} || 0;
1749 0   0       $hash{$os}{$tester}{that} = $data->{$post2}{$os}{$tester} || 0;
1750             $hash{$os}{$tester}{all} = ($data->{$post3}{$os}{$tester} || 0) + ($data->{$post2}{$os}{$tester} || 0) +
1751 0   0       ($data->{$post1}{$os}{$tester} || 0) + ($data->{$post0}{$os}{$tester} || 0);
      0        
      0        
      0        
1752             }
1753             }
1754              
1755 0           $self->{parent}->_log("1.reorg");
1756              
1757 0           my %titles = (
1758             this => 'This Month',
1759             that => 'Last Month',
1760             all => 'All Months'
1761             );
1762              
1763 0           my $sql = 'SELECT * FROM osname ORDER BY ostitle';
1764 0           my @rows = $self->{parent}->{CPANSTATS}->get_query('hash',$sql);
1765 0           my @oses = grep {$_->{osname}} @rows;
  0            
1766              
1767 0           for my $osname (keys %oses) {
1768 0 0         next unless($osname);
1769 0           for my $type (qw(this that all)) {
1770 0           my @leaders;
1771 0 0 0       for my $tester (sort {($hash{$osname}{$b}{$type} || 0) <=> ($hash{$osname}{$a}{$type} || 0) || $a cmp $b} keys %{$hash{$osname}}) {
  0   0        
  0            
1772             push @leaders,
1773             { col2 => $hash{$osname}{$tester}{this},
1774             col1 => $hash{$osname}{$tester}{that},
1775             col3 => $hash{$osname}{$tester}{all},
1776 0           tester => $tester
1777             } ;
1778             }
1779              
1780 0           my $os = lc $osname;
1781              
1782 0           my %tvars;
1783 0           $tvars{osnames} = \@oses;
1784 0           $tvars{template} = 'leaderos';
1785 0           $tvars{osname} = $self->{parent}->osname($osname);
1786 0           $tvars{leaders} = \@leaders;
1787 0           $tvars{headers} = { col1 => $post2, col2 => $post3, title => "$tvars{osname} Leaderboard ($titles{$type})" };
1788 0 0         $tvars{links}{this} = $type eq 'this' ? '' : "leaders-$os-this.html";
1789 0 0         $tvars{links}{that} = $type eq 'that' ? '' : "leaders-$os-that.html";
1790 0 0         $tvars{links}{all} = $type eq 'all' ? '' : "leaders-$os-all.html";
1791 0           $self->{parent}->_log("1.leaders/leaders-$os-$type");
1792              
1793 0           $self->_writepage("leaders/leaders-$os-$type",\%tvars);
1794             }
1795             }
1796              
1797 0           $self->{parent}->_log("building leader board");
1798 0           my (%tvars,%stats,%testers) = ();
1799              
1800 0           $tvars{osnames} = \@oses;
1801 0           for my $post ($post0, $post1, $post2, $post3) {
1802 0           for my $os (keys %{$data->{$post}}) {
  0            
1803 0 0         next unless($os);
1804 0           for my $tester (keys %{$data->{$post}{$os}}) {
  0            
1805 0           $testers{$tester} += $data->{$post}{$os}{$tester};
1806             }
1807             }
1808             }
1809              
1810 0           my $count = 1;
1811 0 0         for my $tester (sort {$testers{$b} <=> $testers{$a} || $a cmp $b} keys %testers) {
  0            
1812 0           push @{$tvars{STATS}}, [$count++, $testers{$tester}, $tester];
  0            
1813             }
1814              
1815 0           $count--;
1816              
1817 0           $self->{parent}->tester_loader();
1818              
1819 0           $self->{parent}->_log("Unknown Addresses: ".($count-$self->{parent}->known_t));
1820 0           $self->{parent}->_log("Known Addresses: ".($self->{parent}->known_s));
1821 0           $self->{parent}->_log("Listed Addresses: ".($self->{parent}->known_s + $count - $self->{parent}->known_t));
1822 0           $self->{parent}->_log("Unknown Testers: ".($count-$self->{parent}->known_t));
1823 0           $self->{parent}->_log("Known Testers: ".($self->{parent}->known_t));
1824 0           $self->{parent}->_log("Listed Testers: ".($count));
1825              
1826 0           push @{$tvars{COUNTS}},
1827             ($count-$self->{parent}->known_t),
1828             $self->{parent}->known_s,
1829             ($self->{parent}->known_s + $count - $self->{parent}->known_t),
1830             ($count - $self->{parent}->known_t),
1831             $self->{parent}->known_t,
1832 0           $count;
1833              
1834 0           $self->_writepage('testers',\%tvars);
1835             }
1836              
1837             sub _build_monthly_stats_files {
1838 0     0     my $self = shift;
1839 0           my %tvars;
1840              
1841 0           my $directory = $self->{parent}->directory;
1842 0           my $results = "$directory/stats";
1843 0           mkpath($results);
1844              
1845 0           $self->{parent}->_log("building monthly stats for graphs - 1,3,pcent1");
1846              
1847             #print "DATE,UPLOADS,REPORTS,NA,PASS,FAIL,UNKNOWN\n";
1848 0           my $fh1 = IO::File->new(">$results/stats1.txt");
1849 0           print $fh1 "#DATE,UPLOADS,REPORTS,PASS,FAIL\n";
1850              
1851 0           my $fh2 = IO::File->new(">$results/pcent1.txt");
1852 0           print $fh2 "#DATE,FAIL,OTHER,PASS\n";
1853              
1854 0           my $fh3 = IO::File->new(">$results/stats3.txt");
1855 0           print $fh3 "#DATE,FAIL,NA,UNKNOWN\n";
1856              
1857 0           for my $date (sort keys %{$self->{stats}}) {
  0            
1858 0 0         next if($date > $self->{dates}{THISMONTH});
1859              
1860 0   0       my $uploads = ($self->{pause}{$date} || 0);
1861 0   0       my $reports = ($self->{stats}{$date}{reports} || 0);
1862 0   0       my $passes = ($self->{stats}{$date}{state}{pass} || 0);
1863 0   0       my $fails = ($self->{stats}{$date}{state}{fail} || 0);
1864 0           my $others = $reports - $passes - $fails;
1865              
1866 0           my @fields = (
1867             $date, $uploads, $reports, $passes, $fails
1868             );
1869              
1870 0 0         my @pcent = (
    0          
    0          
1871             $date,
1872             ($reports > 0 ? int($fails / $reports * 100) : 0),
1873             ($reports > 0 ? int($others / $reports * 100) : 0),
1874             ($reports > 0 ? int($passes / $reports * 100) : 0)
1875             );
1876              
1877 0           unshift @{$tvars{STATS}},
1878             [ @fields,
1879             $self->{stats}{$date}{state}{na},
1880 0           $self->{stats}{$date}{state}{unknown}];
1881              
1882             # graphs don't include current month
1883 0 0         next if($date > $self->{dates}{THISMONTH}-1);
1884              
1885 0           my $content = sprintf "%d,%d,%d,%d,%d\n", @fields;
1886 0           print $fh1 $content;
1887              
1888 0           $content = sprintf "%d,%d,%d,%d\n", @pcent;
1889 0           print $fh2 $content;
1890              
1891             $content = sprintf "%d,%d,%d,%d\n",
1892             $date,
1893             ($self->{stats}{$date}{state}{fail} || 0),
1894             ($self->{stats}{$date}{state}{na} || 0),
1895 0   0       ($self->{stats}{$date}{state}{unknown} || 0);
      0        
      0        
1896 0           print $fh3 $content;
1897             }
1898 0           $fh1->close;
1899 0           $fh2->close;
1900 0           $fh3->close;
1901              
1902 0           $self->_writepage('mreports',\%tvars);
1903              
1904 0           $self->{parent}->_log("building monthly stats for graphs - 2");
1905              
1906             #print "DATE,TESTERS,PLATFORMS,PERLS\n";
1907 0           $fh2 = IO::File->new(">$results/stats2.txt");
1908 0           print $fh2 "#DATE,TESTERS,PLATFORMS,PERLS\n";
1909              
1910 0           for my $date (sort keys %{$self->{stats}}) {
  0            
1911 0 0         next if($date > $self->{dates}{THISMONTH}-1);
1912             printf $fh2 "%d,%d,%d,%d\n",
1913             $date,
1914             ($self->{monthly}{$date}{tester} || 0),
1915             ($self->{monthly}{$date}{platform} || 0),
1916 0   0       ($self->{monthly}{$date}{perl} || 0);
      0        
      0        
1917             }
1918 0           $fh2->close;
1919              
1920 0           $self->{parent}->_log("building monthly stats for graphs - 4");
1921              
1922             #print "DATE,ALL,FIRST,LAST\n";
1923 0           $fh1 = IO::File->new(">$results/stats4.txt");
1924 0           print $fh1 "#DATE,ALL,FIRST,LAST\n";
1925              
1926 0           for my $date (sort keys %{ $self->{stats} }) {
  0            
1927 0 0         next if($date > $self->{dates}{THISMONTH}-1);
1928              
1929 0 0         if(defined $self->{counts}{$date}) {
1930 0           $self->{counts}{$date}{all} = scalar(keys %{$self->{counts}{$date}{testers}});
  0            
1931             }
1932 0   0       $self->{counts}{$date}{all} ||= 0;
1933 0   0       $self->{counts}{$date}{first} ||= 0;
1934 0   0       $self->{counts}{$date}{last} ||= 0;
1935 0 0         $self->{counts}{$date}{last} = '' if($date > $self->{dates}{LASTMONTH});
1936              
1937             printf $fh1 "%d,%s,%s,%s\n",
1938             $date,
1939             $self->{counts}{$date}{all},
1940             $self->{counts}{$date}{first},
1941 0           $self->{counts}{$date}{last};
1942             }
1943 0           $fh1->close;
1944             }
1945              
1946             sub _build_failure_rates {
1947 0     0     my $self = shift;
1948 0           my (%tvars,%dists);
1949              
1950 0           $self->{parent}->_log("building failure rates");
1951              
1952 0           my $query =
1953             'SELECT x.dist,x.version,u.released FROM ixlatest AS x '.
1954             'INNER JOIN uploads AS u ON u.dist=x.dist AND u.version=x.version '.
1955             "WHERE u.type != 'backpan'";
1956 0           my $next = $self->{parent}->{CPANSTATS}->iterator('hash',$query);
1957 0           while(my $row = $next->()) {
1958 0           $dists{$row->{dist}}{$row->{version}} = $row->{released};
1959             }
1960              
1961 0           $self->{parent}->_log("selecting failure rates");
1962              
1963             # select worst failure rates - latest version, and ignoring backpan only.
1964 0           my %worst;
1965 0           for my $dist (keys %{ $self->{fails} }) {
  0            
1966 0 0         next unless($dists{$dist});
1967 0           my ($version) = sort {$dists{$dist}{$b} <=> $dists{$dist}{$a}} keys %{$dists{$dist}};
  0            
  0            
1968              
1969 0           $worst{"$dist-$version"} = $self->{fails}->{$dist}{$version};
1970 0           $worst{"$dist-$version"}->{dist} = $dist;
1971             $worst{"$dist-$version"}->{pcent} = $self->{fails}{$dist}{$version}{fail}
1972 0 0         ? int(($self->{fails}{$dist}{$version}{fail}/$self->{fails}{$dist}{$version}{total})*10000)/100
1973             : 0.00;
1974 0   0       $worst{"$dist-$version"}->{pass} ||= 0;
1975 0   0       $worst{"$dist-$version"}->{fail} ||= 0;
1976              
1977 0           my @post = localtime($dists{$dist}{$version});
1978 0           $worst{"$dist-$version"}->{post} = sprintf "%04d%02d", $post[5]+1900, $post[4]+1;
1979             }
1980              
1981 0           $self->{parent}->_log("worst = " . scalar(keys %worst) . " entries");
1982 0           $self->{parent}->_log("building failure counts");
1983              
1984             # calculate worst failure rates - by failure count
1985 0           my $count = 1;
1986 0 0         for my $dist (sort {$worst{$b}->{fail} <=> $worst{$a}->{fail} || $worst{$b}->{pcent} <=> $worst{$a}->{pcent}} keys %worst) {
  0            
1987 0 0         last unless($worst{$dist}->{fail});
1988 0           my $pcent = sprintf "%3.2f%%", $worst{$dist}->{pcent};
1989 0           push @{$tvars{WORST}}, [$count++, $worst{$dist}->{fail}, $dist, $worst{$dist}->{post}, $worst{$dist}->{pass}, $worst{$dist}->{total}, $pcent, $worst{$dist}->{dist}];
  0            
1990 0 0         last if($count > 100);
1991             }
1992              
1993 0           $self->_writepage('wdists',\%tvars);
1994 0           undef %tvars;
1995              
1996 0           $self->{parent}->_log("building failure pecentages");
1997              
1998             # calculate worst failure rates - by percentage
1999 0           $count = 1;
2000 0 0         for my $dist (sort {$worst{$b}->{pcent} <=> $worst{$a}->{pcent} || $worst{$b}->{fail} <=> $worst{$a}->{fail}} keys %worst) {
  0            
2001 0 0         last unless($worst{$dist}->{fail});
2002 0           my $pcent = sprintf "%3.2f%%", $worst{$dist}->{pcent};
2003 0           push @{$tvars{WORST}}, [$count++, $worst{$dist}->{fail}, $dist, $worst{$dist}->{post}, $worst{$dist}->{pass}, $worst{$dist}->{total}, $pcent, $worst{$dist}->{dist}];
  0            
2004 0 0         last if($count > 100);
2005             }
2006              
2007 0           $self->_writepage('wpcent',\%tvars);
2008 0           undef %tvars;
2009              
2010 0           $self->{parent}->_log("done building failure rates");
2011              
2012             # now we do as above but for the last 6 months
2013              
2014 0           my @recent = localtime(time() - 15778463); # 6 months ago
2015 0           my $recent = sprintf "%04d%02d", $recent[5]+1900, $recent[4]+1;
2016              
2017 0           for my $dist (keys %worst) {
2018 0 0         next if($worst{$dist}->{post} ge $recent);
2019 0           delete $worst{$dist};
2020             }
2021              
2022             # calculate worst failure rates - by failure count
2023 0           $count = 1;
2024 0 0         for my $dist (sort {$worst{$b}->{fail} <=> $worst{$a}->{fail} || $worst{$b}->{pcent} <=> $worst{$a}->{pcent}} keys %worst) {
  0            
2025 0 0         last unless($worst{$dist}->{fail});
2026 0           my $pcent = sprintf "%3.2f%%", $worst{$dist}->{pcent};
2027 0           push @{$tvars{WORST}}, [$count++, $worst{$dist}->{fail}, $dist, $worst{$dist}->{post}, $worst{$dist}->{pass}, $worst{$dist}->{total}, $pcent, $worst{$dist}->{dist}];
  0            
2028 0 0         last if($count > 100);
2029             }
2030              
2031 0           $self->_writepage('wdists-recent',\%tvars);
2032 0           undef %tvars;
2033              
2034 0           $self->{parent}->_log("building failure pecentages");
2035              
2036             # calculate worst failure rates - by percentage
2037 0           $count = 1;
2038 0 0         for my $dist (sort {$worst{$b}->{pcent} <=> $worst{$a}->{pcent} || $worst{$b}->{fail} <=> $worst{$a}->{fail}} keys %worst) {
  0            
2039 0 0         last unless($worst{$dist}->{fail});
2040 0           my $pcent = sprintf "%3.2f%%", $worst{$dist}->{pcent};
2041 0           push @{$tvars{WORST}}, [$count++, $worst{$dist}->{fail}, $dist, $worst{$dist}->{post}, $worst{$dist}->{pass}, $worst{$dist}->{total}, $pcent, $worst{$dist}->{dist}];
  0            
2042 0 0         last if($count > 100);
2043             }
2044              
2045 0           $self->_writepage('wpcent-recent',\%tvars);
2046             }
2047              
2048             sub _build_performance_stats {
2049 0     0     my $self = shift;
2050              
2051 0           my $directory = $self->{parent}->directory;
2052 0           my $results = "$directory/stats";
2053 0           mkpath($results);
2054              
2055 0           $self->{parent}->_log("building peformance stats for graphs");
2056              
2057 0           my $fh = IO::File->new(">$results/build1.txt");
2058 0           print $fh "#DATE,REQUESTS,PAGES,REPORTS\n";
2059              
2060 0           my $count = scalar(keys %{$self->{build}});
  0            
2061 0   0       my $limit = $self->{parent}->build_history || 90;
2062 0           my $diff = $count - $limit;
2063              
2064 0           for my $date (sort {$a <=> $b} keys %{$self->{build}}) {
  0            
  0            
2065 0 0         next if(--$diff > 0);
2066              
2067             #$self->{parent}->_log("build_stats: date=$date, old=$self->{build}{$date}->{old}");
2068 0 0         next if($self->{build}{$date}->{old} == 2); # ignore todays tally
2069             #next if($date > $self->{dates}{THISMONTH}-1);
2070              
2071             printf $fh "%d,%d,%d,%d\n",
2072             $date,
2073             ($self->{build}{$date}{webtotal} || 0),
2074             ($self->{build}{$date}{webunique} || 0),
2075 0   0       ($self->{build}{$date}{reports} || 0);
      0        
      0        
2076             }
2077 0           $fh->close;
2078             }
2079              
2080             sub _build_sizes {
2081 0     0     my $self = shift;
2082 0           my $du = 'du -h --max-depth=0';
2083              
2084 0           for my $dir (qw( dir_cpan dir_backpan dir_reports )) {
2085 0           my $path = $self->{parent}->$dir();
2086 0           my $res =`$du $path`;
2087 0   0       $res ||= '';
2088 0 0         $res =~ s/\s.*$//s if($res);
2089 0           $self->{sizes}{$dir} = $res;
2090 0           $self->{parent}->_log(".. size for $dir ($path) = $res");
2091             }
2092             }
2093              
2094             =item * _writepage
2095              
2096             Creates a single HTML page.
2097              
2098             =cut
2099              
2100             sub _writepage {
2101 0     0     my ($self,$page,$vars) = @_;
2102 0           my $directory = $self->{parent}->directory;
2103 0           my $templates = $self->{parent}->templates;
2104              
2105             #$self->{parent}->_log("_writepage: page=$page");
2106              
2107 0   0       my $extension = $vars->{extension} || 'html';
2108 0   0       my $template = $vars->{template} || $page;
2109 0   0       my $tlayout = $vars->{layout} || 'layout';
2110 0           my $layout = "$tlayout.$extension";
2111 0           my $source = "$template.$extension";
2112 0           my $target = "$directory/$page.$extension";
2113              
2114             #$self->{parent}->_log("_writepage: layout=$layout, source=$source, target=$target");
2115              
2116 0           mkdir(dirname($target));
2117              
2118 0           $vars->{SOURCE} = $source;
2119 0           $vars->{VERSION} = $VERSION;
2120 0           $vars->{copyright} = $self->{parent}->copyright;
2121 0           $vars->{$_} = $self->{dates}{$_} for(keys %{ $self->{dates} });
  0            
2122              
2123             #if($page =~ /(statscpan|interest)/) {
2124             # $self->{parent}->_log("$page:" . Dumper($vars));
2125             #}
2126              
2127 0           my %config = ( # provide config info
2128             RELATIVE => 1,
2129             ABSOLUTE => 1,
2130             INCLUDE_PATH => $templates,
2131             INTERPOLATE => 0,
2132             POST_CHOMP => 1,
2133             TRIM => 1,
2134             );
2135              
2136 0           my $parser = Template->new(\%config); # initialise parser
2137 0 0         $parser->process($layout,$vars,$target) # parse the template
2138             or die $parser->error() . "\n";
2139             }
2140              
2141             # Provides the ordinal for dates.
2142              
2143             sub _ext {
2144 0     0     my $num = shift;
2145 0 0 0       return 'st' if($num == 1 || $num == 21 || $num == 31);
      0        
2146 0 0 0       return 'nd' if($num == 2 || $num == 22);
2147 0 0 0       return 'rd' if($num == 3 || $num == 23);
2148 0           return 'th';
2149             }
2150              
2151             sub _parsedate {
2152 0     0     my $time = shift;
2153 0           my @time = localtime($time);
2154 0           return sprintf "%04d%02d", $time[5]+1900,$time[4]+1;
2155             }
2156              
2157             sub _count_mailrc {
2158 0     0     my $self = shift;
2159 0           my $count = 0;
2160 0           my $mailrc = $self->{parent}->mailrc();
2161              
2162 0 0         my $fh = IO::File->new($mailrc,'r') or die "Cannot read file [$mailrc]: $!\n";
2163 0           while(<$fh>) {
2164 0 0         next unless(/^alias\s*(\w+)\s+"([\s\w]+)\s+<[^>]+>"/);
2165 0           $self->{alias}{$1} = $2;
2166 0           $count++;
2167             }
2168 0           $fh->close;
2169              
2170 0           return $count;
2171             }
2172              
2173             q("Will code for Guinness!");
2174              
2175             __END__
2176              
2177             =back
2178              
2179             =head1 CPAN TESTERS FUND
2180              
2181             CPAN Testers wouldn't exist without the help and support of the Perl
2182             community. However, since 2008 CPAN Testers has grown far beyond the
2183             expectations of it's original creators. As a consequence it now requires
2184             considerable funding to help support the infrastructure.
2185              
2186             In early 2012 the Enlightened Perl Organisation very kindly set-up a
2187             CPAN Testers Fund within their donatation structure, to help the project
2188             cover the costs of servers and services.
2189              
2190             If you would like to donate to the CPAN Testers Fund, please follow the link
2191             below to the Enlightened Perl Organisation's donation site.
2192              
2193             F<https://members.enlightenedperl.org/drupal/donate-cpan-testers>
2194              
2195             If your company would like to support us, you can donate financially via the
2196             fund link above, or if you have servers or services that we might use, please
2197             send an email to admin@cpantesters.org with details.
2198              
2199             Our full list of current sponsors can be found at our I <3 CPAN Testers site.
2200              
2201             F<http://iheart.cpantesters.org>
2202              
2203             =head1 BUGS, PATCHES & FIXES
2204              
2205             There are no known bugs at the time of this release. However, if you spot a
2206             bug or are experiencing difficulties, that is not explained within the POD
2207             documentation, please send bug reports and patches to the RT Queue (see below).
2208              
2209             Fixes are dependent upon their severity and my availability. Should a fix not
2210             be forthcoming, please feel free to (politely) remind me.
2211              
2212             RT Queue -
2213             http://rt.cpan.org/Public/Dist/Display.html?Name=CPAN-Testers-WWW-Statistics
2214              
2215             =head1 SEE ALSO
2216              
2217             L<CPAN::Testers::Data::Generator>,
2218             L<CPAN::Testers::WWW::Reports>
2219              
2220             F<http://www.cpantesters.org/>,
2221             F<http://stats.cpantesters.org/>,
2222             F<http://wiki.cpantesters.org/>
2223              
2224             =head1 AUTHOR
2225              
2226             Barbie, <barbie@cpan.org>
2227             for Miss Barbell Productions <http://www.missbarbell.co.uk>.
2228              
2229             =head1 COPYRIGHT AND LICENSE
2230              
2231             Copyright (C) 2005-2017 Barbie for Miss Barbell Productions.
2232              
2233             This distribution is free software; you can redistribute it and/or
2234             modify it under the Artistic Licence v2.
2235              
2236             =cut