Archives de catégorie : BOA Pi HomeDashScreen

PiHomeDashScreen – 13 – Faire tomber la neige

snowL’hiver arrive, et avec lui, espérons-le, la neige 🙂 J’aime beaucoup la neige, c’est joli, et c’est très relaxant de regarder tomber les flocons dans le calme 🙂

Sur un écran, l’effet est le même 🙂 J’utilise depuis longtemps Xsnow sur mon bureau. C’est old-school au niveau du design, mais c’est charmant de voir la neige s’amasser au desssus des fenêtres du bureau 🙂 Je n’ai malheureusement pas trouvé le moyen de faire tomber les flocons de neige de Xsnow « au premier plan » c’est à dire devant la fenêtre du navigateur qui affiche les données du dashscreen.

Continuer la lecture

12 – En résumé

IMG_5413Ce dashscreen est accroché au mur de mon séjour depuis presque un an maintenant, et je m’en sers tous les jours 🙂 L’affichage de l’heure et de la date en fait une horloge pratique et très lisible depuis toute la pièce. L’affichage de la météo est également une information utile au quotidien. Quant à la Terre et la Lune, je reconnais que l’utilité est moindre, mais je trouve cela vraiment chouette :). Si ce n’est pas votre truc, vous pouvez facilement remplacer ces images par des photos, pour ajouter une fonction « cadre photo numérique » intégrée à votre dashscreen 😉

Les autres modules, ping, TeamSpeak, bande passante et VPN me sont utiles pour surveiller ce qui se passe sur mon réseau et être alerté des pannes et autres problèmes qui peuvent survenir.

Juste au dessus du module graphique de la bande passante, j’ai intégré un graphique du nombre de visite quotidienne de ce blog 🙂 J’utilise GAPI pour récupérer les statistiques de Google Analytics, puis JSAPI (Google Charts) pour créer le graphique.

Pour les modules, le principe est toujours le même :

  1. une <div> avec un id dans le fichier index.php
  2. une feuille de style style.css pour positionner la div en question et gérer la mise en forme des données affichées
  3. une fonction javascript dans le fichier javascript.js qui lance une requête AJAX à intervalle régulier
  4. une ligne dans le fichier ajax.php pour appeler la bonne fonction PHP
  5. une fonction PHP dans le fichier inc.php pour générer le code HTML pour les images à afficher

Télécharger les sources

Vous pouvez télécharger toutes les sources ici : PiHomeDashScreen

Note : Vous devrez adapter le code à vos besoins.

Etendre le projet ?

Il sera vraiment intéressant de répertorier tous les modules créer sur ce principe par tous les utilisateurs qui se lance dans la réalisation d’un dashscreen. Nous pourrions mettre en place un « dépôt » pour stocker et partager tous les modules existants 🙂 Si cela intéresse du monde, n’hésitez pas à foncer 🙂

Nous pourrions également proposer une appli en ligne avec tous ces modules pour que le Pi n’est qu’à pointer sur une URL pour afficher une page web avec différentes options et modules activés/désactivés 😉

Remerciements

J’aimerai remercier toutes les personnes qui sont intervenues en commentaire ou sur forum, ou qui m’ont contacté directement par mail. Merci pour l’intérêt que vous portez à ce projet, ainsi que pour vos questions, remarques et astuces qui ont contribué à améliorer le projet. Merci également pour les erreurs qui m’ont été remontées avec beaucoup d’indulgence 😉

Le matériel

Un Pi et un écran suffisent pour réaliser ce projet. Vous pouvez choisir le type et la taille d’écran que vous souhaitez. Vous pouvez brancher votre Pi sur votre télé pour avoir un affichage en grand dans votre salon, ou utiliser un tout petit écran de 7″ à 10″ pour vous créer un réveil high-tech (l’idée m’a été soufflée sur un forum 🙂 ). Vous pouvez également étendre les fonctionnalités de ce dashscreen en ajoutant du son, ou un écran tactil pour interagir avec les informations affichées.

Technos et logiciels utilisés

  • Linux, Raspbian
  • script bash
  • Apache
  • PHP5
  • HTML & CSS
  • JavaScript
  • netcat
  • Xplanet
  • imagemagick

Sommaire

Forums de discussion

10 – Module 7 – VPN

J’utilise un petit serveur VPN PPTP pour que mes amis et moi puissions facilement échanger des fichiers ou jouer ensemble en réseau 🙂 C’est très facile à mettre en place aussi bien coté serveur en installant le paquet pptpd, que coté client car c’est nativement supporté par Windows.

Ce module me permet d’afficher la liste des personnes qui sont connectées à mon serveur VPN PPTP.

