martedì 27 agosto 2013

PostgreSQL fancy uptime

Dalla versione 8.1 PostgreSQL mette a disposizione una funzione interna, pg_postmaster_start_time(), che restituisce il momento di avvio dell'istanza corrente (ossia del postmaster).
Unendo tale funzione al current_timestamp e' possibile ottenere facilmente il tempo di uptime del server:

# SELECT date_trunc( 'seconds', current_timestamp - pg_postmaster_start_time() );
date_trunc
------------
00:32:37

In questo modo e' possibile ottenere l'intervallo temporale di uptime del server con precisione al secondo.

Un DBA/Sysadmin che si rispetti dovrebbe sempre controllare con precisione l'uptime del server, ma per chi vuole un uptime un po' piu' "fancy", ho creato una stored procedure, denominata appunto "uptime", che restituisce una stringa di testo con una frase descrittiva del tempo di esecuzione del server, opportunamento arrotondato.
Ad esempio:

# SELECT uptime();
uptime
--------------------
Almost 35 minutes

E ad esempio, dopo 47 minuti di attivita' la funzione di uptime riporta:

# SELECT uptime();
uptime
---------------
Almost 1 hour

La funzione opera in questo modo:
1) ottiene l'uptime in modo preciso;
2) spezza l'uptime nelle sue singole parti (giorni, ore, minute, secondi);
3) calcola gli arrotondamenti per eccesso/difetto. Ad esempio se sono passati piu' di 30 secondi si incrementa di una unita' il contatore dei minuti e si escludono i secondi; se analogamente sono passati piu' di 45 minuti si incrementa il contatore delle ore e si tralasciano i minuti, e cosi' via;
4) si decide quali "pezzi" far vedere (anni, giorni, ore, minuti);
5) si compone una stringa con opportuni separatori lessicali.

Ovviamente la funzione puo' essere migliorata, e buona parte del tempo speso dalla funzione stessa serve a calcolare cosa far vedere all'utente e con che arrotondamento. Questo a maggiore ragione colloca la funzione in ambito "fancy" e non certo come strumento di precisione per la manutenzione dell'istanza PostgreSQL.


Ci sono alcuni casi particolari: se il server e' stato avviato da meno di 10 minuti allora viene ritornato il valore speciale 'now' (da non confondere con l'equivalente testo spesso usato in PostgreSQL):

# SELECT uptime();
uptime
-------------
Almost now!

Se si cade nell'intervallo temporale di +10 e -15 minuti i minuti sono mostrati con maggiore precisione presunta, come ad esempio:

# SELECT uptime();
uptime
-------------------------
Pretty much 12 minutes

Di seguito il codice sorgente della funzione.


CREATE OR REPLACE FUNCTION uptime()
RETURNS text
AS $BODY$

DECLARE
current_uptime_interval interval;
current_description text;
current_hours float;
current_minutes float;
current_days float;
current_years float;
current_seconds float;
minutes_separator text;
hours_separator text;
days_separator text;
BEGIN

current_hours := 0;
current_minutes := 0;
current_days := 0;
current_years := 0;
current_seconds := 0;
minutes_separator := '';
days_separator := '';
hours_separator := '';
current_description := 'Pretty much ';

SELECT date_trunc( 'seconds', current_timestamp - pg_postmaster_start_time() )
INTO current_uptime_interval;

SELECT date_part( 'hour', current_uptime_interval )
INTO current_hours;

SELECT date_part( 'minutes', current_uptime_interval )
INTO current_minutes;

SELECT date_part( 'days', current_uptime_interval )
INTO current_days;

SELECT date_part( 'seconds', current_uptime_interval )
INTO current_seconds;



IF current_seconds > 30 THEN
current_seconds := 0;
current_minutes := current_minutes + 1;
current_description := 'Almost ';
END IF;

IF current_minutes >= 45 THEN
current_hours := current_hours + 1;
current_minutes := 0;
current_description := 'Almost ';
ELSEIF current_minutes <= 10 THEN current_minutes := 0; current_description := 'Almost '; END IF; IF current_hours >= 16 THEN
current_days := current_days + 1;
current_hours := 0;
current_minutes := 0;
current_description := 'Almost ';
END IF;

IF current_days >= 300 THEN
current_days := 0;
current_hours := 0;
current_minutes := 0;
current_years := current_years + 1;
current_description := 'Almost ';
END IF;


-- special case: the server started here!
IF current_years = 0
AND current_days = 0
AND current_hours = 0
AND current_minutes < 10 THEN RETURN 'Almost now!'; END IF; IF current_years > 0 AND current_days > 0 THEN
days_separator := ' and ';
END IF;
IF current_days > 0 AND current_hours > 0 THEN
hours_separator := ' and ';
END IF;
IF current_hours > 0 AND current_minutes > 0 THEN
minutes_separator := ' and ';
END IF;


SELECT current_description
|| CASE WHEN current_years = 1 THEN '1 year'
WHEN current_years > 1 THEN current_years || ' years'
ELSE ''
END
|| days_separator
|| CASE WHEN current_days = 1 THEN '1 day'
WHEN current_days > 1 THEN current_days || ' days'
ELSE ''
END
|| hours_separator
|| CASE WHEN current_hours = 1 THEN '1 hour'
WHEN current_hours > 1 THEN current_hours || ' hours'
ELSE ''
END
|| minutes_separator
|| CASE WHEN current_minutes = 1 THEN '1 minute '
WHEN current_minutes > 1 THEN current_minutes || ' minutes '
ELSE ''
END
INTO current_description;

RETURN current_description;
END;
$BODY$
LANGUAGE plpgsql;

Nessun commento: