2010-06-24

L'ordinateur de Mademoiselle Pêche sera-t-il allumé demain ?

Voilà encore une grande question pour l'humanité, et une occasion de jouer à nouveau avec gnuplot. D'après le graphe présentant une semaine générique, et en admettant qu'on soit lundi, la réponse est "plutôt oui, aux alentours de midi".




Pour fabriquer ce graphe, il faut quelques données.

L'idéal serait d'avoir un un fichier "days.txt" qui, sur chaque ligne, contiendrait "D/HH P", où D serait le jour de la semaine, HH serait l'heure, ou plutôt, la tranche horaire entre HH et HH+1, et P serait le pourcentage de chances que l'ordinateur de Mademoiselle Pêche soit allumé au jour D et à l'heure HH. Malheureusement, d'après la documentation gnuplot, il n'est pas possible de paramétrer la directive "set timefmt" pour lui demander de lire une donnée de type "jour de la semaine". On peut par contre afficher une donnée de ce type, en utilisant "%a" dans les paramètres de la directive "set format x". Donc il va falloir tricher pour faire lire les données à gnuplot, et lui indiquer de quel "jour de la semaine" on parle. Par exemple, on peut fixer l'année. Disons (presque au hasard) qu'on fixe l'année à 1970. La directive "set timefmt" accèpte "%j" en paramètre, pour indiquer le "jour de l'année" (de 1 à 365). Donc si on parle de "1970/1", on désigne le jour 1 de l'année 1970. La commande suivante indique qu'il s'agissait d'un jeudi :

perl -e 'print(scalar(localtime(0)) . "\n");'
Thu Jan  1 01:00:00 1970

Et avec la commande suivante, en ajoutant 4 jours, on constate que le jour 5 (1+4) de l'année 1970 était un lundi :

perl -e 'print(scalar(localtime(4 * 24 * 3600)) . "\n");'
Mon Jan  5 01:00:00 1970

Donc on peut donner en entrée à gnuplot des lignes du type "1970/D+4 P", avec D représentant le "jour de la semaine", et variant de 1 pour lundi, à 7 pour dimanche : de 1970/5 à 1970/11.

Pour le pourcentage de chances que l'ordinateur de Mademoiselle Pêche soit allumé, c'est un peu moins trivial : je ne suis pas voyant. On va considérer que l'histoire se répète (ou que Mademoiselle Pêche a ses petites habitudes), et que si, par le passé, l'ordinateur était généralement allumé le mardi entre 11H et 12H, alors il le sera, à la même heure, mardi prochain. Pour le passé, c'est plus simple. On peut par exemple regarder le contenu des fichiers syslog, et se dire que s'il y a une ligne de log datée du jour D et de l'heure HH, alors c'est que l'ordinateur était allumé. Mademoiselle Pêche fait partie de ces gens étonnants qui éteignent régulièrement leur ordinateur.

Plus on a de données, mieux c'est, donc on peut configurer l'ordinateur de Mademoiselle Pêche pour stocker une grande période de logs. Mademoiselle Pêche est sous Ubuntu/GNU/Linux, donc on peut modifier, pour cela, la valeur du paramètre "rotate" dans le fichier "/etc/logrotate.d/rsyslog" :

# /etc/logrotate.d/rsyslog
# (...)
/var/log/syslog
{
    rotate 60
    daily
    # (...)
}
# (...)

Oui mais parfois, Mademoiselle Pêche laisse l'ordinateur allumé, mais aucun log n'est produit, donc la lecture des fichiers syslog n'est pas forcément très fiable. On peut forcer rsyslog (ou autre syslog-ng) à écrire au moins un log toutes les n secondes. Pour rsyslog, il s'agit des paramètres "$ModLoad immark", et, optionnellement, "$MarkMessagePeriod" dans le fichier "/etc/rsyslog.conf".

Après quelques semaines, on peut demander à un script perl de lire ces fichiers syslog, et de produire un fichier indiquant, pour chaque jour de la semaine, et chaque heure, si l'ordinateur était allumé (valeur 100%), ou non (valeur 0%), ou parfois (autre valeur).

Le script "onoff_days.pl" :

#!/usr/bin/perl -w

# torglut
# GPL

use strict;

use Time::Local;

my $line;
my $stamp;
my $next_stamp;
my %stamp_hash;
my @stamp_array;
my %v;  # database
my $step = 3600;    # one hour

my %mon2num    = qw(
 jan 00 feb 01 mar 02 apr 03 may 04 jun 05
 jul 06 aug 07 sep 08 oct 09 nov 10 dec 11
);

# get current year
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();

&init();    # empty %v database

while ($line = <stdin>) {
    $stamp_hash{int(&stampfromline($line) / $step)} = 1;
}

delete($stamp_hash{0});    # delete failed stamps
@stamp_array = sort {$a <=> $b} (keys(%stamp_hash));    # sort stamps

$stamp       = shift(@stamp_array);    # first stamp
&add_value($stamp, 100);    # computer is ON

while ($next_stamp = shift(@stamp_array)) {
    while (++$stamp < $next_stamp) { &add_value($stamp, 0); }    # OFF
    &add_value($stamp, 100);                                        # ON
}