jarvis_screenshot_VPNJe ne détail pas dans cet article comment installer et configurer PPTPD. Voici un tutoriel qui vous aidera à mettre en place ce type de VPN : https://help.ubuntu.com/community/PPTPServer

Le serveur pptpd étant installé sur ma gateway, il faut procéder de la même manière que pour le module graphique de la bande passante pour transférer les données sur le Pi en utilisant netcat. La seule chose de nouveau ici est la commande last qui permet de récuppérer les informations de connexion des clients du VPN.

Transférer les données vers le Pi

Côté Pi (serveur netcat) :

#!/bin/bash

### BEGIN INIT INFO
# Provides:          nc_vpn_srv
# Required-Start:
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: vpn for Jarvis
# Description:       Enable service provided by daemon.
### END INIT INFO

dst_port=12346
dst_dir=/home/jarvis/vpn
dst_file=vpn_oberon.log

nc_vpn()
{
  while [ "true" ]
  do
    nc -q 1 -u -l 12346 > $dst_dir/$dst_file < /dev/null
    sleep 5
  done
}

case "$1" in
        stop)
                echo " Stoping vpn for Jarvis..."
                killall vpn
                killall nc
                ;;
        start)
                echo " Starting vpn for Jarvis..."
                nc_vpn&
                exit 0
                ;;
        *)
                exit 1
                ;;
esac

exit 0

Côté gateway (client netcat) :

#! /bin/bash

### BEGIN INIT INFO
# Provides:          nc_vpn_clt
# Required-Start:
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: vpn for Jarvis
# Description:       Enable service provided by daemon.
### END INIT INFO

src_port=8889
dst_port=12346
dst_host=10.0.0.1

nc_vpn()
{
        while [ "true" ]
        do
                last | grep ppp | grep "logged in" | nc -q 1 -p $src_port -u $dst_host $dst_port
                sleep 5
        done
}

case "$1" in
        stop)
                echo " Stoping vpn for Jarvis..."
                killall vpn
                killall nc
                ;;
        start)
                echo " Starting vpn for Jarvis..."
                nc_vpn&
                exit 0
                ;;
        *)
                exit 1
                ;;
esac

exit 0

Excécutez ces scripts au démarrage du système en les placants dans le répertoire /etc/init.d puis exécutant la commande suivante :

update-rc.d nom_du_script.sh defaults

index.php

Une simple <div> suffit car nous voulons simplement afficher une liste d’utilisateurs :

<div id="vpn"></div>

style.css

Un petit peu de mise en forme pour faire joli 🙂

/* vpn */

div#vpn
{
  left              : 550px;
  bottom            : 10px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
  color : #FFF;
}

td.vpn
{
  text-align        : left;
  font-size         : 30px;
  padding           : 5px;
}

img.vpn
{
  width : 20px;
  height : 20px;
}

javascript.js

Une simple requête AJAX permet de récupérer les données à afficher.

/* VPN */

var vpn_timeout;

function vpn ()
{
  $.ajax({
    async : false,
    type: "GET",
    url: "./ajax.php",
    data: "block=vpn",
    success: function(html){
      $("#vpn").html(html);
    }
  });

  vpn_timeout = setTimeout("vpn()", 5000);
}

ajax.php

Rien de particulier pour ce module :

  /////////////////////////////////////////////////
  //  VPN PPTPD
  /////////////////////////////////////////////////

  if($block == 'vpn'){
    echo vpn();
  }

inc.php

Deux fonctions sont nécessaires pour ce module :

  • vpn_parseData() : parse le fichier envoyé à travers netcat
  • vpn() : créé un tableau HTML avec les noms des utilisateurs connectés
  /////////////////////////////////////////////////
  //  VPN PPTPD
  /////////////////////////////////////////////////

  function vpn () {

    $datas = vpn_parseData ("/home/jarvis/vpn/vpn_oberon.log");

    $html  = '';

    if(sizeof($datas) > 0){
      $html .= '<table cellspacing="0px">';
      foreach($datas as $data){
        $html .= '<tr>';
        $html .= '<td valign="middle"><img class="vpn" src="pict/vpn.png"></td><td class="vpn">'.$data[0].'</td>';
        $html .= '</tr>';
      }
      $html .= '</table>';
    }

    return $html;
  }

  function vpn_parseData ($stat_file) {
    $datas = array();
    if(filemtime($stat_file) < time()-10){return $datas;}
    $stats = fopen($stat_file, 'r');
    while (($line = fgets($stats)) !== false) {
      $explode_line = str_word_count($line, 1, "0123456789.");
      $datas[]  = $explode_line;
    }
    fclose($stats);
    return $datas;
  }

