lunedì 26 marzo 2018

Verificare sync NTP con PHP su Raspberry Pi

Aggiornamento 02/04/2018: lo stesso script per il demone Chrony è a questo indirizzo.

Nel caso di applicazioni complesse su Raspberry Pi, torna utile verificare se l'NTP è sincronizzato con una sorgente e quindi si può fare affidamento sulla funzione microtime(true) per le temporizzazioni.

Nel caso in cui sia richiesta all'applicazione una risposta quasi-realtime (il kernel standard del Raspberry non consente l'utilizzo in hard-realtime), la chiamata periodica al comando ntpstat introduce una latenza che porterebbe all'esecuzione a singhiozzo del programma.

La soluzione adottata è quella di creare un file su filesystem tmpfs, che risiede in RAM, che viene aggiornato da un processo separato dal programma principale, il quale a sua volta verificherà il contenuto del file per sapere se l'NTP è sincronizzato oppure no. La lettura del file è molto più veloce e di durata prevedibile rispetto all'esecuzione di ntpstat, tanto da poter essere chiamata ad ogni ciclo del programma.

Per prima cosa bisogna creare un filesystem tmpfs. Creiamo il mountpoint:

$ sudo mkdir /mnt/pirun1/

Il filesystem dovrà essere montato prima dell'avvio dello script, è consigliabile aggiungere una riga a /etc/fstab:

tmpfs /mnt/pirun1 tmpfs size=256k

ntp dovrà essere installato e configurato, anche ntpstat dovrà essere installato.
Lo script creerà il file /mnt/pirun1/NtpSync, che conterrà "1" se NTP è in sync, "0" in caso contrario, può essere lanciato in background o con screen (i.e. in /etc/rc.local) e dovrà restare sempre in esecuzione.

<?php

error_reporting
(E_ALL);

# Apro in scrittura il file su tmpfs
# se non esiste lo crea
$fhandle fopen("/mnt/pirun1/NtpSync""c");

# La scrittura dello stato avviene solo in caso di acquisizione
# o perdita del sync, per risparmiare risorse
# Lo stato del ciclo precedente viene salvato nella variabile:
$LastValue 2;
# Inizializzo a 2 in modo che poi, confrontata con 1 o 0 sia sempre falsa e
# faccia scrivere sempre il file temporaneo all'avvio dello script;

while (true) {
  
system("ntpstat > /dev/null 2> /dev/null"$return_var);
  
/*
   * Non mi interessa l'output di ntpstat, la solo la variabile di ritorno:
   * è =0 se è in sync, !=0 se è fuori sync
   *
   * Oltre a definire una variabile per identificare lo stato synced/unsynced,
   * imposto l'intervallo di controllo: se sono in sync, posso accettare
   * un ritardo nella segnalazione (l'orario non sarà immediatamente sbagliato),
   * ma quando vado in sync lo devo sapere in tempi brevi.
   */
  
if ($return_var === 0) {
    
$IsSynced 1;
    
$PollingInterval 1000000;
  } else {
    
$IsSynced 0;
    
$PollingInterval 20000;
  }

  
# Se cambia lo stato, il file viene aggiornato.
  
if ($IsSynced <> $LastValue) {
    
fseek($fhandle0);
    if (
$IsSynced === 1) {
      
fwrite($fhandle"1");
      
LogRow("In sync" PHP_EOL);
    } else {
      
fwrite($fhandle"0");
      
LogRow("Out of sync" PHP_EOL);
    }
  }
  
$LastValue $IsSynced;
  
usleep($PollingInterval);
}

function 
LogRow($text) {
  echo (
date("c") . " " $_SERVER["SCRIPT_NAME"] . " - " $text);
}