&print_stats();

sub stampfromline {
    my $line = shift();
    if ($line =~ m/^(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+/) {
        return(&timelocal($5, $4, $3, $2, $mon2num{lc($1)}, $year));
    } else {
        return(0);
    }
}

sub add_value {
    my $date  = shift();
    my $value = shift();
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($date * $step);
    # if $wday == 0 then it means Sunday :
    if ($wday == 0) { $wday = 7; }
    $v{$wday}{$hour}{number} += 1;
    $v{$wday}{$hour}{value}  += $value;
}

sub init {
    for (my $d = 1; $d < 8; $d++) {
        for (my $h = 0; $h < 24; $h++) {
            $v{$d}{$h}{number} = 0;
            $v{$d}{$h}{value}  = 0;
        }
    }
}

sub print_stats {
    for (my $d = 1; $d < 8; $d++) {
        for (my $h = 0; $h < 24; $h++) {
            my $p = 0;
            if ($v{$d}{$h}{number} != 0) {
                $p = int($v{$d}{$h}{value} / $v{$d}{$h}{number});
            }
            my $h_str = sprintf("%02d", $h);
            my $dd = $d + 4;    # Day 4 of year 1970 was Sunday
            print('1970/' . $dd . '/' . $h_str . ' ' . $p . "\n");
        }
    }
}

La commande à lancer pour produire le fichier "days.txt" :

zcat syslog.*.gz | onoff_days.pl > days.txt

Le fichier de commandes gnuplot "plot_days.gp" :

set output "computer_days.png"
set terminal png size 800,600
set xdata time
set timefmt "%Y/%j/%H"
set format x "%a %H"
set yrange [0:100]
set grid
set key left box
plot "days.txt" using 1:2 with lines title " On"

La commande pour demander à gnuplot de tracer la courbe sur une semaine :

gnuplot plot_days.gp

Et voilà, on peut répondre à la question initiale. Sauf qu'à y regarder de près, c'est plutôt moche comme graphe (je n'ai peut-être pas assez de données pour arrondir la courbe).

Voici donc un autre script perl, un autre fichier de commandes gnuplot, et une autre courbe, non pas sur une semaine, mais sur une journée, cette fois-ci :

Fichier "onoff_hours.pl" :

#!/usr/bin/perl -w

# torglut
# GPL

use strict;

use Time::Local;

my $line;
my $stamp;
my $next_stamp;
my %stamp_hash;
my @stamp_array;
my %v;            # database
my $step = 3600;    # one hour

my %mon2num    = qw(
 jan 00 feb 01 mar 02 apr 03 may 04 jun 05
 jul 06 aug 07 sep 08 oct 09 nov 10 dec 11
);

# get current year
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();

&init();    # empty %v database

while ($line = ) {
    $stamp_hash{int(&stampfromline($line) / $step)} = 1;
}

delete($stamp_hash{0});        # delete failed stamps
@stamp_array = sort {$a <=> $b} (keys(%stamp_hash));    # sort stamps

$stamp    = shift(@stamp_array);    # first stamp
&add_value($stamp, 100);    # computer is ON

while ($next_stamp = shift(@stamp_array)) {
    while (++$stamp < $next_stamp) { &add_value($stamp, 0); }    # OFF
    &add_value($stamp, 100);                    # ON
}

&print_stats();

sub stampfromline {
    my $line = shift();
    if ($line =~ m/^(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+/) {
        return(&timelocal($5, $4, $3, $2, $mon2num{lc($1)}, $year));
    } else {
        return(0);
    }
}

sub add_value {
    my $date  = shift();
    my $value = shift();
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($date * $step);
    $v{$hour}{number}    += 1;
    $v{$hour}{value}    += $value;
}

sub init {
    for (my $h = 0; $h < 24; $h++) {
        $v{$h}{number}    = 0;
        $v{$h}{value}    = 0;
    }
}

sub print_stats {
    for (my $h = 0; $h < 24; $h++) {
        my $p = 0;
        if ($v{$h}{number} != 0) {
            $p = int($v{$h}{value} / $v{$h}{number});
        }
        my $h_str = sprintf("%02d", $h);
        print($h_str . ' ' . $p . "\n");
    }
}

Fichier "plot_hours.gp" :

set output "computer_hours.png"
set terminal png size 800,600
set xdata time
set timefmt "%H"
set format x "%H"
set yrange [0:100]
set grid
set key left box
plot "hours.txt" using 1:2 smooth csplines with lines title " On"

Commandes :

zcat syslog.*.gz | onoff_hours.pl > hours.txt
gnuplot plot_hours.gp

La courbe générée est plus sympa.

Et on voit, par exemple, qu'il serait judicieux de lancer une sauvegarde distante aux alentours de 11H du matin, car l'ordinateur a des chances d'être allumé dans cette tranche horaire :

rsync -av --exclude='*/Trash/*' --exclude='.thumbnails/*' --exclude='*Cache/*' --exclude='*cache/*' -e "ssh -l peche -p 2626" peche@peche.example.org:/home/peche/ /home/torglut/peche/.

Aucun commentaire:

Enregistrer un commentaire