Et voilà pour ce dernier module qui s’avère très simple 🙂

9 – Module 6 – Graphique de la bande passante

Pour diverses raisons, il peut intéressant de s’avoir si notre bande passante internet est saturée ou non. Ce module affiche un historique d’environ 5 minutes de l’utilisation de la bande passante descendante (download) et montante (upload). De cette manière, je peux voir en un seul coup d’oeil si quelque chose d’anormal se passe sur mon réseau.

jarvis_screenshot_ifstat

Introduction

Ce module est de loin le plus compliqué de tous. D’une part, il va falloir récupérer le débit instantané utilisé par l’interface réseau de la gateway qui est reliée à la Box internet. Puis, il va falloir transmettre ces données au Pi sur lequel fonctionne le dashscreen. Enfin, il faudra créer un graphique pour représenter ces informations…

En effet, j’ai choisi de ne pas utiliser de soft type mrtg qui sont spécialisés pour ce genre de chose. Je préfère installer le minimum de soft, et utiliser quelques commandes de base. Pour faire quelque chose de simple (comme afficher la bande passante sur la graphique), pas besoin de sortir l’artillerie lourde 🙂 Et puis …si on ne se compliquait pas la vie, ce serait beaucoup moins drôle, n’est ce pas ? 😉

Mesurer la bande passante

ifstat est un petit outil en ligne de commande qui affiche les statistiques d’utilisation d’une interface réseau. Dans un terminal, taper la commande suivante :

ifstat -n -i eth0

Et vous obtiendrait en continue, quelque chose dans le genre :

       eth0       
 KB/s in  KB/s out
    0.45      0.06
    0.71      0.39
    2.85      0.76
    0.57      0.06
    1.86      0.19
    0.45      0.06
    0.45      0.06
    0.45      0.06
    0.57      0.06
    0.59      0.13
    0.45      0.06
    0.45      0.06
    0.59      0.26
    0.57      0.06
    1.74      0.13
    ...

Chaque seconde on obtient le débit instantané (ou presque), en KBytes/s, c’est à dire en Ko/s 🙂 C’est exactement ce qu’il nous faut 🙂 Il convient maintenant d’envoyer ces informations en continue sur notre Pi.

Transmettre les données au Pi à travers le réseau

Le moyen le plus simple de transmettre une info d’un ordinateur à un autre c’est de les envoyer dans des paquets UDP… C’est tellement trivial qu’il ma fallu plusieurs jours avant d’en avoir l’idée !

La commande netcat est tout à fait appropriée pour ce type d’opération. Sur une machine A qui joue le rôle de serveur faites :

nc -u -l 12345
  • -u : pour utiliser l’UDP
  • -l : pour passer en mode écoute (serveur)
  • 12345 : un numéro de port découte

Puis sur une machine B qui joue le rôle de client, faites :

nc -p 8888 -u 10.0.0.1 12345
  • -p 8888 : un port source
  • -u : pour utiliser l’UDP
  • 10.0.0.1 : adresse IP de la machine A (serveur)
  • 12345 : port d’écoute de la machine A (serveur)

Et hop, tout ce que vous tapez au clavier sur B, s’affiche sur A 🙂

Albert Einstein disait « Tout devrait être rendu aussi simple que possible, mais pas plus ! ».

A méditer 😉

Pour transmettre les données renvoyées par la commande ifstat à travers netcat, nous utiliserons un pipe. Voici les deux scripts complets (client/serveur) pour réaliser cette opération :

  • Script serveur à exécuter sur le Pi nc_ifstat_srv.sh :
### BEGIN INIT INFO 
# Provides: nc_ifstat_srv 
# Required-Start: 
# Required-Stop: 
# Default-Start: 2 3 4 5 
# Default-Stop: 0 1 6 
# Short-Description: ifstat for Jarvis 
# Description: Enable service provided by daemon. 
### END INIT INFO

dst_port=12345
dst_dir=/home/jarvis/ifstat
dst_file=eth0.log

nc -u -l $dst_port >> $dst_dir/$dst_file&
  • Script client à exécuter sur votre gateway/routeur nc_ifstat_clt.sh  :
### BEGIN INIT INFO
# Provides:          nc_ifstat_clt
# Required-Start:
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: ifstat for Jarvis
# Description:       Enable service provided by daemon.
### END INIT INFO

src_port=8888
dst_port=12345
dst_host=10.0.0.2
eth=eth0

nc_ifstat()
{
	while [ "true" ]
	do
  		ifstat -n -i $eth | nc -p $src_port -u $dst_host $dst_port
  		sleep 5
	done
}

