newsstats/bin/postingstats.pl
Thomas Hochstein 66890b68d8 Update documentation.
- Fix clientstats doc (copied from hoststats).
- Add some more examples ro README.

Signed-off-by: Thomas Hochstein <thh@thh.name>
2025-06-01 16:40:31 +02:00

450 lines
13 KiB
Perl
Executable file

#!/usr/bin/perl
#
# postingstats.pl
#
# This script will create statistic postings from NewsStats output.
# It defaults to statistics for de.* posted to de.admin.lists, but
# defaults can be changed at ----- configuration -----.
#
# It is part of the NewsStats package.
#
# Copyright (c) 2010-2012, 2025 Thomas Hochstein <thh@thh.name>
#
# It can be redistributed and/or modified under the same terms under
# which Perl itself is published.
#
# Usage:
# $~ groupstats.pl --nocomments --sums --format dump | postingstats.pl -t groups
# $~ hoststats.pl --nocomments --sums --format dump | postingstats.pl -t hosts
# $~ clientstats.pl --nocomments --sums --versions --format dump | postingstats.pl -t clients
#
BEGIN {
use File::Basename;
# we're in .../bin, so our module is in ../lib
push(@INC, dirname($0).'/../lib');
}
use strict;
use warnings;
use NewsStats qw(:DEFAULT LastMonth);
use Getopt::Long qw(GetOptions);
Getopt::Long::config ('bundling');
use constant TABLEWIDTH => 28; # width of table without newsgroup name
##### ----- pre-config -----------------------------------------------
### read commandline options
my ($Month, $Type);
GetOptions ('m|month=s' => \$Month,
't|type=s' => \$Type,
'h|help' => \&ShowPOD,
'V|version' => \&ShowVersion) or exit 1;
$Month = &LastMonth if !$Month;
if ($Month !~ /^\d{4}-\d{2}$/) {
$Month = &LastMonth;
&Bleat(1,"--month option has an invalid format - set to $Month.");
};
# parse $Type
if (!$Type) {
# default
$Type = 'GroupStats';
} elsif ($Type =~ /(news)?groups?/i) {
$Type = 'GroupStats';
} elsif ($Type =~ /(host|server)s?/i) {
$Type = 'HostStats';
} elsif ($Type =~ /(client|reader)s?/i) {
$Type = 'ClientStats';
};
my $Timestamp = time;
##### ----- configuration --------------------------------------------
my $TLH = 'de';
my %Heading = ('GroupStats' => 'Postingstatistik fuer de.* im Monat '.$Month,
'HostStats' => 'Serverstatistik fuer de.* im Monat '.$Month,
'ClientStats' => 'Newsreaderstatistik fuer de.* im Monat '.$Month
);
my %TH = ('counter' => 'Nr.',
'value' => 'Anzahl',
'percentage' => 'Prozent'
);
my %LeadIn = ('GroupStats' => <<GROUPSIN, 'HostStats' => <<HOSTSIN, 'ClientStats' => <<CLIENTSIN);
From: Thomas Hochstein <thh\@thh.name>
Newsgroups: local.test
Subject: Postingstatistik fuer de.* im Monat $Month
Message-ID: <destat-postings-$Month.$Timestamp\@mid.news.szaf.org>
Approved: thh\@thh.name
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
User-Agent: postingstats.pl/$VERSION (NewsStats)
GROUPSIN
From: Thomas Hochstein <thh\@thh.name>
Newsgroups: local.test
Subject: Serverstatistik fuer de.* im Monat $Month
Message-ID: <destat-hosts-$Month.$Timestamp\@mid.news.szaf.org>
Approved: thh\@thh.name
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
User-Agent: postingstats.pl/$VERSION (NewsStats)
HOSTSIN
From: Thomas Hochstein <thh\@thh.name>
Newsgroups: local.test
Subject: Newsreaderstatistik fuer de.* im Monat $Month
Message-ID: <destat-clients-$Month.$Timestamp\@mid.news.szaf.org>
Approved: thh\@thh.name
Mime-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
User-Agent: postingstats.pl/$VERSION (NewsStats)
CLIENTSIN
my %LeadOut = ('GroupStats' => <<GROUPSOUT, 'HostStats' => <<HOSTSOUT, 'ClientStats' => <<CLIENTSOUT);
Alle Zahlen wurden ermittelt auf einem Newsserver mit redundanter Anbin-
dung fuer de.* unter Anwendung ueblicher Filtermassnahmen. Steuernach-
richten werden nicht erfasst; Postings, die supersedet oder gecancelt
wurden, bleiben erfasst, sofern sie das System ueberhaupt (und vor der
Loeschnachricht) erreicht haben. Crosspostings werden in jeder Gruppe,
in die sie gerichtet sind, gezaehlt, aber bei Ermittlung der Summe be-
reinigt; daher ist die Postinganzahl fuer de.* gesamt niedriger als die
Summe der Postinganzahlen der Einzelgruppen.
Die Daten stehen graphisch aufbereitet unter <http://usenet.dex.de/> zur
Verfuegung.
GROUPSOUT
Alle Zahlen wurden ermittelt auf einem Newsserver mit redundanter Anbin-
dung fuer de.* unter Anwendung ueblicher Filtermassnahmen. Steuernach-
richten werden nicht erfasst; Postings, die supersedet oder gecancelt
wurden, bleiben erfasst, sofern sie das System ueberhaupt (und vor der
Loeschnachricht) erreicht haben.
HOSTSOUT
Alle Zahlen wurden ermittelt auf einem Newsserver mit redundanter Anbin-
dung fuer de.* unter Anwendung ueblicher Filtermassnahmen. Steuernach-
richten werden nicht erfasst; Postings, die supersedet oder gecancelt
wurden, bleiben erfasst, sofern sie das System ueberhaupt (und vor der
Loeschnachricht) erreicht haben. Versionsangaben werden nur gezaehlt,
wenn Sie ermittelbar sind; daher kann die Summe der Newsreader-Versionen
kleiner sein als die Postingzahl fuer den Newsreader. Ausserdem koennen
an einem Beitrag mehrere Clients beteiligt sein, bspw. der Newsreader
und ein lokaler Server wie der Hamster. Daher kann die Summe aller
Newsreader groesser sein als die Summe der Postings; auch ergeben die
Prozentzahlen dementsprechend in der Summe mehr als 100%.
CLIENTSOUT
##### ----- subroutines ----------------------------------------------
sub Percentage {
# calculate percentage rate from base value and percentage
my ($Base,$Percentage) = @_;
return ($Percentage * 100 / $Base);
}
sub Divider {
# build a divider line of $Symbol as wide as the table is
my ($Symbol,$MaxLength) = @_;
return ':' . $Symbol x ($MaxLength+TABLEWIDTH) . ":\n";
}
sub SingleVersion {
my ($LastName,$RSubValue,$RValue,$RMaxLength) = @_;
# get version to add to client name
my ($Version) = keys %{$$RSubValue{$LastName}};
$Version =~ s/^- //;
# add version to client name by creating a new name
# and deleting the old one
my ($NameVersion) = $LastName . ' ' . $Version;
$$RValue{$NameVersion} = $$RValue{$LastName};
delete($$RValue{$LastName});
$$RMaxLength = length($NameVersion) if length($NameVersion) > $$RMaxLength;
# delete single version
delete($$RSubValue{$LastName});
}
##### ----- main loop ------------------------------------------------
my (%Value, %SubValue, $SubCounter, $LastName, $SumName, $SumTotal,
$MaxLength);
if ($Type eq 'GroupStats') {
$SumName = "$TLH.ALL";
$TH{'name'} = 'Newsgroup'
} elsif ($Type eq 'HostStats') {
$SumName = 'ALL';
$TH{'name'} = 'Postingserver'
} elsif ($Type eq 'ClientStats') {
$SumName = 'ALL';
$TH{'name'} = 'Newsreader / Client'
}
### read from STDIN
$MaxLength = 0;
while(<>) {
my ($Name, $Value) = $_ =~ /(.+) (\d+)$/;
$SumTotal = $Value if $Name eq $SumName;
next if $Name =~ /ALL$/;
# handle client versions
if ($Type eq 'ClientStats' and $Name =~ /^- /) {
$SubValue{$LastName}{$Name} = $Value;
$SubCounter++;
} else {
# clients with just one version
&SingleVersion($LastName,\%SubValue,\%Value,\$MaxLength)
if ($LastName && $SubCounter == 1);
# reset version counter and client name
$SubCounter = 0;
$LastName = $Name;
$Value{$Name} = $Value;
$MaxLength = length($Name) if length($Name) > $MaxLength;
}
}
# clients with just one version (last iteration)
&SingleVersion($LastName,\%SubValue,\%Value,\$MaxLength)
if ($LastName && $SubCounter == 1);
### print to STDOUT
# calculate padding for $Heading
my $PaddingLeft = ' ' x int((($MaxLength+TABLEWIDTH-2-length($Heading{$Type}))/2));
my $PaddingRight = $PaddingLeft;
$PaddingLeft .= ' ' if (length($Heading{$Type}) + (length($PaddingLeft) * 2) +2 < $MaxLength+TABLEWIDTH);
print $LeadIn{$Type};
# print table header
print &Divider('=',$MaxLength);
printf(": %s%s%s :\n",$PaddingLeft,$Heading{$Type},$PaddingRight);
print &Divider('=',$MaxLength);
printf(": %-3s : %-6s : %-7s : %-*s :\n",
substr($TH{'counter'},0,3),
substr($TH{'value'},0,6),
substr($TH{'percentage'},0,7),
$MaxLength,$TH{'name'});
print &Divider('-',$MaxLength);
# print table
my $Counter = 0;
foreach my $Name (sort { $Value{$b} <=> $Value{$a} } keys %Value) {
$Counter++;
printf(": %3u. : %6u : %6.2f%% : %-*s :\n",
$Counter,$Value{$Name},&Percentage($SumTotal,$Value{$Name}),
$MaxLength,$Name);
# handle client versions
if ($SubValue{$Name}) {
foreach my $SubName (sort { $SubValue{$Name}{$b} <=> $SubValue{$Name}{$a} }
keys %{$SubValue{$Name}}) {
printf(": : %6u : %6.2f%% : %-*s :\n",
$SubValue{$Name}{$SubName},
&Percentage($SumTotal,$SubValue{$Name}{$SubName}),
$MaxLength,$SubName);
}
}
}
# print table footer
print &Divider('-',$MaxLength);
printf(": : %6u : %s : %-*s :\n",$SumTotal,'100.00%',$MaxLength,'');
print &Divider('=',$MaxLength);
print $LeadOut{$Type};
__END__
################################ Documentation #################################
=head1 NAME
postingstats - format and post reports
=head1 SYNOPSIS
B<postingstats> [B<-Vh>] [B<-t> I<groups|hosts|clients>] [B<-m> I<YYYY-MM>]
=head1 REQUIREMENTS
See L<doc/README>.
=head1 DESCRIPTION
This script will re-format reports on newsgroup usage created by
B<groupstats.pl>, B<hoststats.pl> or B<clientstats.pl> and create a
message that can be posted to Usenet.
=head2 Features and options
B<postingstats> will create a table with entries numbered from most
to least and percentages calculated from the sum total of all values.
It depends on a sorted list on STDIN in I<dump> format with I<sums>;
I<versions> from B<clientstas.pl> are optional.
B<postingstats> needs a B<--type> and a B<--month> to create a caption
and select matching lead-ins and lead-outs. B<--type> is also needed
to catch the correct sum total from input which differs between I<groups>
on one hand and I<hosts> or I<clients> on the other hand.
It will default to posting statistics (number of postings per group)
and last month.
Output from B<postingstats> can be piped to any C<inews> implementation,
e.g. C<tinews.pl> from L<ftp://ftp.tin.org/pub/news/clients/tin/tools/tinews.pl>
(present in C</contrib/>).
=head2 Configuration
Configuration is done by changing the code in the
C<----- configuration -----> section.
=over 3
=item C<$TLH>
Top level hierarchy the report was created for. Used for display and
sum total (only for I<groups>).
=item C<%Heading>
Hash with keys for I<GroupStats>, I<HostStats> and I<ClientStats>.
Used to display a heading.
=item C<%TH>
Hash with keys for I<counter>, I<value> and I<percentage>. Used to
create the table header for I<number>, I<quantity> and I<percentage>.
I<counter> must not be longer than 3 characters, I<value> no longer
than 6 characters and I<percentage> no longer than 7 characters.
Output will be truncated otherwise.
=item C<%LeadIn>
Hash with keys for I<GroupStats>, I<HostStats> and I<ClientStats>.
Used to create the headers for the postings. Can contain other text
that will be shown before C<%Heading>.
=item C<%LeadOut>
Hash with keys for I<GroupStats>, I<HostStats> and I<ClientStats>.
Will be shown at the end of the posting.
=back
=head1 OPTIONS
=over 3
=item B<-V>, B<--version>
Display version and copyright information and exit.
=item B<-h>, B<--help>
Display this man page and exit.
=item B<-t>, B<--type> I<groups|hosts|clients>
Set report type to posting statistics, hosts statistics or client
statistics accordingly.
=item B<-m>, B<--month> I<YYYY-MM>
Set month (for display only).
=back
=head1 INSTALLATION
See L<doc/INSTALL>.
=head1 USAGE
Create a posting from a posting statistics report for last month:
groupstats.pl --nocomments --sums --format dump | postingstats.pl -t groups
Create a posting from a posting statistics report for 2012-01:
groupstats.pl --nocomments --sums --format dump -m 2012-01 | postingstats.pl -t groups -m 2012-01
Create a posting from a host statistics report for last month:
hoststats.pl --nocomments --sums --format dump | postingstats.pl -t hosts
Create a posting from a client statistics report for last month:
clientstats.pl --nocomments --sums --versions --format dump | postingstats.pl -t clients
=head1 FILES
=over 4
=item F<bin/postingstats.pl>
The script itself.
=item F<lib/NewsStats.pm>
Library functions for the NewsStats package.
=item F<etc/newsstats.conf>
Runtime configuration file.
=back
=head1 BUGS
Please report any bugs or feature requests to the author or use the
bug tracker at L<https://code.virtcomm.de/thh/newsstats/issues>!
=head1 SEE ALSO
=over 2
=item -
L<doc/README>
=item -
L<doc/INSTALL>
=item -
groupstats -h
=item -
hoststats -h
=item -
clientstats -h
=back
This script is part of the B<NewsStats> package.
=head1 AUTHOR
Thomas Hochstein <thh@thh.name>
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2010-2012, 2025 Thomas Hochstein <thh@thh.name>
This program is free software; you may redistribute it and/or modify it
under the same terms as Perl itself.
=cut