package CPAN::Testers::WWW::Statistics::Pages; use warnings; use strict; use vars qw($VERSION); $VERSION = '0.73'; #---------------------------------------------------------------------------- =head1 NAME CPAN::Testers::WWW::Statistics::Pages - CPAN Testers Statistics pages. =head1 SYNOPSIS my %hash = { config => 'options' }; my $obj = CPAN::Testers::WWW::Statistics->new(%hash); my $ct = CPAN::Testers::WWW::Statistics::Pages->new(parent => $obj); $ct->create(); =head1 DESCRIPTION Using the cpanstats database, this module extracts all the data and generates all the HTML pages needed for the CPAN Testers Statistics website. In addition, also generates the data files in order generate the graphs that appear on the site. Note that this package should not be called directly, but via its parent as: my %hash = { config => 'options' }; my $obj = CPAN::Testers::WWW::Statistics->new(%hash); $obj->make_pages(); =cut # ------------------------------------- # Library Modules use Data::Dumper; use File::Basename; use File::Copy; use File::Path; use HTML::Entities; use IO::File; use Sort::Versions; use Template; #use Time::HiRes qw ( time ); # ------------------------------------- # Variables my ($known_s,$known_t) = (0,0); my %month = ( 0 => 'January', 1 => 'February', 2 => 'March', 3 => 'April', 4 => 'May', 5 => 'June', 6 => 'July', 7 => 'August', 8 => 'September', 9 => 'October', 10 => 'November', 11 => 'December' ); my $ADAY = 86400; my ($LIMIT,%options,%pages); my ($THISYEAR,$RUNDATE,$STATDATE,$THISDATE,$THATYEAR,$LASTDATE,$THATDATE,$SHORTDATE); my ($DATABASE2); my %matrix_limits = ( all => [ 1000, 5000 ], month => [ 100, 500 ] ); # ------------------------------------- # Subroutines =head1 INTERFACE =head2 The Constructor =over 4 =item * new Page creation object. Allows the user to turn or off the progress tracking. new() takes an option hash as an argument, which may contain 'progress => 1' to turn on the progress tracker and/or 'database => $db' to indicate the path to the database. If no database path is supplied, './cpanstats.db' is used. =back =cut sub new { my $class = shift; my %hash = @_; die "Must specify the parent statistics object\n" unless(defined $hash{parent}); my $self = {parent => $hash{parent}}; bless $self, $class; $self->_init_date(); return $self; } =head2 Public Methods =over 4 =item * create Method to facilitate the creation of pages. =back =cut sub create { my $self = shift; $self->{parent}->_log("start"); $self->_write_basics(); $self->_write_stats(); $self->{parent}->_log("finish"); } =head2 Private Methods =over 4 =item * _write_basics Write out basic pages, all of which are simply built from the templates, without any data processing required. =cut sub _write_basics { my $self = shift; my $directory = $self->{parent}->directory; my $templates = $self->{parent}->templates; my $database = $self->{parent}->database; my $results = "$directory/stats"; mkpath($results); $self->{parent}->_log("writing basic files"); my $ranges1 = $self->{parent}->ranges('TEST_RANGES'); my $ranges2 = $self->{parent}->ranges('CPAN_RANGES'); # additional pages not requiring metrics my %pages = ( cpanmail => {}, response => {}, perform => {}, graphs => {}, graphs1 => {RANGES => $ranges1, template=>'archive',PREFIX=>'stats1' ,TITLE=>'Monthly Report Counts'}, graphs2 => {RANGES => $ranges1, template=>'archive',PREFIX=>'stats2' ,TITLE=>'Testers, Platforms and Perls'}, graphs3 => {RANGES => $ranges1, template=>'archive',PREFIX=>'stats3' ,TITLE=>'Monthly Non-Passing Reports Counts'}, graphs4 => {RANGES => $ranges1, template=>'archive',PREFIX=>'stats4' ,TITLE=>'Monthly Tester Fluctuations'}, graphs6 => {RANGES => $ranges2, template=>'archive',PREFIX=>'stats6' ,TITLE=>'All Distribution Uploads per Month'}, graphs12 => {RANGES => $ranges2, template=>'archive',PREFIX=>'stats12',TITLE=>'New Distribution Uploads per Month'} ); $self->{parent}->_log("building support pages"); $self->_writepage($_,$pages{$_}) for(keys %pages); # copy files $self->{parent}->_log("copying static files"); my $tocopy = $self->{parent}->tocopy; foreach my $filename (@$tocopy) { my $src = $templates . "/$filename"; if(-f $src) { my $dest = $directory . "/$filename"; mkpath( dirname($dest) ); if(-d dirname($dest)) { copy( $src, $dest ); } else { warn "Missing directory: $dest\n"; } } else { warn "Missing file: $src\n"; } } } =item * _write_index Writes out the main index page, after all stats have been calculated. =cut sub _write_index { my $self = shift; my $directory = $self->{parent}->directory; my $templates = $self->{parent}->templates; my $database = $self->{parent}->database; $self->{parent}->_log("writing index file"); # calculate database metrics my $mtime = (stat($database))[9]; my @ltime = localtime($mtime); $DATABASE2 = sprintf "%d%s %s %d", $ltime[3],_ext($ltime[3]),$month{$ltime[4]},$ltime[5]+1900; my $DATABASE1 = sprintf "%04d/%02d/%02d", $ltime[5]+1900,$ltime[4]+1,$ltime[3]; my $DBSZ_UNCOMPRESSED = int((-s $database ) / (1024 * 1024)); my $DBSZ_COMPRESSED = int((-s $database . '.gz') / (1024 * 1024)); # index page my %pages = ( index => { THISDATE => $THISDATE, DATABASE => $DATABASE1, DBSZ_COMPRESSED => $DBSZ_COMPRESSED, DBSZ_UNCOMPRESSED => $DBSZ_UNCOMPRESSED, report_count => $self->{count}{reports}, distro_count => $self->{count}{distros}, report_rate => $self->{rates}{report}, distro_rate => $self->{rates}{distro} }, ); $self->_writepage($_,$pages{$_}) for(keys %pages); } =item * _write_stats Extracts data, compiles the pages, generates the graph data files and creates the HTML pages. =cut sub _write_stats { my $self = shift; ## BUILD INDEPENDENT STATS $self->_report_cpan(); ## BUILD GENERAL STATS $self->_build_stats(); ## BUILD STATS PAGES $self->_report_interesting(); $self->_build_osname_matrix(); $self->_build_platform_matrix(); $self->_build_monthly_stats_files(); $self->_build_failure_rates(); $self->_build_monthly_stats(); $self->_build_performance_stats(); ## BUILD INDEX PAGE $self->_write_index(); } sub _build_stats { my $self = shift; $self->{parent}->_log("building rate hash"); my ($d1,$d2) = (time(), time() - $ADAY); my @date = localtime($d2); my $date = sprintf "%04d%02d%02d", $date[5]+1900, $date[4]+1, $date[3]; my @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(*) FROM cpanstats WHERE state!='cpan' AND fulldate like '$date%'"); $self->{rates}{report} = $rows[0]->[0] ? $ADAY / $rows[0]->[0] * 1000 : $ADAY / 10000 * 1000; @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(*) FROM uploads WHERE released > $d2 and released < $d1"); $self->{rates}{distro} = $rows[0]->[0] ? $ADAY / $rows[0]->[0] * 1000 : $ADAY / 60 * 1000; $self->{rates}{report} = 1000 if($self->{rates}{report} < 1000); $self->{rates}{distro} = 1000 if($self->{rates}{distro} < 1000); $self->{parent}->_log("building dist hash"); my $iterator = $self->{parent}->{CPANSTATS}->iterator('hash',"SELECT dist,version FROM ixlatest"); while(my $row = $iterator->()) { $self->{dists}{$row->{dist}}->{ALL} = 0; $self->{dists}{$row->{dist}}->{IXL} = 0; $self->{dists}{$row->{dist}}->{VER} = $row->{version}; } $self->{parent}->_log("building stats hash"); $self->{count}{$_} ||= 0 for(qw(posters entries reports distros)); #$self->{count} = { posters => 0, entries => 0, reports => 0, distros => 0 }, $self->{xrefs} = { posters => {}, entries => {}, reports => {} }, $self->{xlast} = { posters => [], entries => [], reports => [] }, my $file = $self->{parent}->builder(); if($file && -f $file) { if(my $fh = IO::File->new($file,'r')) { while(<$fh>) { my ($d,$r,$p) = /(\d+),(\d+),(\d+)/; next unless($d); $self->{build}{$d}->{webtotal} = $r; $self->{build}{$d}->{webunique} = $p; } $fh->close; } } my %testers; $iterator = $self->{parent}->{CPANSTATS}->iterator('array',"SELECT * FROM cpanstats ORDER BY id"); while(my $row = $iterator->()) { # 0, 1, 2, 3, 4, 5 6, 7, 8, 9, 10 # id, state, postdate, tester, dist, version, platform, perl, osname, osvers, fulldate $row->[7] =~ s/\s.*//; # only need to know the main release if($row->[1] eq 'cpan') { $self->{stats}{$row->[2]}{pause}++; $self->{stats}{$row->[2]}{uploads}{$row->[4]}{$row->[5]}++; $self->{fails}{$row->[4]}{$row->[5]}{post} = $row->[2]; } else { my $osname = $self->{parent}->osname($row->[8]); my $name = $self->_tester_name($row->[3]); $self->{stats}{$row->[2]}{reports}++; $self->{stats}{$row->[2]}{state }{$row->[1]}++; $self->{stats}{$row->[2]}{tester }{$name }++; $self->{stats}{$row->[2]}{dist }{$row->[4]}++; $self->{stats}{$row->[2]}{version }{$row->[5]}++; $self->{stats}{$row->[2]}{platform}{$row->[6]}++; $self->{stats}{$row->[2]}{perl }{$row->[7]}++; $self->{stats}{$row->[2]}{osname }{$osname}++; # check failure rates $self->{fails}{$row->[4]}{$row->[5]}{fail}++ if($row->[1] =~ /FAIL|UNKNOWN/i); $self->{fails}{$row->[4]}{$row->[5]}{pass}++ if($row->[1] =~ /PASS/i); $self->{fails}{$row->[4]}{$row->[5]}{total}++; # build matrix stats my $perl = $row->[7]; $perl =~ s/\s.*//; # only need to know the main release $self->{perls}{$perl} = 1; $self->{pass} {$row->[6]}{$perl}{all}{$row->[4]} = 1; $self->{platform}{$row->[6]}{$perl}{all}++; $self->{osys} {$osname} {$perl}{all}{$row->[4]} = 1; $self->{osname} {$osname} {$perl}{all}++; if($row->[2] == $LASTDATE) { $self->{pass} {$row->[6]}{$perl}{month}{$row->[4]} = 1; $self->{platform}{$row->[6]}{$perl}{month}++; $self->{osys} {$osname} {$perl}{month}{$row->[4]} = 1; $self->{osname} {$osname} {$perl}{month}++; } # record tester activity $testers{$name}{first} ||= $row->[2]; $testers{$name}{last} = $row->[2]; $self->{counts}{$row->[2]}{testers}{$name} = 1; if(defined $self->{dists}{$row->[4]}) { $self->{dists}{$row->[4]}{ALL}++; $self->{dists}{$row->[4]}{IXL}++ if($self->{dists}{$row->[4]}{VER} eq $row->[5]); } my $day = substr($row->[10],0,8); $self->{build}{$day}{reports}++ if(defined $self->{build}{$day}); } my @row = (0, @$row); $self->{count}{posters} = $row[1]; $self->{count}{entries}++; $self->{count}{reports}++ if($row[2] ne 'cpan'); for my $type (qw(posters entries reports)) { next if($type eq 'reports' && $row[2] eq 'cpan'); if($self->{count}{$type} == 1 || ($self->{count}->{$type} % 500000) == 0) { $self->{xrefs}{$type}->{$self->{count}->{$type}} = \@row; } else { $self->{xlast}{$type} = \@row; } } } for my $tester (keys %testers) { $self->{counts}{$testers{$tester}{first}}{first}++; $self->{counts}{$testers{$tester}{last}}{last}++; } my @versions = sort {versioncmp($b,$a)} keys %{$self->{perls}}; $self->{versions} = \@versions; $self->{parent}->_log("stats hash built"); } =item * _report_interesting Generates the interesting stats page =cut sub _report_interesting { my $self = shift; my %tvars; $self->{parent}->_log("building interesting page"); my (@bydist,@byvers); my $inx = 20; for my $dist (sort {$self->{dists}{$b}{ALL} <=> $self->{dists}{$a}{ALL}} keys %{$self->{dists}}) { push @bydist, [$self->{dists}{$dist}{ALL},$dist]; last if(--$inx <= 0); } $inx = 20; for my $dist (sort {$self->{dists}{$b}{IXL} <=> $self->{dists}{$a}{IXL}} keys %{$self->{dists}}) { push @byvers, [$self->{dists}{$dist}{IXL},$dist,$self->{dists}{$dist}{VER}]; last if(--$inx <= 0); } $tvars{BYDIST} = \@bydist; $tvars{BYVERS} = \@byvers; for my $type (qw(posters entries reports)) { $self->{xrefs}{$type}{$self->{count}{$type}} = $self->{xlast}{$type}; for my $key (sort {$a <=> $b} keys %{ $self->{xrefs}{$type} }) { my @row = @{ $self->{xrefs}{$type}{$key} }; $row[0] = $key; $row[2] = uc $row[2]; $row[4] = $self->_tester_name($row[4]) if($row[4] && $row[4] =~ /\@/); push @{ $tvars{ uc($type) } }, \@row; } } my @headings = qw( count id grade postdate tester dist version platform perl osname osvers fulldate ); $tvars{HEADINGS} = \@headings; $self->_writepage('interest',\%tvars); } =item * _report_cpan Generates the statistic pages that relate specifically to CPAN. =cut sub _report_cpan { my $self = shift; my (%authors,%distros,%counts,%tvars); $self->{parent}->_log("building cpan trends page"); my $next = $self->{parent}->{CPANSTATS}->iterator('hash',"SELECT * FROM uploads ORDER BY released"); while(my $row = $next->()) { next if($row->{dist} eq 'perl'); my $date = _parsedate($row->{released}); $authors{$row->{author}}{count}++; $distros{$row->{dist}}{count}++; $authors{$row->{author}}{dist}{$row->{dist}}++; $authors{$row->{author}}{dists}++ if($authors{$row->{author}}{dist}{$row->{dist}} == 1); $self->{counts}{$date}{authors}{$row->{author}}++; $self->{counts}{$date}{distros}{$row->{dist}}++; $self->{counts}{$date}{newauthors}++ if($authors{$row->{author}}{count} == 1); $self->{counts}{$date}{newdistros}++ if($distros{$row->{dist}}{count} == 1); } my $directory = $self->{parent}->directory; my $results = "$directory/stats"; mkpath($results); my $stat6 = IO::File->new("$results/stats6.txt",'w+') or die "Cannot write to file [$results/stats6.txt]: $!\n"; print $stat6 "#DATE,AUTHORS,DISTROS\n"; my $stat12 = IO::File->new("$results/stats12.txt",'w+') or die "Cannot write to file [$results/stats12.txt]: $!\n"; print $stat12 "#DATE,AUTHORS,DISTROS\n"; for my $date (sort keys %counts) { my $authors = scalar(keys %{ $self->{counts}{$date}{authors} }); my $distros = scalar(keys %{ $self->{counts}{$date}{distros} }); $self->{counts}{$date}{newauthors} ||= 0; $self->{counts}{$date}{newdistros} ||= 0; print $stat6 "$date,$authors,$distros\n"; print $stat12 "$date,$self->{counts}{$date}{newauthors},$self->{counts}{$date}{newdistros}\n"; # print $stat6 "$date,$authors\n"; # print $stat7 "$date,$distros\n"; # print $stat12 "$date,$self->{counts}{$date}{newauthors}\n"; # print $stat13 "$date,$self->{counts}{$date}{newdistros}\n"; } $stat6->close; # $stat7->close; $stat12->close; # $stat13->close; $self->_writepage('trends',\%tvars); $self->{parent}->_log("building cpan leader page"); my $query = 'SELECT x.author,COUNT(x.dist) AS count FROM ixlatest AS x '. 'INNER JOIN uploads AS u ON u.dist=x.dist AND u.version=x.version '. "WHERE u.type != 'backpan' GROUP BY x.author"; my @latest = $self->{parent}->{CPANSTATS}->get_query('hash',$query); my (@allcurrent,@alluploads,@allrelease,@alldistros); my $inx = 1; for my $latest (sort {$b->{count} <=> $a->{count}} @latest) { push @allcurrent, {inx => $inx++, count => $latest->{count}, name => $latest->{author}}; last if($inx > 20); } $inx = 1; for my $author (sort {$authors{$b}{dists} <=> $authors{$a}{dists}} keys %authors) { push @alluploads, {inx => $inx++, count => $authors{$author}{dists}, name => $author}; last if($inx > 20); } $inx = 1; for my $author (sort {$authors{$b}{count} <=> $authors{$a}{count}} keys %authors) { push @allrelease, {inx => $inx++, count => $authors{$author}{count}, name => $author}; last if($inx > 20); } $inx = 1; for my $distro (sort {$distros{$b}{count} <=> $distros{$a}{count}} keys %distros) { push @alldistros, {inx => $inx++, count => $distros{$distro}{count}, name => $distro}; last if($inx > 20); } $tvars{allcurrent} = \@allcurrent; $tvars{alluploads} = \@alluploads; $tvars{allrelease} = \@allrelease; $tvars{alldistros} = \@alldistros; $self->_writepage('leadercpan',\%tvars); $self->{parent}->_log("building cpan interesting stats page"); $tvars{authors}{total} = _count_mailrc(); my @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(distinct author) FROM uploads"); $tvars{authors}{active} = $rows[0]->[0]; $tvars{authors}{inactive} = $tvars{authors}{total} - $rows[0]->[0]; @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(distinct dist) FROM uploads WHERE type != 'backpan'"); $tvars{distros}{uploaded1} = $rows[0]->[0]; $self->{count}{distros} = $rows[0]->[0]; @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(distinct dist) FROM uploads"); $tvars{distros}{uploaded2} = $rows[0]->[0]; $tvars{distros}{uploaded3} = $tvars{distros}{uploaded2} - $tvars{distros}{uploaded1}; @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(*) FROM uploads WHERE type != 'backpan'"); $tvars{distros}{uploaded4} = $rows[0]->[0]; @rows = $self->{parent}->{CPANSTATS}->get_query('array',"SELECT COUNT(*) FROM uploads"); $tvars{distros}{uploaded5} = $rows[0]->[0]; $tvars{distros}{uploaded6} = $tvars{distros}{uploaded5} - $tvars{distros}{uploaded4}; $self->_writepage('statscpan',\%tvars); } sub _build_osname_matrix { my $self = shift; $self->{list}{count} = 0; my %tvars = (template => 'osmatrix', FULL => 1, MONTH => 0); $self->{parent}->_log("building OS matrix - 1"); my $CONTENT = $self->_osname_matrix($self->{versions},'all'); $tvars{CONTENT} = $CONTENT; $self->_writepage('osmatrix-full',\%tvars); %tvars = (template => 'osmatrix', FULL => 1, MONTH => 0, layout => 'layout-wide'); $tvars{CONTENT} = $CONTENT; $self->{parent}->_log("building OS matrix - 2"); $self->_writepage('osmatrix-full-wide',\%tvars); %tvars = (template => 'osmatrix', FULL => 1, MONTH => 1); $self->{parent}->_log("building OS matrix - 3"); $CONTENT = $self->_osname_matrix($self->{versions},'month'); $tvars{CONTENT} = $CONTENT; $self->_writepage('osmatrix-full-month',\%tvars); %tvars = (template => 'osmatrix', FULL => 1, MONTH => 1, layout => 'layout-wide'); $tvars{CONTENT} = $CONTENT; $self->{parent}->_log("building OS matrix - 4"); $self->_writepage('osmatrix-full-month-wide',\%tvars); my @vers = grep {!/^5\.(11|9|7)\./} @{$self->{versions}}; %tvars = (template => 'osmatrix', FULL => 0, MONTH => 0); $self->{parent}->_log("building OS matrix - 5"); $CONTENT = $self->_osname_matrix(\@vers,'all'); $tvars{CONTENT} = $CONTENT; $self->_writepage('osmatrix',\%tvars); %tvars = (template => 'osmatrix', FULL => 0, MONTH => 0, layout => 'layout-wide'); $tvars{CONTENT} = $CONTENT; $self->{parent}->_log("building OS matrix - 6"); $self->_writepage('osmatrix-wide',\%tvars); %tvars = (template => 'osmatrix', FULL => 0, MONTH => 1); $self->{parent}->_log("building OS matrix - 7"); $CONTENT = $self->_osname_matrix(\@vers,'month'); $tvars{CONTENT} = $CONTENT; $self->_writepage('osmatrix-month',\%tvars); %tvars = (template => 'osmatrix', FULL => 0, MONTH => 1, layout => 'layout-wide'); $tvars{CONTENT} = $CONTENT; $self->{parent}->_log("building OS matrix - 8"); $self->_writepage('osmatrix-month-wide',\%tvars); } sub _osname_matrix { my $self = shift; my $vers = shift or return ''; my $type = shift; return '' unless(@$vers); my %totals; for my $osname (sort keys %{$self->{osys}}) { if($type eq 'month') { my $check = 0; for my $perl (@$vers) { $check++ if(defined $self->{osys}{$osname}{$perl}{$type}) } next if($check == 0); } for my $perl (@$vers) { my $count = defined $self->{osys}{$osname}{$perl}{$type} ? scalar(keys %{$self->{osys}{$osname}{$perl}{$type}}) : 0; $totals{os}{$osname} += $count; $totals{perl}{$perl} += $count; } } my $index = 0; my $content = '
| OS/Perl | ' . join(" | ",@$vers) . ' | OS/Perl | ||
|---|---|---|---|---|---|
| Totals | ' . join(' | ',map {$totals{perl}{$_}||0} @$vers) . ' | Totals | ||
| ' . $osname . ' | ' . $totals{os}{$osname} . ' | '; for my $perl (@$vers) { my $count = defined $self->{osys}{$osname}{$perl}{$type} ? scalar(keys %{$self->{osys}{$osname}{$perl}{$type}}) : 0; if($count) { if($self->{list}{osname}{$osname}{$perl}) { $index = $self->{list}{osname}{$osname}{$perl}; } else { $index = $self->{list}{count}++; my %tvars = (template => 'distlist'); my @list = sort keys %{$self->{osys}{$osname}{$perl}{$type}}; $tvars{dists} = \@list; $tvars{vplatform} = $osname; $tvars{vperl} = $perl; $tvars{count} = $count; $self->_writepage('matrix/osys-'.$index,\%tvars); } } my $number = $self->{osname}{$osname}{$perl}{$type} || 0; my $class = 'none'; $class = 'some' if($number > 0); $class = 'more' if($number > $matrix_limits{$type}->[0]); $class = 'lots' if($number > $matrix_limits{$type}->[1]); $content .= qq{}
. ($count ? qq|$count $self->{osname}{$osname}{$perl}{$type}| : '-') . ' | ';
}
$content .= '' . $totals{os}{$osname} . ' | ' . $osname . ' | '; $content .= '|
| Totals | ' . join(' | ',map {$totals{perl}{$_}||0} @$vers) . ' | Totals | ||
| OS/Perl | ' . join(" | ",@$vers) . ' | OS/Perl |
| Platform/Perl | ' . join(" | ",@$vers) . ' | Platform/Perl | ||
|---|---|---|---|---|---|
| Totals | ' . join(' | ',map {$totals{perl}{$_}||0} @$vers) . ' | Totals | ||
| ' . $platform . ' | ' . $totals{platform}{$platform} . ' | '; for my $perl (@$vers) { my $count = defined $self->{pass}{$platform}{$perl}{$type} ? scalar(keys %{$self->{pass}{$platform}{$perl}{$type}}) : 0; if($count) { if($self->{list}{platform}{$platform}{$perl}) { $index = $self->{list}{platform}{$platform}{$perl}; } else { $index = $self->{list}{count}++; my %tvars = (template => 'distlist'); my @list = sort keys %{$self->{pass}{$platform}{$perl}{$type}}; $tvars{dists} = \@list; $tvars{vplatform} = $platform; $tvars{vperl} = $perl; $tvars{count} = $count; $self->_writepage('matrix/list-'.$index,\%tvars); } } my $number = $self->{platform}{$platform}{$perl}{$type} || 0; my $class = 'none'; $class = 'some' if($number > 0); $class = 'more' if($number > $matrix_limits{$type}->[0]); $class = 'lots' if($number > $matrix_limits{$type}->[1]); $content .= qq{}
. ($count ? qq|$count $self->{platform}{$platform}{$perl}{$type}| : '-') . ' | ';
}
$content .= '' . $totals{platform}{$platform} . ' | ' . $platform . ' | '; $content .= '|
| Totals | ' . join(' | ',map {$totals{perl}{$_}||0} @$vers) . ' | Totals | ||
| Platform/Perl | ' . join(" | ",@$vers) . ' | Platform/Perl |