case "$1" in
        stop)
		echo " Stoping ifstat for Jarvis..."
                killall ifstat
		killall nc
		;;
        start)
		echo " Starting ifstat for Jarvis..."
		nc_ifstat&
		exit 0	
                ;;
        *)
                exit 1
                ;;
esac

exit 0

Pour exécuter le script au démarrage de la machine, placer le dans le répertoire /etc/init.d puis lancer la commande :

update-rc.d nom_du_script.sh defaults

A ce stade, vous devriez avoir un fichier sur votre Pi dans lequel, chaque seconde, la bande passante instantanée y est ajoutée en fin de fichier 🙂 Il ne nous reste plus qu’à lire ce fichier avec un script PHP, et dessiner le graphique pour représenter les données qu’il contient 🙂

Création du graphique en PHP

Ici encore, j’aurais pu utiliser une lib graphique en PHP, en JavaScript ou en n’importe quoi d’autre. Mais au lieux de sous exploiter une usine à gaz difficilement personnalisable, j’ai préféré créer ma propre fonction graphique en PHP. Vous allez voir que c’est beaucoup plus simple que ce qu’on peut s’imaginer 🙂

Voici ma fonction imagickHisto() :

function imagickHisto ($max, $eth = '', $up_down = 'down') {

  $datas = parseData ("eth0.log", $up_down);

  $width            = 304; // largeur du graphique
  $height           = 100; // hauteur du graphique
  $padding          = 1;
  $ticks            = 5;
  $background_color = '#000'; // couleur du fond
  $axes_color       = '#555'; // couleur des axes

  if($up_down == 'down'){
    $data_color       = '#1D1'; // couleur du graphique pour le download
  }
  else{
    $data_color       = '#D11'; // couleur du graphique pour l'upload
  }

  $nb_values        = $width - 2*$padding - 2;
  $max_value        = $height - 2*$padding - 4;

  $nb_datas         = sizeof($datas);
  $trim             = $nb_values - $nb_datas;

  if($trim < 0){$trim = 0;}

  $image = new Imagick();
  $image ->newImage( $width, $height, new ImagickPixel($background_color) );
  $draw  = new ImagickDraw();
  $draw->setStrokeColor( new ImagickPixel($axes_color) );

  $xx1    = $padding;
  $xy1    = $height - $padding - 1;
  $xx2    = $width - $padding - 1;
  $xy2    = $xy1;
  $yx1    = $xx1;
  $yy1    = $xy1;
  $yx2    = $yx1;
  $yy2    = $padding;
  $half_y = $height/2;
  $half_x = $width/2;

  $draw->line  ( $xx1, $xy1, $xx2, $xy2 );
  $draw->line  ( $yx1, $yy1, $yx2, $yy2 );

  $draw->line  ( $yx1, $yy2, $yx1+$ticks, $yy2 );
  $draw->line  ( $yx1, $half_y, $yx1+$ticks, $half_y );

  $draw->setStrokeColor( new ImagickPixel($data_color) );

  $first_x = $xx1 + 1 + $trim;
  $last_x  = $xx2 - 1;
  $first_y = $xy1 - 1;
  $last_y  = $yy2 + 1;

  for($i=0;$i<$nb_values;$i++){
    if(isset($datas[$i])){
      $value   = $datas[$i]*$max_value/$max;
      $value_y = $first_y - $value;
      $value_x = $first_x + $i;
      $draw->line  ( $value_x, $first_y, $value_x, $value_y );
    }
  }

  $image->drawImage( $draw );

  $text_draw = new ImagickDraw();
  $text_draw->setFillColor($axes_color);
  $text_draw->setFontSize( 12 );
  $image->annotateImage($text_draw, $half_x-20, $padding+10, 0, "$eth - $up_down");

  $image->setImageFormat( "png" );
  header( "Content-Type: image/png" );
  echo $image;
  exit;
}

Veuillez accepter mes excuses pour le manque cruel de commentaire dont fait l’objet cette fonction. Pour résumer, la fonction prend trois paramètres :

  • $max : la valeur max (en Ko) pour borner le graphique. Par exemple, mon débit réel maximum atteint 1800 Ko/s. Je choisie donc $max = 2000.
  • $eth : nom de l’interface (exemple eth0)
  • $up_down : « up » ou « down » pour spécifier la couleur du graphique ainsi que les données à afficher issue de ifstat

La fonction lit un tableau de donnée renvoyé par la fonction parseData(), puis dessine sur le graphique une barre de 1px de large par donnée. Avec une largeur de 300px, on peut donc afficher 5 minutes d’historique de données 🙂

Voici la fonction parseData() dont le rôle est de lire le fichier contenant les données issues de la commande ifstat :

  function parseData ($stat_file, $up_down) {
    $datas = array();
    if(filemtime($stat_file) < time()-10){return $datas;}
    $stats = fopen($stat_file, 'r');
    while (($line = fgets($stats)) !== false) {
      $explode_line = str_word_count($line, 1, "0123456789.");
      if($up_down == 'down') {
        $datas[]  = $explode_line[0];
      }
      else{
        $datas[]  = $explode_line[1];
      }
    }
    fclose($stats);
    $datas = array_slice($datas, -300);
    return $datas;
  }

Cette fonction prend deux paramètres :

  • $stat_file : fichier à parser
  • $up_down : « up » ou « down » pour distinguer les données d’upload et de download

Pour la suite, nous reprennons la même démarche que pour les autres modules 🙂

index.php

Il faut une div par graphique ; nous en ajoutons donc deux au fichier index.php, une pour l’upload et une pour le download.

 <div id="ifstat_eth0_up"><img id="img_eth0_up" src="pict/blank.png"></div>
 <div id="ifstat_eth0_down"><img id="img_eth0_down" src="pict/blank.png"></div>

Comme pour le module Xplanet, il convient d’initier la source des balises images <img> avec une image vide blank.png.

style.css

Il suffit ici de positionner nos deux <div> :

/* ifstat */

div#ifstat_oberon_up
{
  right             : 20px;
  bottom            : 20px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

div#ifstat_oberon_down
{
  right             : 340px;
  bottom            : 20px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

javascript.js

Une simple requête AJAX permet de récupérer les graphiques à afficher.

/* ifstat */

var ifstat_timeout;

function ifstat () {

  var now             = new Date().getTime();

  var url_down = "ajax.php?block=ifstat&eth=wan&up_down=down&max=2000&hour="+now;
  var img_eth0_down = $("<img />").attr("src", url_eth0_down);
  $("#img_eth0_down").attr("src", url_eth0_down);

  var url_up   = "ajax.php?block=ifstat&eth=wan&up_down=up&max=150&hour="+now;
  var img_eth0_up = $("<img />").attr("src", url_eth0_up);
  $("#img_eth0_up").attr("src", url_eth0_up);

  ifstat_timeout = setTimeout("ifstat()", 5000);
}

Même principe que pour le module Xplanet, il convient de précharger les images avant de les afficher. Je fixe le délais de rafraichissement à 5 secondes, ce qui est suffisant.

ajax.php

Le code à ajouter dans ce fichier reste élémentaire comme pour les autres modules. Il ne faut toutefois pas oublier de prendre en compte les paramètres de la fonction imagickHisto() :

  /////////////////////////////////////////////////
  //  IFSTAT
  /////////////////////////////////////////////////

  if($block == 'ifstat'){
    imagickHisto ($_REQUEST['max'], $_REQUEST['eth'], $_REQUEST['up_down']);
  }


inc.php

Dans ce fichier, il suffit de copier-coller les fonctions imagickHisto() et parseData() qui ont été décrites plus haut dans cet article.

Voilà le rendu final en gros plan, lorsque la bande passante est très sollicitée :

ifstat

7 – Module 4 – Ping de serveur

Si vous avez des serveurs, des NAS, ou n’importe quel autre type de machine dont vous souhaitez connaitre l’état (allumé/éteint, ou disponible/indisponible), ce type de module, encore une fois très simple, vous sera d’une grande utilité 🙂

jarvis_screenshot_pingL’idée est d’afficher en vert les serveurs qui répondent correctement aux solicitations du module, et en rouge ceux qui ne répondent pas. Pour accentuer le caractère alarmant d’une machine qui ne répond pas, nous ajouterons une animation pour optenir ce résultat :

ping

index.php

Comme pour les autres modules, une seule <div> suffit.

 <div id="ping"></div>

La balise reste vide, et sera « remplie » par la fonction JavaScript.

style.css

Pour ce module, la feuille de style est à peine plus longue :

/* ping */

div#ping
{
  left              : 0px;
  bottom            : 0px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

td.ping
{
  font-size         : 30px;
  text-align        : left;
  border-radius     : 10px;
  padding           : 5px 10px;
  font-weight       : bold;
}

td.ping_up
{
  color             : #00DD00;
}

td.ping_down
{
  background-image  : url('pict/warning.gif');
  color             : #DD0000;;
}

Les noms des machines sont affichés dans un tableau <table> dont chaque case <td> peut soit utiliser le style « ping_up« , si la machine répond, soit « ping_down«  si la machine ne répond pas. Dans ce dernier cas, nous utiliserons une image, un gif animé, pour faire « clignoter » la case en rouge, du mois, en donner l’impression 🙂

javascript.js

La fonction javascript de ce module ne fait qu’une requête AJAX, et affiche le résultat dans la div de la page index.php :

/* PING */

var ping_timeout;

function ping ()
{
  $.ajax({
    async : false,
    type: "GET",
    url: "./ajax.php",
    data: "block=ping",
    success: function(html){
      $("#ping").html(html);
    }
  });

  ping_timeout = setTimeout("ping()", 30000);
}

Il est important d’avoir une vision temps réel de l’état de ses machines, nous fixons donc le délai de rafraichissement à 30 secondes. Vous pouvez réduire ce délai selon vos besoins, mais veillez à ce qu’il reste supérieur au temps que met la fonction PHP pour tester toutes vos machines. Dans mon cas, je surveille six machines dont trois à travers internet. En moyenne, il faut 4 à 5 secondes pour tester toutes les machines. Toutefois, il n’est pas rare que cela prenne 20 secondes ! J’ai donc fixer à 30 secondes le temps à attendre entre chaque rafraichissement du module.

ajax.php

Il suffit de rajouter ces quelques lignes au fichier ajax.php pour que la bonne fonction soit appelée :

/////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  if($block == 'ping'){
    echo ping();
  }

inc.php

Dans ce fichier nous allons déclarer la fonction ping() qui est chargée de lancer les requêtes sur les différents serveurs, puis de construire le tableau HTML qui sera affiché.

En général, pour surveiller l’état d’un serveur, on utilise la commande ping qui permet simplement de savoir si la machine ciblée répond aux requêtes ICMP. Cela pose plusieurs problèmes. En effet, il est fort probable que le trafic ICMP soit bloqué par un firewall en amont du serveur visé ce qui faussera le résultat. De plus, si vous souhaiter surveiller un serveur web par exemple, il se peut très bien que la machine réponde bien aux pings, mais que le service web soit indisponible. Les pings n’ont donc que peu d’intéret…

L’idée, est plutôt de verier si les services qui tournent sur nos machines sont bien disponibles. Pour cela, nous allons simplement ouvrir une socket grâce à la fonction PHP fsockopen(). Si l’ouverture de la socket échoue c’est que le service n’est pas disponible 🙂

/////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  function ping () {
    $hosts    = array();
    // Définition des machines à tester
    $hosts_ip = array(
                    'Oberon'    => array('10.0.0.1',  '22'),    // SSH
                    'Triton'    => array('10.0.0.2',  '15915'), // TeamSpeak
                    'Ananke'    => array('10.0.0.3',  '2049'),  // NFS
                    'ds110j'    => array('10.0.0.4',  '2049'),  // NFS
                    'ds210j'    => array('10.0.0.5',  '2049'),  // NFS
                    'ds212j'    => array('10.0.0.6',  '2048')   // NFS
                );

    // pour chaque machine on test l'ouverture d'une socket sur le port spécifié
    // on stock le résultat 'up' ou 'down' dans la variable $hosts
    foreach($hosts_ip as $hostname => $host_data){
      $host_ip    = $host_data[0];
      $host_port  = $host_data[1];
      $socket     = 0;
      $socket     = @fsockopen($host_ip, $host_port, $errno, $errstr, 3);
      if($socket && !$errno){$hosts[$hostname] = 'up';}
      else{$hosts[$hostname] = 'down';}
    }

    // construction du tableau HTML
    // pour chaque machine testée, on affiche son nom et on fixe le style 'ping_up' ou 'ping_down' à la balise td
    $html  = '';
    $html .= '<table cellspacing="10px">';
    $c=0;
    foreach($hosts as $hostname => $host_status){
      if($c == 0){$html .= '<tr>';}
      $html .= '<td class="ping ping_'.$host_status.'">'.$hostname.'</td>';
      $c++;
      if($c == 2){$c = 0; $html .= '</tr>';}
    }
    if($c != 0){$html .= '</tr>';}
    $html .= '</table>';

    return $html;
  }

La variable $hosts_ip contient la liste des machines à tester et afficher. Le nom à affiché, l’adresse IP, et le port TCP du service à tester y sont stocké. Vous devez adapter les noms, adresses IP et numéro de port à vos besoins.

Comme vous pouvez le voir dans les commentaires du code, selon la machine à tester, j’ouvre une socket sur des services tels que SSH, TeamSpeak et NFS.  Cela fonctionne également très bien sur des serveurs FTP, HTTP, POP3, etc,… Tant que le service à tester écoute sur un port TCP, cela fonctionnera 🙂

Note : Il est possible d’optimiser cette fonction PHP en parallèlisant l’ouverture des sockets sur chaque machine. En effet, cette fonction teste les machines une par une ce qui peut être long… En « forkant » cette tâche grâce à la fonction PHP pcntl_fork(), il est possible de lancer toutes les requêtes d’un seul coup et ainsi diminuer le temps total que met la fonction pour renvoyer le tableau HTML à afficher. N’hésitez pas à laisser un commentaire si vous souhaitez plus de détails sur cette manière de procéder 🙂

Optimisation en utilisant le fork

Suite à la demande de lepopeye en commentaire de cet article, je vais expliquer comment paralléliser l’ouverture des sockets et ainsi gagner un temps précieux à chaque exécution de la fonction.

Le principe est simple : Pour chaque machine à tester, nous allons créer un processus fils grâce à la fonction PHP fork_pcntl() qui lancera la fonction fsockopen(). Comme les processus fils et le processus père ne peuvent pas partager de variables (à moins de faire appel à des techniques de mémoire partagée…), il convient de stocker les résultats dans une petite base de données SQlite 🙂 Le processus père attend que tous les processus fils soient terminés pour aller lire le contenu de la base de données SQlite (qui contient l’état de chaque machine). Le reste est inchangé, nous construisons le tableau HTML à afficher comme dans la fonction ping() initiale 🙂 A savoir que le fork n’est possible qu’en ligne de commande, il faudra donc adapter le fichier ajax.php de cette manière :

  /////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  if($block == 'ping'){
    echo shell_exec('php5 fork.php'); // fork.php étant le fichier dans lequel se trouve la fonction ping_fork();
  }

Voici la version « forkée » de la fonction ping(), que vous pouvez utiliser directement en la copiant dans un fichier fork.php :

function ping_fork() {

  $hosts_ip = array(
                    'Oberon'    => array('10.0.0.1',  '22'),    // SSH
                    'Triton'    => array('10.0.0.2',  '15915'), // TeamSpeak
                    'Ananke'    => array('10.0.0.3',  '2049'),  // NFS
                    'ds110j'    => array('10.0.0.4',  '2049'),  // NFS
                    'ds210j'    => array('10.0.0.5',  '2049'),  // NFS
                    'ds212j'    => array('10.0.0.6',  '2048')   // NFS
                );

  $pids  = array();

  // Connexion à la base de données sqlite et création de la table hosts_status si elle n'existe pas encore
  $db = new SQLite3('ifstat/hosts.sqlite');
  $db->exec('CREATE TABLE IF NOT EXISTS hosts_status (host_name VARCHAR(10), host_status VARCHAR(5));');

  // pour chaque machine, on créé un processus fils
  foreach($hosts_ip as $host_name => $host){
    $pids[$host_name] = pcntl_fork();
    if(!$pids[$host_name]) {
      $socket = @fsockopen($host[0], $host[1], $errno, $errstr, 3);
      if($socket && !$errno){$status = 'up';}else{$status = 'down';}
      // on attend que la table hosts_status ne soit plus verrouillée par un éventuel accès concurrent
      if($db->busyTimeout(5000)){
        $db->exec("INSERT INTO hosts_status VALUES ('$host_name', '$status');");
      }
      exit();
    }
  }

  // le processus père doit attendre que tous les processus fils soient terminés
  foreach($pids as $host_name => $pid){
    pcntl_waitpid($pid, $status, WUNTRACED);
  }

  $results = $db->query('select * from hosts_status;');

  $html  = '';
  $html .= '<table cellspacing="10px">';
  $c=0;
  while($host = $results->fetchArray(SQLITE3_ASSOC)){
    if($c == 0){$html .= '<tr>';}
    $html .= '<td class="ping ping_'.$host['host_status'].'">'.$host['host_name'].'</td>';
    $c++;
    if($c == 2){$c = 0; $html .= '</tr>';}
  }
  if($c != 0){$html .= '</tr>';}
  $html .= '</table>';

  $db->exec("DELETE FROM hosts_status;");

  return $html;
}

echo ping_fork();

Le timeout de la fonction fsockopen() est ici fixé à 3 secondes, ce qui veut dire que l’on doit attendre au moins 3 secondes avant de déclarer la machine cible comme « down » (non disponible). Dans la version non forkée de la fonction ping() si une machine ne répondait pas, il fallait attendre 3 secondes avant de tester la machine suivante, et ainsi de suite. Avec mes 6 machines, si aucune ne répond, il aurait fallu attendre 6×3 = 18 secondes avant d’avoir le résultat final. Avec la version forkée, les 6 requêtes sont envoyées en même temps. Quelque soit le nombre de machines à tester, nous aurons donc une réponse au bout de 3 secondes maximum 🙂

J’utilise à présent cette version forkée sur mon dashscreen 🙂 Merci à lepopeye de m’avoir forcé un peu la main 😉

8 – Module 5 – TeamSpeak

Si vous ne connaissez pas TeamSpeak, il s’agit d’un logiciel d’audioconférence qui permet à plusieurs personnes de discuter via Internet (pour plus d’informations http://fr.wikipedia.org/wiki/TeamSpeak). Ce module sert à afficher l’état du serveur TeamSpeak (allumé/éteint) ainsi que la liste des personnes qui y sont connectées.

jarvis_screenshot_TS3Pour obtenir ces informations j’utilise TeamSpeak 3 PHP Framework qui est téléchargeable gratuitement. Cette grosse lib PHP permet d’accéder à toutes les fonctionnalités du serveur TeamSpeak3. Que demande le peuple ? 🙂

Il suffit de télécharger et décompresser TeamSpeak 3 PHP Framework à la racine de votre répertoire web.

index.php

Comme pour les autres modules, une seule <div> suffit. Vous commencez à avoir l’habitude 😉

 <div id="ts3"></div>

La balise reste vide, et sera « remplie » par la fonction JavaScript…

style.css

Feuille de style minimaliste pour ce module :

/* ts3 */

div#ts3
{
  left              : 340px;
  bottom            : 10px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
  color             : #FFF;
  font-weight       : bold;
  text-align        : center;
}

td.ts3_user
{
  text-align        : left;
  font-size         : 30px;
  padding-left      : 10px;
}

javascript.js

Encore une fois, une simple requête AJAX permet de récupérer les informations à afficher.

/* TS3 */

var ts3_timeout;

function ts3 ()
{
  $.ajax({
    async : false,
    type: "GET",
    url: "./ajax.php",
    data: "block=ts3",
    success: function(html){
      $("#ts3").html(html);
    }
  });

  ts3_timeout = setTimeout("ts3()", 10000);
}

Le délai de rafraichissement est fixé à 10 secondes pour voir rapidement qui se connecte/déconnecte du serveur.

ajax.php

Cela se passe d’explications…

  /////////////////////////////////////////////////
  //  TS3
  /////////////////////////////////////////////////

  if($block == 'ts3'){
    echo ts3();
  }

inc.php

C’est ici que nous utilisons TeamSpeak 3 PHP Framework.

Dans un premier temps j’ouvre une socket sur le port 30033 (comme pour le Module 4 – Ping de serveur) pour vérifier que le serveur TeamSpeak tourne bien. Si oui, alors j’affiche la bannière du serveur (l’image triton_ts3.png), puis j’utilise  TeamSpeak 3 PHP Framework pour récupérer la liste et l’état des utilisateurs connectés 🙂

  /////////////////////////////////////////////////
  //  TS3
  /////////////////////////////////////////////////

  function ts3 () {

    $host_ip    = '10.0.0.1';
    $host_port  = '30033';
    $socket     = 0;
    $socket     = @fsockopen($host_ip, $host_port, $errno, $errstr, 3);
    $html       = '';

    if($socket && !$errno){
      require_once("./TS3_PHP_Framework-1.1.12/libraries/TeamSpeak3/TeamSpeak3.php");
      $ts3_ServerInstance = TeamSpeak3::factory("serverquery://login:password@10.0.0.1:10011/");
      $ts3_VirtualServer  = $ts3_ServerInstance->serverGetById(1);

      $html .= '<img width="200px" src="pict/triton_ts3.png" style="margin-bottom : 10px;">';
      $html .= '<table cellspacing="0px" cellpadding="0px">';
      foreach($ts3_VirtualServer->clientList() as $client){
        if($client["client_unique_identifier"] == 'serveradmin'){continue;}
        $html .= '  <tr>';
        $html .= '    <td valign="middle">';
        $html .= '      <img style="width : 20px;" src="./TS3_PHP_Framework-1.1.12/images/viewer/'.$client->getIcon().'.png" alt="">';
        $html .= '    </td>';
        $html .= '    <td valign="middle">';
        $html .= '      '.htmlspecialchars($client);
        $html .= '    </td>';
        $html .= '  </tr>';
      }
      $html .= '</table>';
    }

    return $html;
  }

Ce module est très simple à réaliser grâce à la lib  TeamSpeak 3 PHP Framework 🙂