Realizzazione di una API di logging per la Field Failure Data

Transcript

Realizzazione di una API di logging per la Field Failure Data
Facoltà di Ingegneria
Corso di Studi in Ingegneria Informatica
tesi di laurea specialistica
Realizzazione di una API di logging per la
Field Failure Data Analysis di sistemi
software distribuiti
Anno Accademico 2009/2010
relatori
Ch.mo prof. Domenico Cotroneo
Ch.mo prof. Stefano Russo
candidato
Vittorio Alfieri
matr. 885 / 429
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
“Se credi sempre nelle tue capacità, alla fine anche gli altri faranno lo stesso”
Vittorio Alfieri
1
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Indice
Introduzione ..................................................................................................................................... 3 1. Dependability di un sistema software .......................................................................................... 7 1.1 Definizione e attributi
7 1.2 Tecniche di valutazione
12 1.3 La Failure Data Analisys
18 2. Formati e framework di logging ................................................................................................ 23 2.1 Panoramica generale
23 2.2 Utilizzi tipici delle tecniche di logging
35 2.3 Limiti delle tecniche di logging classico ai fini della FFDA
37 3. Approccio Rule-Based alla Field Failure Data Analysis ........................................................... 40 3.1 Definizione delle regole di logging
40 3.2 Applicazione delle regole di logging ai fini della FFDA
44 3.3 Implementazione della strategia di logging Rule-Based
49 3.4 Una piattaforma per la FFDA log-based: Logbus-ng
52 4. Realizzazione delle API per la FFDA in Logbus-ng ................................................................. 57 4.1 Implementazione della API in C#
57 4.2 Porting della API lato Sorgente in C
66 4.3 Realizzazione di un Monitor per la FFDA
70 5. Un caso di studio: Apache Web Server-1.3.41 .......................................................................... 76 5.1 Instrumentazione del codice sorgente
76 5.2 Overhead introdotto dall’instrumentazione
80 5.3 Validazione dell’approccio Rule-Based
87 5.4 Conclusioni e sviluppi futuri
92 Ringraziamenti ............................................................................................................................... 94 Bibliografia .................................................................................................................................... 97 Lista delle figure presentate nel testo ............................................................................................. 99 2
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Introduzione
Nello scenario attuale, in tantissime situazioni quotidiane il software gioca un ruolo sempre più importante, cercando di supportare se non di sostituire l’intervento dell’uomo,
spesso con notevoli benefici in termini di efficienza. Se pensiamo, infatti, a tutti quei sistemi definiti come safety critical o comunque a tutti quei sistemi con elevate responsabilità, è invitabile sospettare che anche un piccolissimo bug o fallimento possa portare conseguenza anche gravissime. E’ allora importante che il software sia affidabile, dove, con il
termine affidabilità o dependability s’intende quella caratteristica che porta gli utilizzatori
a potersi "fidare" del sistema stesso e a poterlo quindi utilizzare senza particolari preoccupazioni. Si rende quindi necessario lo studio di tecniche che siano volte alla misurazione
del grado di affidabilità di un sistema, ad esempio di quante volte esso fallisce durante una
sua esecuzione, quanto sono gravi i suoi fallimenti e quanto tempo è necessario per ripristinare un corretto funzionamento dello stesso.
Una tecnica molto diffusa per tracciare il comportamento di un sistema e quella di logging.
Log in inglese significa tronco di legno; nel gergo nautico del 1700 era il pezzo di legno
fissato a una fune con nodi a distanza regolare, lanciato in mare e lasciato galleggiare
(Solcometro). Il numero di nodi fuori bordo, entro un intervallo fisso di tempo indicava
approssimativamente la velocità della nave (da qui la convenzione di indicare
la velocità di una nave in nodi).
Il logbook (1800) era il registro di navigazione, presente in ogni nave, su cui veniva segnata, ad intervalli regolari la velocità, il tempo, la forza del vento, oltre a eventi significativi
3
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
che accadevano durante la navigazione. Con il significato di giornale di bordo, o semplicemente giornale, su cui vengono registrati gli eventi in ordine cronologico il termine è
stato importato nell'informatica (1963) per indicare:
-
La registrazione cronologica delle operazioni man mano che vengono eseguite.
-
Il file su cui tali registrazioni sono memorizzate.
Oggi è un termine universalmente accettato con questo significato di base, con tutte le
sfumature necessarie nel contesto specifico. Unito al termine web (web-log) indica un diario, appunto una registrazione cronologica, in rete. Il log più semplice, dalle origini ad oggi, è un file sequenziale sempre aperto in scrittura, che viene chiuso e conservato a cadenze regolari, anche se il log può essere eventualmente un segmento di base dati con accesso
diretto mediante chiave cronologica (timestamp), tuttavia il suo utilizzo come registro cronologico non cambia[1].
Gli eventi di log sono generati dal software mediante solitamente degli appositi componenti dell’applicazione o dell’ambiente in esecuzione sulla macchina ove risiede il sistema
da osservare. In genere dai log prodotti è possibile estrarre informazioni utili alla caratterizzazione dei fallimenti che si sono verificati durante l’attività del sistema. Una tipica entry di log contiene una marca temporale dell’evento detta timestamp e una sua descrizione
insieme al modulo/applicazione che ha segnalato l’evento.
Jun 4 00:05:41 ... testLog[1220]: Attenzione a questo evento!!!
Un limite importante delle tecniche di logging risiede nel fatto che la rilevazione di un fallimento dipende fortemente dal fatto che il modulo o l’applicazione tenga traccia o meno
del particolare evento che ha scatenato il fallimento stesso. In altri termini, non tutte le
possibili condizioni di errore vengono riportate nei log e questo può senza dubbio rendere
molto difficile stabilire tutte le cause principali dei vari eventi di fallimento osservati durante l’esecuzione di un sistema software. Un esempio molto importante di sistema di log
è sicuramente il demone UNIX syslogd: questo processo di background registra gli eventi
generati da diverse sorgenti locali quali possono essere il kernel, i principali componenti di
sistema (dischi, periferiche, memoria) ed inoltre i demoni e le applicazioni che sono state
4
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
configurate per comunicare con syslogd. Generalmente ad ogni evento registrato è associata una gravità (severity) differente scelta all’interno di una scala predefinita il cui massimo
corrisponde al valore Emergency mentre il minimo è rappresentato dal valore Debug. Gli
eventi sono composti dal timestamp, dall’host, dall’utente, dal nome del processo e dal suo
pid, inoltre è anche presente una descrizione dell’evento stesso. Il file di configurazione di
syslogd è in genere /etc/syslog.conf, dove sono specificate tutte le destinazioni degli eventi
registrati dal demone, in base al loro livello di gravità ed alla loro origine. Gli stessi principi li troviamo applicati ai sistemi operativi Microsoft Windows. Per tutti i sistemi successivi ad XP, il logger degli eventi è implementato mediante un servizio di sistema che
gira in background e che resta in attesa che i processi in esecuzione sul sistema locale o
remoto gli inviino report o eventi. Ogni evento è in seguito memorizzato in uno specifico
file di log. In tutto sono definiti tre file di log: il log di sicurezza (per le informazioni di sicurezza e controllo), il log si sistema (per gli eventi generati dai moduli del SO), e il log
dedicato alle applicazioni. Gli eventi sono composti da un tipo (info, errore, avviso), un
timestamp, la sorgente dell’evento stesso, la categoria, l’id, l’utente ed il computer.
In alcuni casi si può rendere necessario sviluppare sistemi di monitoraggio ad-hoc, infatti,
le tecniche di logging classico possono non essere disponibili per tutte le classi di sistema:
esempi di questo tipo possono essere la JVM e il sistema operativo Symbian. Inoltre queste tecniche, così come sono state definite, possono risultare inadeguate per alcune tipologie di analisi quali quelle condotte in tempo reale come ad esempio possono essere le analisi di sistemi di sicurezza o di controllo di apparati (centraline, macchinari, etc.). Proprio
in questo scenario si colloca il presente lavoro di tesi, ovvero ci si è proposti di lavorare ad
una infrastruttura di log molto più sofisticate di quelle attualmente utilizzate dai sistemi
software in circolazione. Un’infrastruttura che consenta la creazione, l’instradamento, la
memorizzazione e l’analisi dei log generati dalle varie componenti di un sistema, sia esso
monolitico, a componenti o addirittura distribuito. Nello specifico il presente lavoro di tesi
non tratta nel dettaglio la progettazione e l’architettura dell’infrastruttura di logging, chiamata Logbus-ng, ma si concentra particolarmente sulla progettazione e sulla realizzazione
5
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
di librerie e tool di analisi in grado di sfruttare le potenzialità della nuova piattaforma
nell’ambito della Field Failure Data Analysis, sulla quale, in seguito, si farà una breve dissertazione. Inoltre si porterà l’attenzione su di un reale caso di studio in cui si è adottato un
nuovo approccio alla FFDA mediante gli strumenti appena accennati, ovvero il web-server
Apache, nella sua versione 1.3.41, cercando di dimostrare non solo la effettiva efficacia
dell’infrastruttura realizzata, ma anche e soprattutto i reali benefici che si ottengono, grazie all’applicazione di alcune semplici regole durante l’analisi, nell’individuazione delle
varie tipologie di fallimento temporali come i crash, gli stalli e, in alcuni casi, di fallimento di valore. Infatti, lo scopo ultimo delle librerie e dei tool di analisi progettati è quello di
ottenere un log report già filtrato ed elaborato, che consenta all’analista o al sistemista di
localizzare e classificare con estrema facilità il fallimento di uno o più componenti (in caso di propagazione), e di prendere i dovuti provvedimenti per ripristinare o migliorare il
sistema in modo da aumentarne l’affidabilità.
6
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
1. Dependability di un sistema software
La dependability è una proprietà dei sistemi che comprende molti aspetti quali
l’affidabilità, la sicurezza, la disponibilità. La sua definizione è frutto di un lavoro
d’integrazione di concetti, esposti in [12], grazie al quale si è giunti ad una visione organica e complessiva di tutte le problematiche connesse al corretto funzionamento dei sistemi.
1.1 Definizione e attributi
Si può dire che ogni sistema interagisce, tramite la sua interfaccia di servizio, con degli
utenti, che possono essere sia umani sia altri sistemi. Le interazioni del sistema con gli
utenti sono caratterizzate dalla funzione che il sistema deve implementare, definita tramite
le sue specifiche funzionali, e dal servizio, che è il comportamento del sistema così com’è
percepito dall'utente. L'utente quindi non è onnisciente, ma ha una cognizione limitata del
reale comportamento del sistema e del suo stato. La dependability di un sistema informativo è la sua capacità di fornire un servizio sul quale si può fare affidamento in
Fig. 1.1 – Gli stati di un sistema.
maniera giustificata. Quando il servizio corrisponde alle specifiche funzionali, si dice corretto, mentre si definisce non corretto nel caso contrario. Nell'istante in cui si passa da
servizio corretto a servizio non corretto, si ha un fallimento del sistema. In seguito indicheremo questo evento anche con il termine inglese failure. Il periodo in cui il sistema fornisce un servizio non corretto si dice di outage. Il passaggio inverso al fallimento, da un ser7
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
vizio non corretto a un servizio corretto è detto ripristino o service restoration. La dependability ha delle minacce, cioè dei fenomeni che ostacolano l'erogazione di un servizio
corretto, degli attributi, che la definiscono con maggiore precisione da punti di vista differenti, e dei mezzi attraverso i quali viene raggiunta. Quando il sistema fallisce troppo spesso, i fallimenti hanno conseguenze troppo gravi o i periodi outage sono troppo lunghi, allora il sistema non è dependable. La dependability è quindi limitata dai fallimenti e da ciò
che li causa, in altre parole errori e guasti. Si definisce errore una parte dello stato del sistema che può causarne il fallimento. Con il termine guasto, o fault in inglese, si indica
invece ciò che si ritiene o si ipotizza sia la causa di un errore. Un guasto si dice attivo nel
momento in cui produce un errore, altrimenti si dice dormiente. I modi in cui un sistema
può fallire sono definiti failure modes e possono essere categorizzati sulla base di quattro
aspetti [12, 13] :
-
Dominio: l'erogazione di un servizio dal sistema può rappresentare un value failure, se
il valore del servizio erogato dal sistema non si conforma alla specifica, o un timing
failure, se invece è il tempo in cui tale servizio è fornito che non soddisfa la specifica.
-
Consistenza: nel caso in cui gli utenti di un servizio siano due o più, allora il fallimento del sistema può essere definito consistente se il servizio errato che viene fornito è
analogo per tutti gli utenti o inconsistente altrimenti.
-
Controllabilità: a volte i sistemi sono progettati per fallire seguendo modalità precise.
Un esempio tipico è dato dai sistemi Safety-critical fail-stop che, in caso di fallimento,
devono fermarsi senza fornire risultati errati all'esterno. I fallimenti che soddisfano
vincoli di questo tipo sono detti controlled failures, gli altri uncontrolled failures.
-
Conseguenze: i fallimenti possono essere suddivisi sulla base della gravità delle loro
conseguenze. Fallire nell'inviare un fotogramma durante una videoconfenza o nel controllare l'impianto di sicurezza di una centrale nucleare comporta rischi incomparabilmente diversi. Per questo motivo i fallimenti possono essere classificati in vari modi,
da minori a catastrofici. Nell'ambito dei sistemi Safety-critical la principale suddivisione che viene fatta è tra fallimenti benigni, che hanno conseguenze accettabili, e fallimenti catastrofici.
8
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Anche gli errori possono essere descritti classificandoli sulla base dei fallimenti che possono causare al componente che li contiene. Si può quindi parlare di errori timing e value,
di errori benigni e catastrofici, etc. Un errore si dice rilevato se la sua presenza è rilevata
dal sistema (per esempio tramite un messaggio di errore), altrimenti si dice latente. I guasti, ovvero le cause degli errori, possono essere classificati sulla base di sei aspetti:
-
Fase di creazione: se la progettazione o la realizzazione del sistema non è corretta, per
esempio quando non ci si accorge della presenza di bug software, si lasciano nel sistema, fin dall'inizio della sua vita operativa, dei guasti che sono definiti developmental faults. Invece i guasti che occorrono durante la vita operativa del sistema sono definiti operational faults;
-
Locazione rispetto al sistema: se i guasti sono localizzati dentro al sistema si parla di
internal faults, altrimenti di external faults;
-
Dominio: i guasti possono essere hardware faults o software faults;
-
Cause fenomenologiche: i guasti possono essere causati da comportamenti non corretti delle persone, ed in quel caso si parla di humanmade fault, oppure no, e allora si parla di natural faults;
-
Intento: comportamenti non corretti delle persone possono causare guasti colposi (accidental faults), o dolosi (deliberately malicious faults);
-
Persistenza: un guasto può rappresentare una causa permanente di generazione di errori (permanent faults) o generare un solo errore e scomparire (transient fault).
Questo permette di definire una tassonomia dei guasti, che possono essere categorizzati e
suddivisi in design faults, phisical faults e interaction faults. Si è detto che i guasti quando si attivano generano errori, e che quando gli errori raggiungono l'interfaccia del sistema, ne provocano il fallimento. Il processo di propagazione è quello attraverso cui gli errori raggiungono l'interfaccia del sistema. La catena guasto - errore - fallimento permette
di rappresentare il nesso causale, che adesso illustreremo, esistente tra i tre concetti nel
processo di propagazione. Un sistema consiste in un insieme di componenti che interagiscono, quindi lo stato del sistema è l'insieme degli stati dei suoi componenti. Lo stato di un
componente, come il componente A di figura 1.2, può essere corrotto dall'attivazione di un
9
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
guasto o interno (e precedentemente dormiente) o esterno. L'errore così generato può essere utilizzato, ovvero attivato, nel processo computazionale e, di conseguenza, può propagarsi internamente e determinare la creazione di altri errori all’interno del componente.
Fig. 1.2 – Il processo di propagazione degli errori.
Il fallimento di un componente avviene quando la propagazione di un errore al suo interno
raggiunge la sua interfaccia, provocando l'erogazione di un servizio errato. A sua volta, il
fallimento di un componente determina l'erogazione di un servizio errato ad altri componenti e quindi la propagazione esterna di un errore verso di essi. Nell'esempio riportato, il
componente A fallendo propaga esternamente un errore al componente B. Un errore che
corrompe lo stato di un componente a causa della ricezione di un servizio non corretto
viene definito input error, e appare come causato da un guasto esterno. L'input error così
determinato potrà a sua volta propagarsi all'interno del nuovo componente, ricorsivamente.
Propagandosi esternamente e internamente ai componenti gli errori possono infine raggiungere l'interfaccia del sistema, provocandone il fallimento. In generale quindi un errore
all'interno di un sistema può essere causato da:
1) Un guasto interno, in precedenza dormiente, che si è attivato;
2) Un guasto operazionale fisico, sia interno sia esterno;
3) Un guasto esterno, dovuto alla propagazione di un errore da un altro sistema tramite la
sua interfaccia.
In questo modo si è definito il nesso causale tra i guasti, gli errori e i fallimenti, detto catena guasto - errore - fallimento. Poiché il fallimento di un componente può rappresentare
10
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
un guasto esterno per un altro componente, la catena è ricorsiva.
Fig. 1.3 – La catena guasto - errore - fallimento.
Gli attributi della dependability permettono di darne una definizione molto più articolata e
complessa. In [12] sono definiti sei attributi fondamentali:
-
Availability: è la capacità di fornire un servizio corretto quando viene richiesto;
-
Reliability: è la capacità di fornire con continuità un servizio corretto;
-
Safety: è l'assenza, nell'erogazione di un servizio, di conseguenze catastrofiche sugli
utenti e/o sull'ambiente esterno;
-
Confidentiality: è l'assenza di accessi non autorizzati alle informazioni;
-
Integrity: è l'assenza di alterazioni non corrette allo stato del sistema;
-
Mantainability: è la predisposizione a ricevere manutenzioni e modifiche.
A seconda dell'uso che si vuole fare del sistema si può cercare di massimizzare alcune di
queste caratteristiche rispetto alle altre. Si dicono dependability requirements del sistema
gli obiettivi, definiti nei termini degli attributi illustrati, che un sistema deve raggiungere
in un dato ambiente supponendo che sia soggetto ad un insieme dato di guasti possibili.
Tali obiettivi sono indicati come soglie massime sulla frequenza dei fallimenti, sulle loro
conseguenze e, talvolta, sulla durata dei periodi di outage. Poiché questo lavoro verte sui
sistemi Safety-critical, rivolgeremo l'attenzione sull'attributo di safety. Nella sua definizione è implicita la necessità di definire quali sono i fallimenti catastrofici di un sistema,
che devono essere distinti dai fallimenti benigni (vedi figura 1.4). Nei sistemi Safetycritical in genere si definiscono fallimenti benigni quelli controllabili mentre sono definiti
maligni quelli non controllabili. Un esempio comune di fallimento controllabile richiesto è
quello fail-silent, in cui tutti i fallimenti devono essere dei crash (o halt) failures. In tal caso se il sistema non è in grado di fornire un output corretto, allora non deve fornire alcun
output e si deve fermare. Se così non accade, possono esserci conseguenze catastrofiche
11
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
dovute all'erogazione di un servizio non corretto. Questo tipo di comportamento è conflittuale con l'availability. A prescindere dal contesto applicativo, l'availability è sempre una
caratteristica richiesta a tutti i sistemi in quanto rappresenta la capacità di svolgere, quando
viene richiesto, le funzioni per cui il sistema è stato progettato e realizzato.
Fig. 1.4 – Categorizzazione dei fallimenti rispetto alla safety.
Rispetto a un sistema che si ferma volontariamente è più available un sistema che, anche
se sa di rischiare il fallimento a breve, continua a fornire il servizio finché può. Nei sistemi
Safety-critical però il rischio associato ai fallimenti catastrofici è inaccettabile, quindi si
preferisce sacrificare l'availability per incrementare la safety. Nei sistemi fail-silent questo
significa appunto fermare il sistema in tutte quelle situazioni in cui si ritiene di poter rischiare un fallimento non crash.
1.2 Tecniche di valutazione
La dependability dei sistemi si ottiene ricorrendo principalmente a quattro tecniche:
-
Fault prevention: per prevenire l'occorrenza o l'introduzione di guasti;
-
Fault tolerance: per fornire un servizio corretto in presenza di guasti attivi;
-
Fault removal: per ridurre il numero o la gravità dei guasti;
-
Fault forecasting: per stimare la presenza attuale, l'incidenza futura e le probabili
conseguenze dei guasti.
La fault prevention mira alla realizzazione di sistemi con il minor numero di guasti possibili, originati sia durante la fase di sviluppo che durante la loro vita operativa. Per ridurre
la presenza di design fault si utilizzano tecniche di controllo della qualità durante la progettazione e la realizzazione dell'hardware e del software, come programmazione struttu12
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
rata e information hiding per il software e rigide regole di progettazione per l'hardware.
Per prevenire guasti fisici operazionali si usano, per esempio, schermature contro le radiazioni, per i guasti di interazione si ricorre a corsi di formazione per gli utenti e ad una rigida manutenzione, mentre per i guasti dolosi ci si protegge con firewalls ed Intrusion Detection Systems. Anche se tramite la fault prevention si può ridurre l'incidenza dei guasti,
non è tuttavia possibile eliminare del tutto la possibilità di errori umani nella progettazione
o di eventi sfavorevoli durante la vita operativa del sistema. Per questo è necessario fare
ricorso alla fault tolerance.
La fault tolerance consente l'erogazione del servizio corretto anche in presenza di guasti
attivi. Si compone di due fasi consecutive: error detection e system recovery. L'error detection ha l'obiettivo di verificare la presenza di errori nel sistema e di emettere un messaggio di errore in caso di rilevamento. Un errore presente nel sistema ma non rilevato
viene definito un errore latente. L'error detection può essere concorrente, nel caso in cui la
verifica del sistema avvenga durante l'erogazione del servizio, oppure preventiva, se viene
effettuata mentre l'erogazione del servizio è sospesa. La system recovery rappresenta la seconda fase della fault tolerance ed include le operazioni che vengono effettuate, in seguito
al rilevamento di un errore, per preservare il corretto funzionamento del sistema. Da uno
stato in cui sono presenti uno o più errori, ed eventualmente guasti, si cerca di passare ad
uno stato senza gli errori rilevati e privo di guasti dormienti che possano essere attivati ancora. La system recovery consiste nell'error handling e nel fault handling. L'error handling elimina dallo stato del sistema gli errori rilevati. Può essere effettuata in tre modi:
-
compensation, usata quando la ridondanza dello stato permette di eliminare l'errore
correggendolo;
-
backward recovery, usata quando si ripristina uno stato del sistema (detto checkpoint) memorizzato precedentemente al rilevamento dell'errore;
-
forward recovery, usata quando si porta il sistema in un nuovo stato di default privo
degli errori rilevati.
13
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
La fault handling invece cerca di prevenire ulteriori attivazioni dei guasti che hanno provocato l'errore. E' composta di quattro passi:
-
Fault diagnosis: in cui si cerca di determinare e registrare la locazione ed il tipo del
guasto che ha generato l'errore;
-
Fault isolation: in cui si effettua l'esclusione logica o _sica del component guasto, che
verrà escluso dalla partecipazione all'erogazione del servizio;
-
System reconfiguration: in cui si attivano componenti di riserva o si redistribuisce il
carico di lavoro del componente isolato agli altri componenti non guasti;
-
System reinizialization: in cui si controlla, si aggiorna e si registra la nuova configurazione modificando le strutture dati del sistema.
In seguito alla procedura di fault handling si può effettuare la manutenzione correttiva, in
cui il componente guasto viene in genere rimosso e sostituito da un operatore. L'efficacia
delle tecniche di fault tolerance dipendono essenzialmente dall'error detection. Se fosse
possibile rilevare istantaneamente tutti gli errori che vengono generati in un sistema, e fossero prese le appropriate scelte di system recovery, allora nessuno di essi potrebbe propagarsi fino all'interfaccia del sistema e generare un fallimento. In teoria, quindi, sarebbe
meglio ricorrere quanto più possibile all'error detection. In realtà ciò è limitato da vari fattori. Innanzitutto il costo di realizzazione o di acquisto dei meccanismi. Poi l'overhead che
si ha sulle prestazioni per attendere che i controlli vengano effettuati. Infine la risoluzione
diagnostica del fault handling, che spesso è limitata e quindi rende inutile cercare di rilevare con precisione l'esatta locazione dell'errore. Abbiamo già parlato, in precedenza, della
necessità di alcuni sistemi di garantire un comportamento fail-silent, in cui se non si è in
grado di fornire un output corretto allora non si deve fornire alcun output e ci si deve fermare. Il soddisfacimento di questo dependability requirement è possibile solo se il sistema
è in grado di essere consapevole del rischio di fornire un output non corretto, e questo è
possibile solo grazie all'error detection. In generale quindi l'error detection è fondamentale
non solo per la tolleranza dei guasti ma anche per la realizzazione di quei sistemi, detti
fail-controlled systems, che devono garantire precise modalità di fallimento.
14
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
La fault removal consiste nell'eliminazione dei guasti del sistema e può avvenire sia durante il suo sviluppo che durante la sua vita operativa. Durante lo sviluppo avviene la verifica, il processo attraverso il quale si determina se il sistema implementa le sue specifiche.
Nella verifica in genere ci si limita a controllare se il sistema rispetta alcune proprietà. Se
così non è si cerca di capire qual è il guasto che impedisce il soddisfacimento delle specifiche, lo si corregge, e si ripete il controllo per vedere se la correzione ha a sua volta introdotto guasti. Tecniche di verifica possono essere statiche, e consistere in un'analisi statica
delle caratteristiche del sistema, o dinamiche, e richiedere l'esecuzione del sistema con degli input definiti. La validazione è complementare alla verifica e consiste nel controllare se
le specifiche sono adeguate. Di particolare importanza nei sistemi dependable è la verifica
dei meccanismi di fault tolerance che permettono di raggiungere i dependability requirements. Questo può essere fatto tramite verifiche statiche formali o dinamiche. In quest'ultimo caso si inseriscono guasti ed errori nel sistema per studiarne la capacità di reazione,
usando tecniche dette di fault injection. Durante la vita operativa del sistema la rimozione
dei guasti è chiamata manutenzione e può essere correttiva, quando il guasto viene rimosso dopo che ha prodotto uno o più errori rilevati, oppure preventiva, quando si cercano di
rimuovere guasti presunti prima che si manifestino producendo errori.
Il fault forecasting consiste nel valutare il comportamento futuro del sistema considerando
la possibile occorrenza e/o attivazione dei guasti. Si possono fare valutazioni qualitative,
che cercano di determinare i modi in cui un sistema può fallire e le combinazioni di guasti
che possono portare al fallimento, e quantitative, che cercano di valutare probablisticamente fino a che punto il sistema soddisfa gli attributi di dependability. L'uso di metodi
quantitativi richiede di esprimere gli attributi della dependability illustrati in precedenza
sotto forma di misure. Le definizioni che ne risultano sono le seguenti [14]:
-
Reliability R(t): è la probabilità che il sistema non fallisca mai nell'intervallo [0; t].
-
Time To Failure: è il tempo che intercorre dall'ultimo service restoration (avvio del
sistema o ripristino dopo un fallimento) al fallimento successivo. La sua media è detta
Mean Time To Failure (MTTF).
15
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
-
Mantainability: è il tempo in cui il sistema sta in outage, quindi dal suo fallimento alla service restoration successiva. La sua media è detta Mean Time to Repair (MTTR).
-
Time Between Failures: è il tempo tra un fallimento e il fallimento successivo. La sua
media è detta Mean Time Between Failures e si calcola con MTBF = MTTF +MTTR.
-
Availability A(t): vale 0 se al tempo t il sistema è in outage, ovvero è fallito, 1 altrimenti. Il valore medio E[A(t)] è pari alla probabilità che il sistema fornisca un servizio
corretto al tempo t. Si definisce invece con A(0; t) la frazione di tempo in cui il sistema
è in grado di fornire un servizio corretto durante l'intervallo [0; t]. Se si considera il
comportamento medio del sistema durante tutta la sua vita operativa, quindi nell'intervallo [0; t] con t che tende a 1, si ha che E[A(0; t)] = MTTF=MTBF.
-
Safety S(t): si definisce come la reliability, considerando però come fallimenti solo
quelli catastrofici. Quindi è la probabilità che il sistema non subisca fallimenti catastrofici nell'intervallo [0; t].
Un'altra misura utilizzata nel fault forecasting è la performability, che permette di stimare
le performance di un sistema prendendo in considerazione l'occorrenza di guasti [18]. La
performability è misurata assegnando dei guadagni (reward), positivi o negativi, ai diversi
stati di operatività del sistema. In questo modo si possono stimare anche le performance di
sistemi che in caso di guasti, per esempio, forniscono un livello di servizio ridotto (service
degradation). Normalmente quest’aspetto non viene catturato dalle normali tecniche di
stima delle performance, perché suppongono che il sistema sia pienamente operativo. Le
valutazioni quantitative vengono effettuate utilizzando varie tecniche che si basano su modelli probabilistici del comportamento futuro del sistema e/o su misure effettuate sul sistema. La scelta della tecnica più appropriata deve prendere in considerazione vari aspetti:
il momento nel ciclo di sviluppo in cui viene utilizzata, l'accuratezza richiesta sui suoi risultati, il tempo necessario ad ottenerli, la possibilità di ricavare misure che permettano di
comparare diverse alternative, i costi richiesti dal suo uso, la disponibilità di strumenti che
aiutino ad ottenere i risultati, la scalabilità a sistemi di grandi dimensioni. Le tecniche più
semplici sono quelle basate sul calcolo combinatorio, come i fault trees ed i reliability
block diagrams: permettono di ottenere previsioni facilmente e sono molto scalabili, ma
16
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
risultano inadeguate a valutare sistemi caratterizzati da interazioni complesse tra i loro
componenti. Inoltre l'accuratezza dei risultati ottenuti è relativamente bassa. Ciò nonostante sono tecniche ampiamente utilizzate. Altre tecniche sono basate su modelli analitici,
come per esempio le catene di Markov e le reti di Petri stocastiche. Tali tecniche permettono di modellizzare interazioni complesse tra i componenti del sistema e di ottenere livelli di accuratezza dei risultati migliori di quelli dei modelli combinatori. A seconda del livello di accuratezza desiderato, della fase di sviluppo del sistema in cui vengono utilizzate
e della precisione delle misure disponibili come dati di input, si possono utilizzare modelli
più o meno complessi. Limitazioni all'uso di questi metodi sono date dalla dimensione dello spazio degli stati del modello, che può facilmente crescere in modo eccessivo limitando
la scalabilità, dalle distribuzioni dei tempi con cui possono avvenire gli eventi, che in genere possono essere solamente esponenziali, e dalla stiffness del problema, ovvero dalla
grande differenza che può esserci tra i rate delle transizioni. Esistono poi tecniche simulative, che non risolvono analiticamente il problema, ma ne simulano l'evoluzione ottenendo
campioni delle misure che si vogliono ricavare. Tali tecniche non hanno i problemi dei
metodi analitici, perché non hanno bisogno di utilizzare uno spazio degli stati. Sono quindi
più scalabili, non hanno limitazioni sul tipo di distribuzioni da utilizzare e si comportano
bene anche con problemi stiff. I valori che si ottengono però, essendo campionari, hanno
un margine di confidenza che, per essere ridotto, richiede spesso un numero molto grande
di simulazioni e quindi un costo computazionale molto alto, al contrario dei metodi analitici che permettono di aumentare l'accuratezza dei risultati ottenuti con costi minimi.
Quando poi si devono modellizzare eventi rari possono essere necessari moltissimi run di
simulazione per campionarli con margini di confidenza accettabili. Effettuare misure sul
sistema può essere molto utile per fornire valori di input ai modelli, aumentando l'accuratezza dei risultati da essi ottenuti. Ciò nonostante, per effettuare le misure è necessario
avere a disposizione un prototipo del sistema e strumenti specifici; il procedimento può
essere lungo, costoso e poco scalabile; inoltre i risultati ottenuti, sebbene molto accurati,
non permettono in genere di comparare sistemi diversi senza ricorrere all'uso di modelli.
17
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
1.3 La Failure Data Analisys
L’efficacia delle tecniche di dependability enhancement descritte finora presuppone
un’adeguata conoscenza del comportamento del sistema: la caratterizzazione statistica dei
failure modes, accanto alla formulazione di modelli realistici, incrementa la predicibilità
del sistema stesso facilitando l’applicazione di mirate azioni preventive e/o correttive. La
failure data analysis, attraverso l’esame di dati relativi ai fallimenti, si configura quale
strumento per la valutazione della dependability di un sistema fornendo informazioni utili
sia alla costruzione di un modello di riferimento sia alla progettazione di nuovi sistemi.
R.Iyer et al. in [R.K00] evidenziano come ai fini dell’analisi della dependability di un sistema assuma rilevante importanza il binomio costituito dalla fase del ciclo di vita in cui si
è scelto di operare e dal particolare strumento di valutazione utilizzato. In fase di progettazione, design phase, lo studio dell’affidabilità del sistema può essere condotto utilizzando
software di simulazione: il sistema viene volontariamente sottoposto a situazioni di errore
(simulated fault-injection) con il duplice obiettivo di individuare eventuali dependability
bottlenecks e di stimare la coverage dei meccanismi di fault tolerance. I feedback derivanti
dallo studio delle reazioni del sistema risultano particolarmente utili ai system designers in
un’ottica di riprogettazione cost-effective. A progettazione ultimata, viene generalmente rilasciata una versione prototipale del sistema affinché esso possa essere sottoposto alle dovute attività di testing. In questa fase il sistema viene sollecitato con profili di carico ad
hoc (controlled workloads) per poter studiare le sue reazioni a faults reali (physical faultinjection), le sue capacità di recupero in seguito a situazioni di errore (recovery capabilities) e l’efficacia delle tecniche di detection (detection coverage). Uno studio di questo tipo fornisce informazioni circa il failure process del sistema (cioè la sequenza di stati che
esso attraversa dal momento in cui si verifica l’errore fino all’eventuale recovery) ma non
consente di valutare misure di dependability quali MTTF, MTTR dal momento che saranno stati considerati esclusivamente fault artificiali. E’ importante sottolineare che, a differenza di quanto accade in fase di progettazione, in fase prototipale è possibile iniettare
guasti anche a livello software. In fase di normale utilizzo del sistema, e dunque quando
esso è ormai completamente operativo, è possibile valutarne la dependability mediante
18
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
un’analisi sul campo ovvero analizzandone il comportamento in risposta a profili di traffico reali: un approccio di questo tipo, noto in letteratura come field failure data analysis,
consente di ottenere informazioni relative esclusivamente agli errori rilevati durante il periodo di osservazione e la caratterizzazione statistica che se ne può dedurre pecca in termini di generalità vista l’infinità varietà di situazioni reali in cui il sistema può trovarsi. Ad
ogni modo, di seguito, andremo ad illustrare i dettagli di un simile approccio.
Field Failure Data Analysis
La Field Failure Data Analysis fornisce informazioni che consentono di comprendere al
meglio gli effetti di eventuali errori sul comportamento di un sistema complesso. Questa
tipologia di analisi è molto utile per acquisire informazioni sul sistema sotto osservazione,
e per la creazione e la validazione di modelli analitici mirati a migliorare il processo di
sviluppo. Tutti i dati raccolti sono utili per chiarire e caratterizzare il sistema in fase di
studio. L’analisi qualitativa dei fallimenti e degli errori riscontrati fornisce un importante
feedback per lo sviluppo e può senza dubbio contribuire a migliorare l’intero processo
produttivo. D’altra parte molti studi convergono nell’affermare che non esiste tecnica migliore per comprendere le caratteristiche di affidabilità dei sistemi software di quella basata sull’analisi di misure raccolte direttamente sul campo[2]. La Field Failure Data Analisys
si può applicare sostanzialmente durante la fase di esercizio del sistema, e, come già accennato prima, essa mira soprattutto a misurare l’affidabilità del sistema considerato mentre esso è sottoposto ad un carico di lavoro quanto più realistico possibile. Ovviamente è
necessario monitorare il sistema sotto analisi e tenere traccia di tutti i fallimenti osservati
durante il tutto il suo normale funzionamento, ovvero non si è interessati ad introdurre in
maniera forzata un comportamento anomalo del sistema, ma solo ad osservarlo mentre è in
esercizio. L’obiettivo principale di una campagna FFDA è quello di ottenere una caratterizzazione quanto più dettagliata possibile della affidabilità del sistema sotto analisi, e, nel
dettaglio, si è sostanzialmente interessati a:
-
Identificare le classi di errori/fallimenti che si manifestano durante l’esecuzione del
software cercando di comprenderne gravità e correlazione.
19
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
-
Analizzare le distribuzioni statistiche dei tempi di guasto e ripristino di un sistema.
-
Ottenere le correlazioni tra i fallimenti e i carichi di lavoro del sistema.
-
Identificare le principali cause dei malfunzionamenti ed isolare i colli di bottiglia per
l’affidabilità dell’intero sistema.
-
Ottenere risultati che abbiano validità generale, che sono cruciali per guidare la ricerca
e il processo di sviluppo del software.
Seppure gli studi basati sulla FFDA, siano molto utili per valutare i sistemi reali, essi sono
abbastanza limitati nell’individuazione di alcune categorie di malfunzionamenti, inoltre le
particolari condizioni sotto le quali il sistema è osservato possono variare notevolmente da
una installazione ad un'altra causando quindi forti dubbi sulla effettiva validità statistica
dei risultati ottenuti. Va detto inoltre che le analisi di questo tipo sono spesso poco utili a
migliorare la versione corrente del software, infatti, i maggiori benefici vengono tratti soprattutto dalle release successive a quella analizzata. Infine è importante tenere presente
che la FFDA può, in generale, richiedere tempi molto lunghi di studio del sistema sotto
osservazione, specialmente nel caso in cui il sistema stesso sia particolarmente robusto o
comunque nel caso i fallimenti fossero molto rari.
Fig. 1.5 – La metodologia FFDA
Come mostrato in figura, la metodologia FFDA, consiste solitamente di tre fasi consecutive, ovvero: raccolta e collezione di log provenienti dal sistema monitorato, scrematura ed
elaborazione dei dati raccolti in modo da ottenere le informazioni utili per l’analisi, ed infine lo studio dei dati ottenuti dalle prime due fasi in modo da derivare risultati e modelli
capaci di caratterizzare il sistema nella sua interezza. Con particolare riferimento alla seconda fase possiamo affermare che il filtraggio e le manipolazioni dei dati collezionati
consistono in una analisi di correttezza, consistenza e completezza.
20
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Quindi l’obiettivo è in pratica quello di eliminare i dati non validi e di unire quelli ridondanti o equivalenti, infatti, tale situazione si presenta spesso quando si fa un largo utilizzo
di eventi di log, che, per loro natura, contengono molte informazioni non collegate necessariamente ai fallimenti del sistema e, che, spesso, si presentano in forma duplicata o comunque ridondante e ravvicinata nel tempo quando un particolare evento viene scatenato
all’interno del sistema software. Evidentemente questi dati devono essere uniti in un'unica
segnalazione relativa all’evento osservato. Filtrare i dati è molto utile per ridurre il volume
di informazioni da memorizzare e per concentrare l’attenzione verso un set di dati più significativo, in modo da semplificare notevolmente il processo di analisi. Le tecniche di filtraggio più utili possono essere sicuramente quelle di Blacklist e Whitelist: la prima consiste nello scartare tutti i messaggi che contengono almeno una parola che è contenuta nella
Blacklist stessa, mentre la seconda consiste nel considerare solo i messaggi che contengono almeno una parola presente nella Whitelist. Le tecniche di Coalescenza possono essere
separate in temporali, spaziali e basate sui contenuti. La coalescenza temporale, conosciuta
in letteratura come tupling[3], si basa su di un’euristica temporale, ovvero, si ci basa
sull’osservazione che, spesso, due o più eventi di fallimento sono rilevati insieme a causa
di una stessa causa. Ovvero è lecito pensare che gli effetti si un singolo fault possono propagarsi all’interno del sistema causando la rilevazione di più eventi di fallimento, o comunque è possibile anche che fault possa essere persistente o possa ripetersi nel tempo.
Fig. 1.6 – Rilevazione di eventi di fallimento multipli
La figura riportata sopra mostra proprio il caso in cui vengono rilevati più eventi di fallimento a causa dello stesso fault nel sistema. Alcuni di questi errori vengono catturati dal
sistema, tuttavia non c’è nessuna garanzia che essi vengano realmente riportati all’interno
del file di Log generati. Durante la fase di analisi, il primo step mira a catalogare tutti i fallimenti osservati sulla base della loro natura e/o dislocazione. In aggiunta è importante de21
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
rivare statistiche descrittive a partire dai dati a disposizione con particolare riferimento ai
fallimenti, alla loro gravità, al tempo necessario al ripristino, all’impatto del carico di lavoro sul comportamento del sistema, etc.
Le statistiche più diffuse in questo tipo di analisi includono solitamente la frequenza, la
percentuale e la distribuzione di probabilità: tali statistiche sono in genere utilizzate per
quantificare l’affidabilità, l’accessibilità e la sostenibilità del sistema osservato. La sostenibilità (maintainability) di un sistema è spesso valutata come in base alla semplicità con
cui il sistema stesso può essere ripristinato a seguito dell’occorrenza di un fallimento. Un
tipico indicatore della maintainability del sistema è il Tempo medio di ripristino (MTTR),
in particolare il tempo di recovery (TTR) può essere valutato in base al tempo necessario a
completare un intero ripristino del sistema.
Nella sua fase conclusiva, l’attività di analisi spesso conduce allo sviluppo di modelli per
la descrizione dell’affidabilità del sistema. In letteratura, i modelli più utilizzati sono le
macchine a stati, gli alberi di fault, la catene di Markov e le reti di Petri. La comprensione
piena dei dati ottenuti consente quindi di definire al meglio questi modelli permettendo di
popolare i parametri di questi ultimi in base alle considerazioni sulla frequenza dei fallimenti e dei ripristini del sistema.
22
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
2. Formati e framework di logging
La possibilità di utilizzare un framework software per la raccolta e la successiva distribuzione ai client interessati dei messaggi di logging è un’esigenza molto sentita in vari ambiti dello sviluppo di sistemi più o meno complessi. In circolazione sono disponibili, infatti,
un numero non esiguo di formati e piattaforme più o meno complete che sono orientate a
questo scopo tra le quali è giusto citare Syslog, Apache Log4xx e SecureLog. E’ evidente
che non sono possibili soluzioni completamente Cross-Platform in quanto, soprattutto in
fase di generazione dei messaggi log, le interfacce sono strettamente legate all’ambiente e
al linguaggio in cui il software è stato concepito e scritto. Questo capitolo si propone di
mostrare una panoramica quanto più completa possibile sulle piattaforme e sui framework
già presenti, descrivendone utilizzi e limitazioni, per arrivare a definire una nuova soluzione architetturale volta a massimizzare i vantaggi legati alle tecniche di logging classico
e contemporaneamente a minimizzare sia l’impatto sul codice sorgente sia le difficoltà legate alle analisi degli eventi osservati durante l’esercizio di un qualsiasi sistema software.
2.1 Panoramica generale
Una piattaforma dedicata al logging deve possedere almeno due funzionalità basilari: deve
consentire allo sviluppatore di creare messaggi con invocazioni non complesse e deve
consentire la memorizzazione permanente degli stessi su disco seguendo un formato prestabilito. La scelta del formato delle informazioni è in genere un problema molto sentito in
ogni ambito, ma in questo particolare ambito è ancora più cruciale in quanto, di fatto, non
esiste nessuno standard ufficiale, e in circolazione, è possibile trovare una molteplicità di
formati di log, tutti molto eterogenei tra loro. Probabilmente questa diversità è dovuta al
23
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
fatto che la gestione dei log è sempre stata fatta internamente ai team di sviluppo, senza
doversi mai preoccupare di accordarsi su un particolare standard o comunque senza mai
seguire una particolare convenzione, spesso, infatti, capita che i formati di log differiscano
sensibilmente anche tra varie sezioni dello stesso software, in quanto ad oggi il processo di
logging è estremamente legato alla soggettività del singolo sviluppatore. Il bisogno di uno
standard è molto sentito, non solo allo scopo di avere un unico formato di rappresentazione dei dati (ASCII, Unicode, etc.), ma anche al fine di avere una sintassi ben definita in
grado di esplicitare semplicemente il tipo ed il numero di informazioni a decorazione del
messaggio di log stesso, tra cui il timestamp e il componente sorgente. Studiando attentamente i formati esistenti, il risultato più lampante è stato quello di constatare che le piattaforme di logging Apache e il server HTTP Apache (che usa una piattaforma di logging adhoc) usano un formato discrezionale all’amministratore, che se pur ha il vantaggio di essere molto flessibile ed abbracciare qualsiasi esigenza di parsing automatico da parte degli
amministratori (si pensi ad un log che registri solo i referenti delle transazioni HTTP, un
altro gli IP sorgente, ecc.), non presenta nessun riscontro all’interno di altre piattaforme
software di largo utilizzo. Tuttavia, seppure non dichiarato come standard assoluto esiste
un Request For Comments dello IETF che descrive un formato di logging standardizzato
dall’authority per gli standard di Internet: la RFC 5424[4], che soppianta la RFC 3164[5].
Esso definisce in maniera esaustiva un formato di logging a misura di server in ambiente
UNIX chiamato Syslog, anche se, pur essendo direttamente derivato da [5], è in definitiva
incompatibile con esso. Inoltre si è riscontrato che le piattaforme di logging fornite da
Apache dispongono del supporto nativo a Syslog, pertanto è ragionevole pensare che tale
formato sia di gran lunga quello più indicato ad essere considerato come standard di riferimento per una eventuale nuova piattaforma di logging.
Lo standard Syslog
Analizziamo ora le caratteristiche del formato Syslog[4] ed evidenziamone le differenze
con[5]. Per prima cosa, il documento 3164, più che definire uno standard a priori del suo
utilizzo (rimanendo le RFC degli standard de facto), è redatto sotto forma di uno studio dei
24
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
formati di log comunemente usati in ambiente UNIX e Berkeley BSD, cercando di porre
un freno al caos dei formati definendone uno di fatto derivato da quelli in uso. Nel testo si
possono notare infatti espressioni non imperative come “si è osservato che”, riferite alle
convenzioni sui caratteri separatori. Lo standard 5424 è invece definito in maniera imperativa mediante la notazione ABNF[6], e particolare cura è posta alla specifica delle codifiche binarie utilizzate. La notazione ABNF permette infatti di definire delle sintassi in maniera non ambigua, e pertanto un parser per questo formato può essere realizzato in maniera più semplice non dovendo considerare condizioni di ambiguità. Tuttavia va sottolineato che molte piattaforme cosiddette legacy non sono compatibili con tale formato ma
con quello precedente: ad esempio Linux e MacOS fanno largo utilizzo dello standard
3164, e, la necessità di raccoglierli al fine di garantire una migliore diagnosi dei fallimenti
è molto sentita al fine di avere maggiore completezza durante la fase di analisi.
Esaminiamo più in dettaglio, senza soffermarci sulla sintassi, la struttura e il contenuto informativo di un messaggio Syslog[4]. Esso è contraddistinto da una severity, ossia un indice numerico da 0 a 7 del suo grado di importanza durante le analisi. Esistono otto livelli
canonici, dal debug fino alle condizioni di emergenza passando per avvisi ed errori; assieme alla severità è memorizzata una facility. Di per sé, la facility non rivela grandi contenuti informativi, o comunque può essere usata per raggruppare i messaggi a seconda del
sottosistema da cui provengono senza esaminare gli altri campi. Essa ha comunque le sue
origini nel formato Syslog BSD[5] per formare, assieme al valore di severità, un indice di
priorità del messaggio. Il campo facility è infatti la codifica numerica di un elenco ben
noto, che al valore più basso ha il kernel del sistema operativo, mentre all’aumentare del
valore si passa agli dei programmi utente. Il valore di priorità si ottiene moltiplicando per
8 (i livelli di severità, appunto) la facility e ad essa sommando la severità del messaggio.
Questo comporta che i messaggi di una facility (ne esistono 24) minore hanno sempre
maggiore priorità di quelli di una facility maggiore, cioè che ad esempio in un cluster di
smistamento posta un errore critico del server SMTP abbia minore priorità di un messaggio informativo del kernel. E’ in generale lecito considerare la facility solo come un valore
25
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
di raggruppamento dei messaggi, per quanto il gruppo sia definito in [4] come broad category e pertanto soggetto a collisioni da parte di vari software operanti nella stessa categoria applicativa (ad esempio Postfix e MS Exchange). Il messaggio Syslog contiene poi una
marcatura temporale, indispensabile a disporre i messaggi di log sulla linea temporale ed
effettuare la coalescenza temporale, ma soggetta ai ben noti problemi di sincronizzazione
distribuita o ad errori quale l’impostazione ad una data molto antica per via della mancata
impostazione del clock di sistema prima dell’avvio dell’infrastruttura di logging. La sintassi del timestamp in Syslog 2009 è inoltre rigidamente definita e riferibile sempre
all’orario UTC, contenendo un offset di fuso orario, mentre quella in Syslog BSD non
contiene l’anno (utile dunque solo nelle analisi in tempo reale e/o a breve termine) ed è locale al nodo che l’ha generata, di cui bisogna, dunque, conoscere il fuso orario. Altri campi sono il nome host della macchina che ha originariamente generato il messaggio, e che
dunque un relay non deve alterare se non per una valida ragione, il nome dell’applicazione
ed il suo pid, l’id del messaggio ed il messaggio testuale. Tutti questi campi sono opzionali
ma indispensabili per una corretta analisi, seppure, in generale, non sufficienti a caratterizzare in maniera del tutto completa il comportamento nel dettaglio di un sistema in quanto
mancanti di tutte le informazioni ricavabili invece dal runtime. A questi campi Syslog
2009 aggiunge una grande novità: i dati strutturati, ovvero una collezione di coppie
chiave/valore, ordinate esse stesse per una chiave con elevati requisiti di unicità in Internet, contenenti qualsiasi tipo di informazione strutturata e leggibile dalla macchina in automatico. La genialità, e al contempo la difficoltà di quest’approccio, è la possibilità di
garantire la semantica dei valori contenuti nei dati strutturati per mezzo di un meccanismo
di controllo dell’unicità delle chiavi facente uso degli SMI Enterprise ID[7] assegnati dallo
IANA[8]. La regola completa è la seguente: i nomi a visibilità globale sono registrati presso lo IANA, e nel documento IETF[4] sono definiti i primi nomi già registrati e la loro
semantica: tali nomi non conterranno mai il carattere chiocciola “@” perché riservato alla
seconda categoria: chi non volesse infatti incorrere nelle lungaggini burocratiche per registrare e standardizzare un SD-ID, può adottare un identificativo privato, a patto di aver registrato presso lo IANA uno SMI Enterprise ID. L’Università degli Studi di Napoli Fede26
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
rico II dispone dell’ID 8289, e il meccanismo risulta di fatto molto simile a quello già noto
dei namespace XML. Un SD-ID privato assume la forma di “id@ENTERPRISE”, dove id
è a discrezione dell’implementatore e ENTERPRISE è l’Enterprise ID univoco IANA. Bisogna tenere sempre presente però che i dati strutturati non sono presenti nel formato
Syslog BSD e, pertanto, si deve prestare attenzione al fatto che la conversione di un messaggio 5424 verso tale formato porterebbe automaticamente alla perdita irreversibile di tali
informazioni con conseguente ripercussione sulla grana delle analisi. Tipici esempi di
messaggi di log in formato Syslog BSD possono essere:
-
<0>1990 Oct 22 10:52:01 TZ-6 myhost.com 10.1.2.3 sched[0]: some text!
-
<34>Oct 11 22:14:15 mymachine su: ’su root’ failed for lonvick on /dev/pts/8
-
<13>Feb 5 17:32:18 10.0.0.99 Use the BFG!
Invece esempi tipici di messaggi nel format più recente di Syslog, ovvero quello previsto
dall’RFC5424 sono i seguenti:
-
<165>1 2003-08-24T05:14:15.000003-07:00 10.0.0.1 myproc 8710 - - %% some text!
-
<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] some text!
Si evincono subito le differenze già descritte in precedenza, tra cui la maggiore precisione
del timestamp e la notevole corposità delle structured data che portano con loro moltissime utili informazioni di contorno a discapito però di una dimensione sempre crescente del
pacchetto di log. Per concludere la trattazione di Syslog è importante dare anche un cenno
sui tipi di trasporto utilizzabili in rete per i pacchetti di Log, dettaglio non irrilevante se si
sta analizzando un sistema distribuito su più nodi in cui sono attivi diversi componenti in
grado di poter generare eventi di log e quindi in grado di fornire preziose informazioni in
caso di fallimento totale e/o parziale. Lo standard prevede di fatto sia il classico meccanismo non reliable basato su UDP (RFC 5426) sia un meccanismo più complesso ma molto
affidabile basato su TLS e quindi su TCP (RFC 3195). Per quanto riguarda il primo meccanismo il porto di default stabilito dallo standard è il 514, inoltre ogni datagramma UDP
deve contenere solo il messaggio syslog, che a sua volta potrà o meno essere completo, ma
in ogni caso formattato secondo lo standard 5424, tutte le eventuali informazioni aggiunti27
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
ve non devono essere presenti nel datagramma. Come già detto, il trasporto su UDP non è
affidabile, quindi alcuni pacchetti contenenti messaggi syslog possono perdersi durante il
trasferimento senza alcuna notifica: è evidente che possono esserci notevoli conseguenze a
causa di questa perdita specie per quello che concerne la sicurezza in quanto gli amministratori possono restare del tutto ignari di un problema legato ad uno o più componenti. Va
inoltre detto, per completezza, che esiste la possibilità che alcuni pacchetti vengano intercettati e scartati da un attacker al fine di nascondere le tracce di una o più operazioni non
autorizzate.
L’utilizzo del trasporto TCP garantisce quantomeno che non vi siano perdite incontrollate
di pacchetti a causa di congestioni della rete, ovviamente a scapito di una maggiore lentezza e quindi di un impatto maggiore sulle prestazioni del codice sorgente. La sicurezza offerta dallo standard 3195 è comunque sufficientemente elevata in quanto basata su autenticazione ed autorizzazione mediante certificati, in modo da rendere ragionevolmente difficile eseguire un attacco simile a quello descritto nel caso del trasporto UDP. Va comunque
detto che in generale il trasporto mediante TLS fornisce una sicurezza sulla comunicazione e non sull’integrità degli oggetti, ovvero il messaggio nella sua interezza può ritenersi
protetto, tuttavia un dispositivo compromesso può sempre generare indiscriminatamente
messaggi non corretti, inoltre un relay o un collector possono modificare, inserire e cancellare messaggi senza poter essere in nessun modo controllabili. Per ovviare a queste situazioni è evidente che avere un canale sicuro come TLS non basta, ma si rende necessaria
l’adozione di tecniche più sofisticate di detection che vanno al di là dello standard stesso.
Le librerie Apache Log4J e Apache Log4Net
Passiamo ora ad una analisi delle API di logging più diffuse tra gli sviluppatori, ovvero le
API fornite da Apache: Log4J[10] e Log4Net[11]. Log4J è una libreria Java sviluppata
dalla Apache Software Foundation che permette di mettere a punto un ottimo sistema di
logging per tenere sotto controllo il comportamento di una applicazione, sia in fase di sviluppo che in fase di test e messa in opera del prodotto finale. L'ultima versione stabile di28
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
sponibile ad oggi è la 1.2.15. Il modo migliore per configurare la libreria, ed utilizzarla in
un'applicazione, è scrivere un file di properties. Il file può anche essere scritto in formato
xml la cui struttura tipica risulta essere la seguente:
#LOGGER
log4j.rootCategory=DEBUG,APPENDER_OUT,APPENDER_FILE
#APPENDER_OUT
log4j.appender.APPENDER_OUT=org.apache.log4j.ConsoleAppender
log4j.appender.APPENDER_OUT.layout=org.apache.log4j.PatternLayout
#APPENDER_FILE
log4j.appender.APPENDER_FILE=org.apache.log4j.RollingFileAppender
log4j.appender.APPENDER_FILE.File=mioLog.log
log4j.appender.APPENDER_FILE.MaxFileSize=100KB
log4j.appender.APPENDER_FILE.MaxBackupIndex=1
log4j.appender.APPENDER_FILE.layout=org.apache.log4j.PatternLayout
Questa configurazione di esempio permette di scrivere i log sia sulla console che su di un
file di testo. Il file di configurazione è costituito da due componenti principali, ovvero il
Logger e l’Appender .Ciascun Logger viene associato ad un livello di log. I livelli disponibili, in ordine gerarchico, sono i seguenti: DEBUG, INFO, WARN, ERROR, FATAL.
Nell'esempio riportato il Logger viene impostato con livello DEBUG e gli vengono associati due Appender: APPENDER_OUT e APPENDER_FILE. Ciascun Appender definisce
un indirizzamento del flusso. Log4J mette a disposizione diversi Appender. I più utilizzati
sono i seguenti:
-
ConsoleAppender che permette di scrivere sulla console dell'applicazione;
-
FileAppender che permette di scrivere su file;
-
SocketAppender che permette di loggare utilizzando il protocollo TCP/IP;
-
JMSAppender che permette di scrivere su una coda JMS;
-
SMTPAppender che permette di inviare mail con i protocolli SMTP e JavaMail;
29
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
-
RollingFileAppender che permette di scrivere su un file di testo definendone la lunghezza massima. Quando la lunghezza massima è raggiunta, il file è rinominato aggiungendo un numero progressivo al nome del file;
Ciascun Appender naturalmente ha bisogno di alcuni parametri di configurazione specifici. Ad esempio, il FileAppender ha bisogno della directory e del nome del file di log sul
quale scrivere, mentre l'Appender SMTP ha bisogno dell'indirizzo del server SMTP.
Nell'esempio il LOGGER_OUT è di tipo Console mentre il LOGGER_OUT è di tipo RollingFile. A ciascun Appender è possibile associare un Layout mediante il quale è possibile
specificare il modo in cui le informazioni devono essere formattate. Log4J mette a disposizione diverse tipologie di Layout predefinite. Le principali sono le seguenti:
-
SimpleLayout che produce stringhe di testo semplice;
-
PatternLayout che produce stringhe di testo formattate secondo un pattern definito
nel file di configurazione;
-
HTMLLayout che produce un layout in formato HTML;
-
XMLLayout che produce un layout in formato XML.
Se il layout non viene specificato, log4J utilizza il SimpleLayout. Ora che abbiamo visto il
file di configurazione, possiamo vedere con un semplicissimo esempio com’è possibile
utilizzare Log4J in una semplice applicazione.
public class MyLog4J {
public static void main(String[] args) {
PropertyConfigurator.configure("myLog.properties");
Logger log = Logger.getLogger(MyLog4J.class);
log.debug("Test Livello DEBUG");
log.info("Test Livello INFO");
}
}
Mediante il metodo statico configure, della classe PropertyConfigurator, viene caricato il
file di configurazione. Quest'operazione deve essere fatta solo una volta all'avvio dell'applicazione e non è necessaria qualora il file viene chiamato con il nome di default
30
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
log4j.properties, poiché Log4J si occupa automaticamente di invocare il metodo. Con il
metodo statico getLogger della classe Logger otteniamo un'istanza della classe mediante la
quale possiamo invocare uno dei metodi disponibili corrispondenti ai diversi livelli di log
predefiniti. In entrambi i casi Log4J utilizza il pattern Singleton per cui, se in vari punti
dell’applicazione si invoca un Logger con lo stesso nome, si otterrà sempre la stessa istanza. Ad esempio scrivendo nel codice il seguente frammento:
Logger log1 = Logger.getLogger("myLog");
Logger log2 = Logger.getLogger("myLog");
Si ottengono due variabili che puntano alla stessa istanza. Se eseguissimo l'esempio, noteremmo che sia sulla console, sia sul file di log, verrebbero stampate le 5 voci perché il livello definito nel file di configurazione è DEBUG che si trova al gradino più basso della
gerarchia. Se invece impostiamo il livello ad ERROR, ad esempio, noteremmo come verrebbero stampati esclusivamente i messaggi di ERROR e FATAL. Molto spesso il log
viene utilizzato in fase di sviluppo per analizzare il corretto funzionamento dell'applicazione. Quando l'applicazione è terminata, è opportuno monitorare esclusivamente gli eventi più gravi. Con Log4J ciò è possibile modificando appositamente il file di properties.
Ad esempio, nella nostra applicazione, potremo prevedere log di livello DEBUG per tenere traccia di alcune informazioni utili durante lo sviluppo, come il valore di particolari variabili, e log di livello ERROR per tenere traccia di informazioni utili quando l'applicazione è finita. Nel momento in cui l'applicazione è pronta per essere utilizzata basta soltanto
modificare il livello di log (sostituire DEBUG con ERROR) nel file di configurazione e
tutto continua a funzionare senza dover modificare i sorgenti e ricompilarli. Log4net è una
libreria molto potente realizzata come porting di Log4J per la piattaforma Common Language Runtime/.NET. L’entità principale di questo framework è la classe ILog, del namespace Log4Net, la cui definizione, semplificata eliminando gli overload ridondanti, è
descritta perfettamente dal seguente frammento di codice C# in perfetta analogia a quella
31
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
che è l’interfaccia omonima della piattaforma java-based prima discussa:
public interface ILoggerWrapper {
void Debug(object message);
void Debug(object message, Exception exception);
void DebugFormat(string format, params object[] args);
void Error(object message);
void Error(object message, Exception exception);
void ErrorFormat(string format, params object[] args);
void Fatal(object message);
void Fatal(object message, Exception exception);
void FatalFormat(string format, params object[] args);
void Info(object message);
void Info(object message, Exception exception);
void InfoFormat(string format, params object[] args);
void Warn(object message);
void Warn(object message, Exception exception);
void WarnFormat(string format, params object[] args);
}
Come da documentazione, Log4Net prevede di base cinque “livelli” di logging (sebbene
al suo interno siano molti di più e comprendano gli otto livelli canonici dello standard
Syslog), ordinati dal meno al più importante: Debug, Info, Warning, Error, Fatal. Com’è
convenzione in tutte le piattaforme di logging, l’importanza, o più propriamente severità di
un messaggio di log è un valido indice di priorità nelle fasi di analisi, con i log di debug
quasi sempre utilizzati a supporto dell’analisi di situazioni anomale in cui è necessario
avere informazioni di contesto, e i log di livello informativo generati al solo fine di permettere operazioni di data mining o monitoraggio delle prestazioni in condizioni normali
di funzionamento (carico medio, ecc.). Naturalmente, come detto fin dall’inizio, la seman32
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
tica dei messaggi e dunque delle loro severità è a discrezione del programmatore. Log4net
è interamente configurabile a runtime, mediante il meccanismo del file .config della piattaforma .NET come peraltro accade per la sua versione java. Quello che ci interessa sapere è
che anche Log4Net è costituito da una serie di appender, implementabili anche dall’utente
grazie ad apposite interfacce, che realizzano di fatto lo storage del log. Il package principale di Log4Net fornisce implementazioni per degli appender che loggano su disco, su
DBMS, su rete, via email e persino su server Syslog BSD remoti. Log4Net non ha un
meccanismo implicito di supporto alle eccezioni, in generale questo è un aspetto strettamente legato allo specifico linguaggio di programmazione, che deve supportare il costrutto
apposito, e non una caratteristica della procedura di logging. Tuttavia è fondamentale loggare proprio le situazioni di errore, segnalate appunto dalle eccezioni, e che inoltre le eccezioni contengono il prezioso stack trace che permette di risalire alla causa principale del
problema. La verità è che, viene lasciata piena libertà al programmatore di loggare testualmente la condizione di errore con un Error e aggiungere un log di debug contenente lo
stack trace dell’eccezione appena rilevata. Inoltre è possibile osservare che negli output su
file di Log4Net, l’eccezione viene trascritta così com’è come una nuova riga di testo, violando il principio secondo cui il file di log contiene una entry per riga di testo (infatti lo
stack trace è su più righe), al punto che alcuni appender di Log4Net, di fatto escludono le
eccezioni dal messaggio di log per non violare le sintassi.
La libreria Log4C
Log4C[9] è la versione C-based delle librerie descritte in precedenza. Il suo scopo è quello
di fornire agli sviluppatori di software C una API efficiente e configurabile per loggare
messaggi ed eventi. Essa può essere collegata con l’applicativo utente o con alter librerie
esterne che verranno successivamente collegate al programma principale. La libreria non è
fornita da Apache direttamente (ne esiste tuttavia una versione per C++) ed è rilasciata sotto licenza LGPL su SourceForge. Questa libreria è perfettamente compatibile con tutti i
principali compilatori presenti sui principali sistemi operativi in commercio. Tuttavia,
stando a quanto riportato sul sito SourceForge, dal 29 luglio 2010, questo progetto non è
33
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
più sotto attività di sviluppo né da parte dei creatori né di nessun altro. Log4C segue abbastanza precisamente lo stile complessivo del suo corrispettivo Java, infatti, esistono tre tipi
fondamentali di oggetto in esso: Categorie, Appender e Layout. Questi tre oggetti possono
essere visti rispettivamente come il cosa, il quando ed il perché del sistema di logging: Le
categorie descrivono a quale sottosistema sono collegati i messaggi, gli appender determinano dove deve essere inviato il messaggio, mentre i layout dicono come è formattato il
messaggio stesso. Esiste sempre una categoria principale (root) che rappresenta concettualmente la radice di tutta la gerarchia delle categorie. L’atro concetto fondamentale di
Log4C è la priorità di logging: ogni messaggio ha una certa priorità. Ad un certo punto, ad
una particolare istanza di Log4C, deve essere associata una priorità: solo i messaggi con
una priorità almeno pari a quella specificata sull’istanza corrente sono inviati all’appender
per essere processati, e questo fornisce una sorta di prefiltraggio in grado di escludere molti eventi ritenuti inutili ai fini di una particolare analisi. Un’istanza della libreria può essere
configurata in tre differenti modi:
-
Variabili di ambiente.
-
Programmaticamente.
-
File di configurazione.
Le variabili di ambiente tipicamente forniscono un rapido metodo di configurazione dei
parametri di Log4C, che risultano valorizzati prima che l’applicativo cominci a funzionare. Tuttavia solo una piccola parte dei parametri può essere configurata in questa maniera
come ad esempio l’appender e la priorità della categoria radice. Via codice possono essere
configurati tutti i possibili parametri della libreria, e questo può tornare utile quando
l’applicativo target possiede un proprio file di configurazione e risulta comodo utilizzare
quest’ultimo invece che un altro dedicato esclusivamente a Log4C. Il terzo metodo basato
file XML è invece molto utile su quei sistemi in cui molte applicazioni fanno utilizzo di
Log4C, infatti esse possono condividere la stessa configurazione di log: ciò consente
quindi di controllare il comportamento di logging di molte applicazione a partire da un
unico punto di configurazione. Per concludere, è utile dare qualche cenno riguardante la
compilazione della libreria stessa: in generale sono disponibili i file di autoconf e di auto34
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
make per molti OS Unix, mentre su Windows è disponibile un makefile da utilizzare con
MSVC. Gli sviluppatori, in ogni caso, possono anche scegliere di utilizzare un proprio sistema di build per compilare i sorgenti in accordo con le loro specifiche esigenze.
2.2 Utilizzi tipici delle tecniche di logging
La cosa più importante da tenere presente quando si vogliono utilizzare le strategie di logging all’interno di un software è di sicuro la definizione di ciò che si vuole tracciare e di
come farlo. Ciò richiede quindi uno studio preliminare del sistema e del suo ambiente, in
primo luogo per identificare le tecniche migliori da utilizzare ed in secondo luogo per decidere se accompagnare ad esse un componente sviluppato ad-hoc per il monitoraggio del
sistema. La scelta della tecnica appropriata dipende ovviamente anche dagli scopi che ci
siamo prefissi prima di cominciare l’analisi. Le tecniche più comuni per la raccolta di log
sono i report di fallimento e di evento. I primi sono generati da operatori umani, di solito
utenti o staff specializzati e contengono informazioni quali la data e l’ora del fallimento,
una breve descrizione del comportamento anomalo, l’azione intrapresa dall’operatore per
eseguire il ripristino, il modulo hardware/software ritenuto responsabile del fallimento e,
se possibile, la causa principale del fallimento. Il problema principale di questa tecnica è
legato al fatto che gli operatori umani sono responsabili della rilevazione dei fallimenti,
con la conseguenza che spesso alcuni di essi restano ignoti. Inoltre, le informazioni contenute nel report possono variare significativamente tra un operatore ed un altro in base alle
sue esperienze ed opinioni. Per ovviare a ciò recentemente si sono proposti sistemi automatici di report come ad esempio il software di Error Reporting della Microsoft. Esso crea
un report dettagliato ogni volta che un’applicazione va in crash o in stallo, oppure quando
va in crash direttamente il Sistema Operativo. Tale report contiene uno snapshot dello stato del computer durante il crash: questo snapshot include una lista contenente sia il nome
sia il timestamp dei binari caricati nella memoria del computer al momento del crash, cosi
come un semplice stack trace. Questa informazione consente una rapida identificazione
della routine che ha causato il fallimento, così come la ragione e la causa dello stesso. In
ogni caso, siano i log destinati ad un servizio di collectioning, o siano essi destinati sem35
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
plicemente a finire su file in grosse memorie ai fini di una eventuale post analysis, il nocciolo della questione è, di fatto, come gli sviluppatori vogliono tenere traccia degli eventi
che si scatenano in un sistema (o semplicemente in uno solo dei suoi componenti), e di
come questa metodologia possa essere più o meno adatta a rendere i log risultanti semplici
da elaborare o anche solo semplici da catalogare e consultare. Un tipico esempio di codice
instrumentato per effettuare logging può essere il seguente:
static int find_capability(const char *type) {
struct capability_entry *entry;
if(type == NULL) {
pr_error("type cannot be null\n", 0);
return ERR;
}
for(entry = capability_table; entry->type; ++entry)
if(!strncmp(entry->type, type, 4))
break;
return entry->capability;
}
In questo semplicissimo snippet, tratto dai sorgenti dei driver per il CD-ROM (Linux
2.6.7), si evincono subito due cose: innanzitutto il formato dell’unica entry di log è assolutamente arbitrario, o meglio è più orientato alla comprensione di un operatore umano e
non certo di un parser o un analizzatore automatico, inoltre, non esiste nessuna informazione certa riguardo a cosa succeda all’interno del ciclo for, ovvero, cosa accade se durante una iterazione “entry->type” è NULL, oppure, cosa accade se la condizione dell’if più
interno non si verifica mai ed “entry” viene incrementata sino ad arrivare ad una condizione di errore? Ebbene non se ne avrebbe nessuna traccia, perché in entrambi i casi descritti,
la funzione terminerebbe inaspettatamente e probabilmente potrebbero riscontrarsi problemi seri in tutto il modulo kernel. Il log risultante, in questo caso si limiterebbe a riportare eventuali messaggi provenienti da altre sezioni di codice interessate al fault, ma di fatto
non si potrebbe stabilire con certezza quale particolare componente (o addirittura funzio36
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
ne) abbia provocato il malfunzionamento. Esempi di codice con instrumentazioni simili a
questa sono diffusissimi nell’ambito del software open source, ed in generale in tutti i
software in commercio, rendendo chiaro che, purtroppo, non è pensabile spesso utilizzare
le informazioni trovate nei file di log così come sono, anzi spesso non è possibile utilizzarle perché non sono presenti affatto. Questo porta ad effettuare delle serie considerazioni
sui limiti che si riscontrano nell’utilizzare le tecniche di logging per la fasi di raccolta dati
della FFDA, e questo verrà approfondito nel paragrafo successivo.
2.3 Limiti delle tecniche di logging classico ai fini della FFDA
Durante gli ultimi trent’anni, la FFDA ha mostrato tutti i sui benefici se applicata ad una
vasta gamma di sistemi, dai Sistemi Operativi ai sistemi di controllo, e dalle piattaforme
server sino ai dispositivi mobile. Molti studi hanno contribuito ad ottenere una importante
conoscenza dei modelli di fallimento di un numero sempre maggiore di sistemi, rendendo
così possibile il loro miglioramento a partire dalle generazioni successive. Una delle più
diffuse sorgenti di failure data utilizzate nella FFDA è sicuramente rappresentata dai file
di log, che in genere sono il primo posto in cui gli amministratori di sistema vanno a cercare informazioni quando si rendono conto di un’anomalia o di un fallimento. Va anche
detto che, nella maggior parte dei sistemi complessi, i log sono una risorsa abbastanza sottovalutata a causa della loro natura non strutturata e molto soggettiva. Purtroppo, le potenzialità delle analisi FFD basate su log sono compromesse da molteplici fattori, senza considerare che, di fatto, non esiste nessuno standard o strategia comune in grado di dettare le
linee guida nella raccolta dei log stessi. Un primo fattore negativo è senza dubbio legato
all’eterogeneità dei log, che in genere aumenta in maniera proporzionale all’aumento della
complessità del sistema, inoltre, i log possono variare significativamente sia nel formato
sia nei contenuti, a seconda di chi ha scritto lo specifico componente o la specifica sezione
di codice. Mentre l’eterogeneità dei formati può essere aggirata convertendo tutte le entry
di log verso un formato comune, la varietà dei contenuti resta un problema abbastanza
complicato da risolvere in quanto, come già detto, è strettamente legata a come il particolare sviluppatore vuole tenere traccia degli eventi del sistema. Un altro problema, molto
37
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
noto in letteratura, è legato al fatto che in genere i log sono molto inaccurati: essi possono
contenere duplicati o informazioni inutili o addirittura possono essere incompleti e quindi
mancanti di alcuni eventi di fallimento. Un altro difetto delle classiche tecniche di logging
è sicuramente la non consapevolezza della presenza dei fenomeni di propagazione dei fallimenti che spesso è causa di un numero elevato di eventi apparentemente non correlati nei
log. Una strategia ampiamente utilizzata in questi casi è quella di utilizzare una finestra
temporale “one-fits-all” per effettuare la coalescenza temporale degli eventi. Tuttavia ciò è
fatto in genere senza nessuna reale consapevolezza delle vere correlazioni tra i vari messaggi, correndo il serio rischio di classificare come correlati due fallimenti indipendenti e
viceversa, arrivando quindi a dei risultati che, di fatto, non riflettono necessariamente la
realtà dei fatti. Quindi è evidente che la scarsa consapevolezza delle dipendenze sussistenti
tra le varie entità di un sistema porta ad una inevitabile compromissione delle analisi di
correlazione, molto utili per scoprire le tracce di propagazione dei fault. Tutte queste problematiche legate al logging tradizionale, sono esacerbate nel caso di sistemi molto complessi. Infatti, di solito, questa tipologia di sistemi integra un numero elevato di componenti software (sistemi operativi, strati middleware, componenti applicativi, etc.) eseguiti
in una architettura distribuita. In questo caso la mancanza di una soluzione standard di
logging è più sentita in quanto i produttori dei vari elementi del sistema possono essere diversi e non consapevoli l’uno dell’altro, ed ognuno di essi può far loggare il proprio componente secondo un suo specifico formato e senza alcuna forma di cooperazione. Questo
porta inevitabilmente ancora ad avere tutti i problemi di eterogeneità prima discussi. Va
sottolineato poi che tutte le tecniche volte a rimodellare i log già generati da un sistema
così complesso sono molto pesanti e richiedono la stesura di algoritmi ad-hoc per rimuovere i dati non rilevanti, per eliminare le ambiguità e per fare coalescenza tra i dati ritenuti
correlati. Per rendere tangibile quanto detto finora, è molto utile citare uno studio reale fatto sul Web Server Apache riportato in [15]. Da questo studio è emerso un dato molto rilevante, ovvero si è avuta la conferma che la mancanza di molte entry di log e la loro assoluta incorrelazione ed eterogeneità può inficiare la FFDA in maniera assolutamente grave.
Infatti, si è giunti alla conclusione che all’incirca 6 fallimenti su 10 del server non lascia38
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
vano nessuna traccia nei file di log di Apache. Per superare tutte queste problematiche e
limitazioni, deve essere adottata una strategia di logging innovativa e ben definita: ciò può
essere fatto, durante la fase di progettazione e stesura del codice, mediante l’adozione, da
parte degli sviluppatori, di alcune regole nella produzione ed organizzazione degli eventi
di log. In altre parole, è plausibile che un piccolo sforzo durante le fasi di design e sviluppo possa contribuire a produrre dei log che possano essere molto più utili a rendere quanto
più precisi è possibile i risultati una analisi FFD. Inoltre un’altra strada da perseguire è sicuramente quella di avere dei tool automatici, raccolti in un framework, per la generazione
e l’analisi (post e/o real time) degli eventi di log, da fornire agli ingegneri del software, sia
per le nuove applicazioni sia per tutti i sistemi cosiddetti legacy, cercando di renderli
quanto più compatibili con la nuova strategia e con il nuovo strumento in loro possesso.
Lo scopo finale di queste proposte deve essere quello di migliorare la qualità e l’efficacia
delle tecniche di logging, cercando di ottenere dati accurati e omogenei che sono subito
pronti ad essere analizzati senza nessun’altro processo di elaborazione. Inoltre si dovrebbe
rendere possibile l’estrazione (anche on-line) di informazioni aggiuntive a partire degli
eventi di log prodotti dai singoli componenti del sistema sotto analisi.
39
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
3. Approccio Rule-Based alla Field Failure Data Analysis
In questo capitolo andremo a descrivere degli algoritmi e delle regole di logging mirate a
scoprire l’occorrenza e la posizione di un fallimento senza nessuna ambiguità, con particolare rifermento ai fallimenti di timing, in modo da poter tracciare i fenomeni di propagazione introdotti dalle interazioni interne al sistema ed in modo da rendere possibile delle
effettive misure della dependability del sistema osservato. Si descriveranno inoltre le metodologie si interpretazione ed utilizzo dei risultati ottenuti mediante l’applicazione delle
regole di logging, per finire in ultimo con l’introduzione di un framework di logging in
grado di supportare l’analisi FFD log-based.
3.1 Definizione delle regole di logging
Prima di passare alla vera e propria definizione delle regole di logging, è molto importante
cercare di capire in che modo è necessario modellare il sistema da analizzare al fine di
rendere praticabile l’approccio Rule-Based[17]. Questa fase di modellizzazione, rende
possibile non solo la descrizione dei principali componenti e delle principali interazioni
del sistema, ma rende possibile la progettazione delle regole e degli strumenti necessari ad
utilizzarle, senza il bisogno di fare riferimento ad un preciso caso reale.
Fig. 3.1 – Il modello del sistema.
Più in dettaglio, sarà utilizzato un modello per chiarire dove vanno posti i meccanismi di
40
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
logging all’interno del codice sorgente delle applicazioni, e come progettare algoritmi e
tool per automatizzare la raccolta e l’analisi dei log. Detto ciò, andiamo a classificare i
componenti principali di un sistema in due categorie, secondo le seguenti definizioni:
-
Entità: è un elemento attivo del sistema, fornisce servizi che possono essere invocati
da altre entità. Un’entità esegue elaborazioni locali, interagisce con altre entità o risorse del sistema e può essere oggetto di interazioni cominciate da altre entità.
-
Risorsa: è un componente passivo del sistema, può essere al più oggetto di interazioni
cominciate da altre entità del sistema.
Le definizioni proposte forniscono dei concetti abbastanza generali, che hanno ovviamente
bisogno di essere specializzati secondo le esigenze del progettista del sistema. Per esempio, le entità potrebbero essere dei processi o dei threads, che sono sempre elementi attivi
di un sistema, mentre le risorse potrebbero essere dei files e/o un database. Inoltre le entità potrebbero rappresentare dei componenti logici come, ad esempio, del codice eseguibile
contenuto in una libreria o in un package, indipendentemente dal processo che lo esegue.
Come si può immaginare, le entità interagiscono con altri componenti del sistema mediante chiamate a funzione o invocazioni di metodi, al fine di fornire servizi più o meno complessi. Nella stesura delle regole di logging in realtà non si è interessati alla particolare
modalità di interazione di un sistema reale, ma l’attenzione deve essere rivolta alle proprietà dell’interazione stessa, che sono quelle più o meno già accennate in precedenza, ovvero: un’interazione è sempre iniziata da un’entità, il suo oggetto può essere una risorsa o
un’altra entità del sistema, essa può anche generare elaborazioni successive, specie se
coinvolge una o più entità.
Tenendo in considerazione il modello di sistema appena presentato, è necessario comprendere bene dove inserire gli eventi di log all’interno del codice sorgente di un’entità per
consentire effettive misure di dependability. A questo scopo è giusto differenziare due categorie di eventi, le interazioni e gli eventi di life-cycle (ciclo di vita). Le prime forniscono
informazioni legate ai fallimenti, mentre i secondi consentono di conoscere lo stato operativo di un’entità. Ognuna delle regole che andremo a presentare di seguito definisce cosa
41
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
loggare (l’evento osservato dall’entità) e dove loggare (il punto preciso nel codice sorgente un cui inserire la chiamata di log).
Eventi di interazione
Lo scopo di questa tipologia di eventi è quello di rendere possibile la scoperta dei fallimenti delle entità, e di discriminare se essi sono legati ad un’elaborazione locale o ad una
interazione fallita con un’altra entità o risorsa. Andiamo adesso a porre l’attenzione sui
servizi forniti dalle entità, caratterizzati dalle seguenti regole:
-
R1: Service Start – SST, la regola impone che un evento di SST sia loggato prima
dell’instruzione iniziale di ogni servizio.
-
R2: Service End – SEN, la regola impone che un evento di SEN sia loggato dopo
l’ultima istruzione di ogni servizio. Essa fornisce l’evidenza che un’entità, una volta
invocata, serva completamente l’interazione richiesta.
Gli eventi SST e SEN da soli non sono sufficienti a mostrare se un’entità ha fallito a causa
di un errore locale o a causa di un’interazione non andata a buon fine. Per questo motivo è
importante introdurre degli eventi specifici e quindi delle regole per le interazioni:
-
R3: Entity (Resource) Interaction Start – EIS (RIS), la regola impone che un evento di
EIS (RIS) sia loggato prima dell’invocazione di ogni servizio. Essa fornisce l’evidenza
che l’interazione legata ad una certa entità (risorsa) è iniziata dalla entità chiamante.
-
R4: Entity (Resource) Interacion End – EIE (RIE), la regola impone che un evento di
EIE (RIE) sia loggato dopo l’invocazione di ogni servizio. Essa fornisce l’evidenza
che l’invocazione legata ad una certa entità (risorsa) ha avuto fine.
Non è consentita nessun’altra istruzione tra gli eventi EIS (RIS) – EIE (RIE). Un’entità solitamente fornisce più di un singolo servizio, o comunque inizia più di una sola interazione. In questo caso sono prodotti molti SST ed EIS, e diventa impossibile collegare ognuno
di essi al particolare servizio o interazione di cui fanno parte. Per ovviare a questo problema, gli eventi di inizio e fine legati ad ogni servizio o interazione all’interno della stesa entità, devono essere loggati insieme ad una chiave univoca. E’ evidente che l’uso massiccio
delle regole di logging può compromettere la leggibilità del codice, tuttavia, sfruttando la
42
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
loro semplicità, è possibile creare dei supporti ad-hoc per automatizzare l’inserimento delle chiamate di log appena prima della fase di compilazione dei sorgenti. Un approccio del
genere renderebbe la scrittura delle regole del tutto trasparente agli sviluppatori e non richiederebbe una modifica diretta del codice sorgente.
Eventi del ciclo di vita
Gli eventi di interazione da soli non consentono di comprendere se una particolare entità è
attualmente fuori servizio o se si è riavviata dopo un fallimento. Per affrontare questa problematica, è necessario introdurre degli eventi specifici per il ciclo di vita delle entità, che
rendono possibile la comprensione dello stato operativo di una certa entità, fornendo informazioni riguardo al fatto che essa abbia correttamente iniziato la sua esecuzione o che
la abbia terminata senza problemi. In tal senso si introducono due ulteriori regole:
-
R5: Start Up – SUP, la regola impone di loggare un evento di SUP come prima istruzione di un’entità al suo avvio.
-
R6: Shutdown – SDW, la regola impone di loggare un evento di SDW come ultima instruzione di un’entità, prima che essa termini correttamente.
Questi eventi sono molto utili per valutare i parametri di dependability del sistema, anche
in tempo reale, come l’uptime e il downtime di ogni entità. Inoltre, le sequenze di SUP e
SDW consentono di identificare i riavvii puliti e sporchi (dalla letteratura clean e dirty), in
analogia a precedenti studi che miravano ad individuare le pricipali cause dei riavvi nei sistemi operativi[16]. L’idea alla base degli eventi di life-cycle è proprio quella di sfruttare
questo concetto ad una grana più fine, in base al modello del sistema, applicandolo a tutte
le entità che lo compongono.
43
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
3.2 Applicazione delle regole di logging ai fini della FFDA
L’uso congiunto del modello e delle regole proposte, fa percepire il sistema, dal punto di
vista dell’analista, come un insieme di entità, ognuna in grado di produrre un flusso di
eventi. Questi flussi possono essere utilizzati per estrarre, durante l’esercizio del sistema,
utilissime informazioni riguardo allo stato corrente dell’esecuzione di tutte le entità, così
come di scoprire eventuali occorrenze di fallimenti. A questo scopo è necessario presentare alcuni semplici algoritmi che, a partire dalle regole prima descritte, riescono ad identificare e correlare degli Alert durante il funzionamento del sistema e quindi ad effettuare effettive misure di dependability. Analizzare i file per isolare le entità, al fine di rilevare
eventuali fallimenti del sistema, è una fase della FFDA che richiede molto tempo e molte
risorse. Come già detto in precedenza, in genere, l’identificazione degli alert viene fatta
osservando il livello di severity delle entry di log o il loro tipo (se il meccanismo di logging usato lo consente) ed analizzando il testo contenuto nel corpo del messaggio di log
(per cercare di capire, ad esempio, se contiene delle specifiche parole chiave collegate al
failure). L’applicazione delle regole deve portare alla presenza di entry di Alert all’interno
del log finale. Ovviamente, l’inacuratezza dei file di log può compromettere questo tipo di
analisi, infatti basta pensare che, se due entry di log con lo stesso significato logico contengono testo differente, possono essere erroneamente classificate come incorrelate o viceversa. Inoltre, all’occorrenza di alcuni fallimenti, come ad esempio gli hang, ci si trova
spesso in possesso di pochissime entry di log utili per la loro identificazione. Gli eventi di
interazione sono stati progettati per rendere possibile l’automazione della identificazione
degli alert, e per discriminare tra gli alert dovuti a cause locali o esterne, come spiegheremo in seguito. Per costruzione, il codice di logging è inframezzato al codice sorgente
dell’entità, inoltre si assume che i possibili errori possono essere quelli legati alla modifica, sospensione o terminazione del flusso di esecuzione dell’entità, che possono portare alla perdita dei messaggi. Questo tipo di assunzione va a coprire una certa casistica di fallimenti quali possono essere i crash e gli hang (attivi e passivi) delle entità un sistema.
Quando una entità va in crash (o in hang) mentre sta servendo una richiesta, si avrà la
mancanza di un SEN nel flusso interessato e, allo stesso tempo, l’entità chiamante potreb44
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
be non essere in grado di scrivere correttamente il suo evento di EIE. Tale assunzione,
d’altra parte, non copre i fallimenti di valore, ma questo è un problema affrontabile anche
mediante altre tecniche non necessariamente legate alle regole di logging (sebbene potrebbe essere utile introdurre un nuovo tipo di evento detto Complaint-CMP, da sollevare
quando lo sviluppatore ritiene che l’entità stia eseguendo una sezione di codice in seguito
ad un simile fallimento).
Fig. 3.2 – La rilevazione di un Alert.
Dato che il nostro interesse è quello di rilevare anomalie risultanti dal ritardo o dalla perdita di eventi, possiamo improntare la detection sulla base di finestre temporali in cui osservare il flusso di eventi generati dal sistema. Come già detto, i messaggi di log sono forniti
in coppie inizio-fine, cioè un SST deve essere seguito dal corrispettivo SEN, così come un
EIS deve essere seguito dal suo EIE (analogamente per le risorse). E’ possibile quindi misurare il tempo trascorso tra due eventi collegati (ad esempio l’inizio e la fine dello stesso
servizio o interazione) durante ogni operazione del sistema conclusasi senza fallimenti di
nessun tipo, in modo da tenere costantemente aggiornata la durata attesa di ogni coppia di
eventi (come mostrato in figura 3.2). In questo modo è possibile configurare un timeout
appropriato per il processo di identificazione degli Alert. Tale processo deve quindi generare un alert quando viene perso un messaggio di fine, a tal riguardo è possibile definire tre
tipi di alert differenti:
-
A1: Entity Interaction Alert – EIA, viene generato quando un messaggio di EIS non è
seguito dal suo corrispettivo EIE all’interno della sua finestra temporale.
-
A2: Resource Interaction Alert – RIA, viene generato quando un messaggio di RIS
non è seguito dal sui corrispettivo RIE all’interno della sua finestra temporale.
45
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
-
A3: Computation Alert – COA, viene generato quando un messaggio di SST non è seguito dal suo corrispettivo SEN all’interno della sua finestra temporale e non sono stati
già generati un EIE o un RIA.
Con riferimento a quest’ultimo tipo di alert è evidente che esso risulta essere legato ad uno
o più problemi locali rispetto alla entità che lo ha generato, mentre un interaction alert riporta un comportamento errato dovuto ad una causa esterna all’entità interessata. Come
già detto, il fenomeno della propagazione degli errori è dovuto alle interazioni tra i componenti di un sistema e, in genere può portare alla generazione di molteplici alert. La coalescenza, in questi casi, rende possibile ridurre il numero di informazioni in eccesso unendo più alert distinti per formarne uno solo, rendendo così l’analisi più agevole.
L’approccio tradizionale affronta solitamente l’eccesso di alert con strumenti esclusivamente time-based, ma senza nessuna vera consapevolezza della reale correlazione tra i
messaggi di log raccolti. L’uso di precise regole di logging riduce significativamente lo
sforzo da profondere in fase di analisi, e incrementa considerevolmente l’efficacia della
fase di coalescenza. E’ evidente, infatti, che gli eventi di interazione rendono possibile la
discriminazione tra le diverse tipologie di alert, ognuna delle quali ha il suo specifico significato: COA e RIA permettono di identificare i fallimenti della sorgente, mentre gli
EIA sono utilissimi per tracciare i fenomeni di propagazione degli errori. Per concludere
questa sezione inerente all’applicazione delle regole in fase di analisi è utile mostrare come i messaggi di SUP ed SDW possono essere utilizzati per analizzare il ciclo di vita delle
entità di un sistema. Per rendere più semplice questo tipo di analisi è utile rappresentare lo
stato di esecuzione di queste ultime mediante una macchina a stati finiti.
Fig. 3.3 – Gli stati operativi di un’entità.
Come si evince facilmente dalla figura riportata è possibile identificare tre possibili stati di
46
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
esecuzione per ogni entità del sistema, i quali possono essere descritti come segue:
-
UP: l’entità è attiva e sta funzionando correttamente.
-
BAD: l’entità potrebbe trovarsi in uno stato corrotto.
-
DOWN: l’entità non è attiva.
Quando un’entità inizia la sua esecuzione, viene generato un messaggio di SUP, e si assume di trovarsi nello stato UP. Se viene osservato un messaggio di interaction alert come
RIA o EIA, l’entità è considerata ancora attiva, ma probabilmente coinvolta in un fallimento da un altro componente del sistema (sia esso una risorsa o un’altra entità). I alcuni
casi, a causa di manutenzioni programmate, o a causa di persistenti problemi di interazione, una entità può essere riavviata. In questo caso si dovrà osservare un messaggio di
SDW, che farà transitare l’entità stessa nello stato DOWN. Se ad un certo punto viene ricevuto un messaggio di COA, l’entità transiterà nello stato BAD. Come già detto, un COA
è il risultato di un problema interno all’entità, e per tale motivo è lecito assumere che lo
stato di quella stesa entità possa essere compromesso. Un’entità nello stato di BAD potrebbe in ogni caso essere ancora capace di compiere normali operazioni (questo potrebbe
essere il caso di fallimenti transitori) oppure potrebbe semplicemente essere andata in
crash. In questo caso, se vengono osservati altri messaggi di COA, RIA o EIA, l’entità
continuerà a permanere nello stato di BAD. Quando una entità viene ripristinata, è possibile osservare un riavvio di tipo clean o di tipo dirty: nel primo caso si osserverà la presenza
della coppia SDW-SUP, che sta a testimoniare che l’entità era ancora attiva al momento
del riavvio (in quanto è riuscita a loggare l’evento di shutdown), mentre nel secondo caso
verrà osservato il solo messaggio di SUP (transizione da stato BAD a stato UP), in quanto
probabilmente l’entità non era più in grado di loggare nulla al momento del riavvio (presumibilmente era andata in crash). Da queste assunzioni è possibile tirare fuori dei dati
molto indicativi per la stima della dependability del sistema, infatti, ad esempio, il tempo
che l‘entità permane nello stato UP contribuisce a valutarne l’uptime, mentre il tempo che
intercorre tra un SUP ed un COA può rappresentare una stima del Time to Failure. In altre
parole, l’obiettivo ultimo di questo approccio alla FFDA è quello di ottenere un log finale
composto esclusivamente dagli eventi rilevanti delle singole entità, ovvero già epurato di
47
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
tutte quelle entry aggiuntive e / o duplicate che non danno nessun tipo di informazione riguardo agli hang, ai riavvi oppure ai crash:
Fig. 3.4 – Un esempio di report di log Rule-Based.
Un report come questo rende semplicissima la rilevazione dei fallimenti di un sistema e
delle sue entità modellate, infatti, sono presenti solo le entry necessarie ad effettuare le
reali misure di dependability volute. La cosa principale è che tutte le analisi possono essere fatte senza dover ricorrere allo sforzo di pre-elaborazione in genere richiesto dalla tecniche classiche di logging. E’ evidente che le tuple del report forniscono una visione aggregata dei dati relativi a i failure, e ciò rende completamente inutile l’applicazione di procedure di coalescenza. Queste informazioni, devono essere poi combinate con gli eventi
del ciclo di vita, in modo da rendere completamente automatiche le rilevazioni delle occorrenze di reboot del sistema. A partire da questa tipologia di report sono possibili tantissime misure della dependability del sistema, ad esempio: la distanza tra SUP ed SDW ci
darà l’uptime, la presenza di COA sarà sintomo di anomalie riscontrate dal software, inoltre, grazie alla consapevolezza delle relazioni tra le entità, si potranno analizzare subito le
propagazione dei guasti verificatesi all’interno dei componenti del sistema monitorato.
48
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
3.3 Implementazione della strategia di logging Rule-Based
Si rende necessario adesso un passaggio da quelle che sono le regole teoriche presentate
nei paragrafi precedenti a quella che può essere definita una vera e proprio strategia di
logging volta a massimizzare l’utilità dai log ai fini della Field Failure Data Analysis.
Cominciamo con il dire che nei messaggi FFDA non devono essere presenti necessariamente informazioni aggiuntive sul contesto o su eventuali parametri di input, essi devono
essere memorizzati così come sono in modo da poter essere analizzati in seguito insieme
ai tradizionali log. E’ importante tenere presente che in generale questa strategia è volta
esclusivamente alla FFDA e quindi non è progettata per il debugging, cioè, seppure utile a
rendere semplice l’individuazione della root cause di un fallimento, non è necessariamente
capace di dare informazioni sul perché del fallimento stesso. In generale la detection di un
fallimento può essere fatta sulla base dell’assenza o meno dei messaggi di End rispetto a
quelli corrispondenti di Start, cosa che può verificarsi in caso di hang di un nodo, di deadlock del sistema o più semplicemente in caso di cicli infiniti in alcune sezioni del codice.
Consideriamo ad esempio un frammento di codice di un banale sistema composto da due
entità che collaborano per fornire un determinato servizio:
void service(int x){
operation();
[…]
interaction(y);
[…]
operation();
[…]
}
void interaction(int y) {
operation();
[…]
z = read();
[…]
}
Ognuna delle istruzioni, incluse quelle omesse, può fallire. Per fallimento, intendiamo un
qualsiasi comportamento inaspettato o anomalo che differisce da quello atteso. In partico49
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
lare si può fare riferimento ai fallimenti di tipo timing e di valore. In un moderno linguaggio di programmazione, ognuna delle istruzioni riportate sopra potrebbe generare un fault
o comunque rilevarlo (lanciando ad esempio un’eccezione), portando il sistema a completare il servizio in un tempo eccessivo rispetto alle specifiche iniziali o addirittura portando
ad un hang in cui il sistema è bloccato e non risponde più a nessun input. La maniera più
semplice di realizzare l’approccio Rule-Based in questo caso è quello di modificare il codice secondo una strategia ben precisa:
1) Per ogni entità che fornisce uno o più servizi, si deve circondare l’intero metodo con
un blocco try-catch-finally (se il linguaggio lo consente , altrimenti una tecnica simile).
La prima istruzione del blocco try deve essere il log di un evento SST. Il blocco catch
deve loggare esclusivamente il messaggio CMP ed effettuare il throws dell’eccezione
catturata, mentre il blocco finally deve contenere solo il messaggio di SEN.
2) Bisogna circondare ogni chiamata a metodo in cui è stata applicata la regola 1 con le
istruzioni di EIS ed EIE rispettivamente appena prima e appena dopo la chiamata in
questione.
3) Si deve circondare ogni chiamata a metodo in cui non è stata applicata la regola 1 e di
cui si ipotizza un potenziale fallimento, con le istruzioni di RIS e RIE rispettivamente
appena prima e appena dopo la chiamata in questione.
La ragione per la quale si è scelto di usare il blocco try-catch-finally è quella di minimizzare i punti in cui effettuare la instrumentazione del codice. Mentre l’evento SST può essere ragionevolmente messo in maniera univoca all’inizio del metodo, l’evento di SEN
dovrebbe essere messo prima di ogni punto di return, ed inoltre si dovrebbero considerare
tutte le possibilità di fallimenti di valore non gestiti all’interno del metodo in questione. Il
blocco finally garantisce che l’evento di SEN venga loggato come ultima azione del metodo chiamato (in modo che il controllo non venga restituito se prima il messaggio non viene inviato), mentre il blocco catch garantisce che se il metodo originale doveva sollevare
un’eccezione, questa viene comunque propagata, ma solo dopo aver loggato un CMP. E’
molto importante che l’eccezione venga propagata dopo aver loggato il messaggio di CMP
in modo da lasciare inalterato il comportamento originale del metodo in questione.
50
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Il frammento di codice mostrato prima può quindi essere modificato come segue per mostrare l’applicazione dell’approccio Rule-Based:
void service (int x){
try {
Log(SST);
operation();
Log(EIS);
interaction(y);
Log(EIE);
operation();
}catch (Exception ex){
Log(CMP);
throw ex;
}finally {
Log(SEN);
}
}
void interaction(int y) {
try {
Log(SST);
operation();
Log(RIS);
z = read();
Log(RIE);
}catch (Exception ex) {
Log(CMP);
throw ex;
}finally {
Log(SEN);
}
}
In questo esempio, l’istruzione chiamata “operation()” non è stata ritenuta suscettibile di
fallimento per tale motivo non è stata circondata dalla coppia di istruzioni RIS / RIE. E’
molto importante comprendere che tutti i messaggi FFD sono appositamente strutturati
non solo per descrivere il tipo di evento osservato, ma anche per identificare il flusso di
esecuzione, in modo da costruire una sorta di StackTrace distribuito da partire dal caos dei
messaggi di log concorrenti.
51
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
3.4 Una piattaforma per la FFDA log-based: Logbus-ng
A questo punto presentiamo la piattaforma Logbus-ng in grado di fornire agli sviluppatori
e agli analisti gli strumenti per la raccolta e l’analisi dei log in pieno accordo con la strategia discussa nei precedenti paragrafi. I requisiti funzionali del bus sono riportati di seguito:
-
La piattaforma deve acquisire messaggi di log per via remota dalle applicazioni e/o dai
sistemi operativi.
-
Il formato dei messaggi di log ricevuti deve essere Syslog BSD o Syslog 2009.
-
La piattaforma deve inoltrare, inalterati, i messaggi di log ricevuti verso i client remoti
che effettuino una apposita sottoscrizione ad un canale.
-
Deve essere possibile fornire supporto ad altri formati di messaggio, in ingresso o in
uscita, mediante componenti plug n’ play.
Inoltre, i requisiti non funzionali della piattaforma di logging sono i seguenti:
-
Il nodo bus deve poter girare sulle maggiori piattaforme hardware/software.
-
L’impatto prestazionale deve essere minimo.
-
Il bus deve poter supportare elevati volumi di traffico senza ripercussioni.
-
Il bus deve poter consegnare i messaggi in maniera affidabile, se richiesto dal client in
fase di sottoscrizione, senza impattare sulle prestazioni degli altri client.
-
Il bus dovrebbe poter essere replicato o distribuito su più nodi.
Nella sua architettura, il Logbus è suddiviso in tre segmenti principali: il segmento delle
sorgenti di log, quello del core che gestisce flussi e canali, e infine il segmento dei clients,
che in seguito saranno denominati più specificamente monitor. I segmenti sorgente e monitor sono definiti da apposite interfacce mediante contratti di servizio e protocolli di rete
basati su TCP/IP, ed implementati da apposite API per i maggiori linguaggi di programmazione. Dovendo fornire il supporto a componenti plug n’ play, possiamo approfittarne
per permettere agli sviluppatori di definire delle proprie interfacce di ingresso/uscita dei
messaggi compatibilmente con le proprie esigenze, purché vengano implementate in componenti per Logbus-ng ed API adeguate. Questo potrebbe essere il caso in cui
un’applicazione legacy effettua logging remoto mediante protocolli di rete proprietari, da
cui effettuare in seguito la conversione a Syslog.
52
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Per rispettare il requisito di compatibilità con le maggiori piattaforme HW/SW, si è optato
per una virtual machine disponibile commercialmente: si è quindi scelto il linguaggio C# e
la relativa piattaforma Common Language Runtime, di cui le implementazioni commerciali disponibili sono .NET per Microsoft Windows e Mono per ambienti POSIX. La scelta è stata anche in parte dettata dall’opportunità di lavorare a stretto contatto con
Log4Net[11] e il relativo codice sorgente.
Si è già in parte esaminato le possibilità attuali di logging remoto secondo il protocollo
Syslog e le piattaforme Apache logging. Syslog 2009 prevede, in un apposito standard, un
meccanismo per trasferire messaggi di log usando il protocollo UDP sul porto 514. Si è
inoltre constatato che il demone syslog-ng può essere configurato per inoltrare tutti o parte
degli eventi registrati verso un host syslog remoto mediante l’invio di ciascun messaggio,
codificato ASCII, e al contempo ascoltare sul porto 514 messaggi provenienti da nodi remoti. Inoltre, abbiamo già detto che anche Log4Net[11] e Log4J[10] dispongono di un
componente in grado di inoltrare messaggi Syslog verso host remoti. La documentazione
riporta infatti che il formato in uso è BSD Syslog. In nessun caso è prevista una procedura
di handshake: quando accade un evento da loggare, un messaggio viene spedito all’host
remoto, che è sempre pre-configurato nell’applicazione. Dunque l’interfaccia sorgente di
Logbus, e di conseguenza l’implementazione della API, più semplice consiste nell’inviare
messaggi Syslog incapsulati in un pacchetto UDP verso un host e un porto prefissati; se il
porto non è specificato, va assunto pari al 514.
Nella log analysis, la prima fase è quella del filtraggio dei log per escludere quei messaggi
che non contengono informazioni utili alle analisi. Se è vero che nelle analisi manuali può
essere utile tenere ugualmente traccia dei messaggi di debug o simili al fine di ottenere
maggiori informazioni sul contesto, è altrettanto vero che in caso di analisi automatiche
non è possibile estrarre contenuti informativi utili da messaggi non conosciuti
dall’applicazione che li elabora. Per di ridurre il traffico sulla rete e lo sforzo di elaborazione, è stato ritenuto utile un filtraggio a monte dei messaggi, ad opera del core.
53
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Si definisce allora canale di uscita, un canale logico associato ad un filtro booleano. A
ciascun canale possono essere iscritti n client; tutti i client iscritti allo stesso canale ricevono tutti e soli i messaggi che corrispondono al filtro specificato. Ora risulta evidente un
nuovo requisito funzionale: un client deve poter creare (e, perché no, eliminare) canali
di uscita fornendo un proprio filtro. Al fine di definire filtri interoperabili tra piattaforme diverse, sono stati definiti dei filtri standard, la cui semantica e i cui parametri sono noti, nel linguaggio XML su cui si basano i Web Services. Esaminiamo solo alcuni dei filtri,
modellati sul formato Syslog 2009:
-
True (False): filtro che accetta (rifiuta) qualsiasi messaggio.
-
And (Or): filtro composto che accetta il messaggio solo se tutti i filtri (almeno uno dei
filtri) che lo compongono danno (dà) esito positivo.
-
Not: filtro composto che dà esito opposto rispetto al filtro che lo compone.
-
RegexMatch: il messaggio è accettato solo se rispetta la Regular Expression fornita.
-
SeverityFilter: confronta la Severity del messaggio con un valore fornito applicando
l’operatore di confronto scelto dall’utente (es. [>=, Warning] è una valida coppia [operatore, valore] e accetta i messaggi con valore di severity minore o uguale di 4).
Esiste un tipo speciale di filtro, personalizzato, richiamabile mediante un tag identificativo
e un insieme di parametri liberi, definiti tramite contratto di design. Si è ritenuto quindi lecito imporre come requisito dell’implementazione C# che i filtri personalizzati siano realizzabili tramite componenti pluggable: questo non ha effetti sulle caratteristiche di interoperabilità di Logbus-ng, poiché una implementazione del segmento core per una diversa
piattaforma ad opera di un diverso sviluppatore può comunque prevedere, cablati nel codice, tutti i filtri personalizzati richiesti, mappati su opportune chiavi.
Addentrandosi nella sezione core del Logbus, è possibile visualizzare un modello (specifico per l’implementazione C#), basato su una pipeline a quattro stadi: stadio di ingresso
(inbound channel), stadio di smistamento (hub), stadio dei canali di uscita (outbound
channel), stadio di trasporto in uscita (outbound transport).
54
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Outbound
Transport
Inbound
channel
Outbound
channel
Outbound
Transport
Outbound
Transport
Inbound
channel
Hub
Outbound
channel
Outbound
Transport
Outbound
Transport
Outbound
channel
Outbound
Transport
Inbound
channel
Outbound
Transport
Fig. 3.5 – La pipeline del core di Logbus-ng
Per comprendere la figura al meglio, è opportuno fare un esempio: supponiamo di voler
utilizzare il nostro Logbus-ng per raccogliere messaggi remoti tramite UDP, TLS e dal
servizio Event Log di Windows: ciascun canale di ingresso presenta le proprie peculiarità
implementative, ma tutti forniscono in uscita messaggi nel formato Syslog (evidentemente
quelli di Windows Event Log vanno opportunamente convertiti), quindi ciascuno è rappresentato da un cerchio Inbound Channel; l’hub, da questa prospettiva, riceve i messaggi e li
inoltra a ciascun canale in uscita, rappresentato da un cerchio Outbound Channel e il cui
unico scopo è decidere se il messaggio dovrà essere o meno inoltrato ai client in base alla
valutazione del filtro; per fare questo, ciascun canale dispone di uno o più gestori delle interfacce di trasporto in uscita, rappresentate dai cerchi Outbound Transport, che gestiscono la consegna effettiva al client remoto. Da notare che i cerchi rappresentano le istanze di
ciascun tipo di gestore, e sono considerati essere multicast: un gestore per il trasporto TLS,
ad esempio, viene istanziato da tutti i canali su cui almeno un client è sottoscritto con quel
tipo di trasporto, e ciascun gestore TLS possiederà la lista dei client remoti a cui inviare i
messaggi tramite messaggi unicast come previsto dal protocollo; il canale in uscita, dal
55
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
suo punto di vista, vede un insieme di canali multicast a cui consegnare il messaggio solo
una volta ciascuno. Al fine di rendere accessibile il Logbus ad eventuali client è stata realizzata una interfaccia derivante da una specifica IDL/WSDL in cui sono stati descritti i
metodi ritenuti necessari alla corretto utilizzo delle funzionalità del bus software:
-
ListChannels, per elencare i canali disponibili per la sottoscrizione.
-
GetChannelInformation, per raccogliere informazioni circa un canale esistente.
-
CreateChannel e DeleteChannel, per creare ed eliminare un canale rispettivamente.
-
GetAvailableFilters, per richiedere al server quali filtri personalizzati sono disponibili
in aggiunta a quelli predefiniti di cui abbiamo discusso.
-
GetAvailableTransports, per richiedere al server la lista dei protocolli di trasporto disponibili tra cui scegliere.
-
SubscribeChannel e UnsubscribeChannel, per le operazioni di sottoscrizione.
Sicuramente il metodo SubscribeChannel richiede come parametri un identificativo per il
canale e l’identificativo del protocollo di trasporto da usare, inoltre un client deve fornire
al server alcuni parametri di trasporto per sottoscrivere un canale Syslog. Supponendo di
voler utilizzare UDP come trasporto, questi parametri sono ragionevolmente indirizzo IP e
porto destinazione. In questo specifico caso, per le caratteristiche del protocollo, non è
possibile rilevare un fallimento del client, ed inoltre questo, una volta tornato in vita, potrebbe non essere in grado di recuperare o annullare la sua vecchia iscrizione al canale: il
metodo RefreshSubscription dell’interfaccia serve proprio a questo. Ovviamente il meccanismo di refresh utilizza un certo time-to-live per la sottoscrizione di ciascun canale,
sicché se il client non invoca il metodo entro il TTL, la sottoscrizione decade e il server
cessa l’invio dei datagrammi. La struttura così flessibile del Logbus oltre ad essere un requisito fortemente voluto in fase di progettazione dell’architettura generale, si rivela essere
un’arma molto potente per chi fosse interessato a sviluppare delle API compatibili con esso, sia lato sorgente che lato client (o monitor). Sarà proprio questo il passo successivo del
presente lavoro di tesi, ovvero progettare e realizzare una libreria di funzioni in grado di
supportare la FFDA log-based in perfetto accordo con le regole di logging presentate nel
corso di questo capitolo.
56
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
4. Realizzazione delle API per la FFDA in Logbus-ng
Come già accennato nel capitolo precedente, il lavoro svolto nell’ambito di questa tesi è
stato anche quello di utilizzare l’architettura del Logbus-ng per realizzare la Field Failure
Data Analysis log-based. Sono state quindi progettate e realizzate una serie di funzionalità raccolte in una API volta a rendere oltre che possibile, anche semplice l’applicazione
delle regole di logging, ricordando come esse differiscano radicalmente dalle classiche
tecniche di logging utilizzate per fare debug, audit, accounting o security. Per implementare nella maniera più semplice le API prima descritte in Logbus-ng è stato aggiunto un livello software a quello già presente per il logging classico, in modo da aiutare gli sviluppatori a loggare gli eventi FFD nella maniera più semplice e gli analisti ad elaborare i dati
raccolti in modo rapido ed efficace. E’ stata quindi realizzata una classe FFDALogger, e
tutta una serie di filtri in base alle entità del sistema, che si occupano proprio di fornire gli
strumenti necessari ad utilizzare Logbus per effettuare l’analisi FFD.
4.1 Implementazione della API in C#
La prima implementazione realizzata della API descritta nel capitolo precedente è stata
quella in linguaggio C#. Come già detto in fase di progettazione la API si divide sostanzialmente in due sezioni, quella per produrre i Log e quella per riceverli ed analizzarli. Le
interfacce fornite lato produttore (source) sono molto simili a quelle fornite dalle più comuni API di logging già discusse nel secondo capitolo, fermo restando che sono state ovviamente specializzate per gestire la produzione degli eventi che si riferiscono alle regole
di logging già presentate in precedenza. La API lato sorgente è implementata come
un’estensione del core principale di Logbus-ng[19], pertanto può essere distribuita come
57
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
dll stand-alone nel package Extensions. Un qualsiasi software che volesse utilizzare tale
libreria non deve fare altro che includerla nei suoi riferimenti e costruire un FFDALogger
mediante i meccanismi messi a disposizione dalla API. La API lato consumatore (monitor)
è in realtà composta da una serie di filtri e metodi dedicati alla individuazione delle varie
entità del sistema in analisi, grazie alla presenza nel core di un apposito plug-in:
l’EntityPlugin, che si occupa appunto di monitorare le entità attive creando canali ad-hoc.
Andiamo ad analizzare nel dettaglio la API lato sorgente. Essa deve fornire un’interfaccia
speciale per loggare gli eventi specifici della FFDA, ovvero i messaggi già analizzati
nell’approccio Rule-Based descritto al capitolo 3. Di conseguenza, sarà possibile loggare
solo i seguenti messaggi:
public interface IFieldFailureDataLogger: IDisposable{
void LogSST(String id);
void LogSST();
void LogSEN(String id);
void LogSEN();
void LogEIS(String id);
void LogEIS();
void LogEIE(String id);
void LogEIE();
void LogRIS(String id);
void LogRIS();
void LogRIE(String id);
void LogRIE();
void LogCMP(String id);
void LogCMP();
}
Il Flusso di esecuzione, nella presente API, è rappresentato dal parametro id passabile ai
metodi di logging, oppure, nel caso non fosse specificato, si assume essere uguale al codice hash del thread che esegue la chiamata di log. Esso dovrà essere tracciabile nei sistemi
concorrenti e distribuiti, e ciò significa che più flussi di esecuzioni concorrenti sullo stesso
nodo dovranno essere distinguibili, e che, flussi in esecuzioni su nodi differenti, dovranno
essere tra loro collegati nel caso in cui ci fosse tra loro un nesso di causa-effetto, a seguito
di un’invocazione. La API si basa sul meccanismo del pattern factory, in modo da rendere
58
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
assolutamente trasparente al programmatore la fase di creazione e valorizzazione
dell’oggetto Logger dedicato alla FFDA (in perfetto accordo con i meccanismi di logging
classici comunque previsti dalla piattaforma Logbus-ng). La factory dei Logger prevede
ovviamente una fase di inizializzazione che può semplicemente essere fatta mediante file
xml (anche in questo caso l’analogia con i framework log4xx è palese nonché voluta per
rendere il meccanismo quanto più semplice è possibile). Nello specifico, la configurazione
della libreria, lato source, può essere la seguente:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="logbus-logger" type="It.Unina.Dis.Logbus.Configuration.LogbusLoggerConfigurationSectionHandler,
It.Unina.Dis.Logbus, Culture=neutral, PublicKeyToken=9bbc6deeaedcd38f" />
</configSections>
<logbus-logger xmlns="http://www.dis.unina.it/logbus-ng/configuration/2.0" xmlns:config=”http://www.dis.unina.it/logbusng/configuration/2.0” default-collector="reliable" default-heartbeat-interval="20">
<collector config:type="SyslogTlsCollector" id="reliable">
<param config:name="host" config:value="localhost"/>
<param config:name="port" config:value="6514"/>
</collector>
<collector config:type="SyslogUdpCollector" id="unreliable">
<param config:name="ip" config:value="127.0.0.1"/>
<param config:name="port" config:value="7514"/>
</collector>
</logbus-logger>
</configuration>
Com’è possibile capire dallo snippet riportato sopra, la configurazione xml è costituita sostanzialmente da due parti, una prima in cui sono dichiarate le sezioni (nel nostro caso
l’unica sezione dichiarata è quella che si riferisce al logger), ed una seconda in cui vengono istanziate le sezioni dichiarate in base alle proprie esigenze. Nel caso specifico è stato
definito come collector (livello trasporto utilizzato dai logger) di default un collector di tipo reliable con un intervallo di heartbeat pari a 20 secondi (l’heartbeat è un semplice messaggio generato dal logger per avvisare un eventuale monitor in ascolto che l’entità è ancora viva anche nel caso in cui non invii più eventi, ciò è utile per distinguere i crash dagli
hang). Il collector di default è inizializzato, come detto, per essere di tipo reliable, ovvero
59
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
in modo tale da garantire l’effettiva consegna dei messaggi al Logbus. Per fare ciò si sceglie TLS, che fornisce sia un trasporto affidabile quale TCP, sia una certa forma di sicurezza grazie ad SSL[6]. I parametri minimi da inserire in configurazione sono host / ip e
porta (se non specificata viene scelta la 6514 di default). E’ possibile inoltre notare la presenza di un collector non reliable basato su UDP, per il quale vale lo stesso formato di parametri del collector precedente. La factory dei logger, sulla base della configurazione riportata prima, potrà creare sia un logger basato su trasporto affidabile sia un logger basato
su trasporto inaffidabile (fermo restando che esistono anche possibilità quali la Console, il
FileSystem, un DBMS, etc.). Per utilizzare la factory di cui sopra, è necessario utilizzare
le interfacce offerte dalla API:
public class FieldFailureDataHelper {
public static IFieldFailureDataLogger CreateFailureDataLogger(string loggerName);
public static IFieldFailureAlerter CreateFailureAlerter();
public static IInstrumentedLogger CreateInstrumentedLogger(string loggerName);
public static IFieldFailureDataLogger CreateUnreliableFailureDataLogger(string loggerName,
string host, int port);
public static IFieldFailureDataLogger CreateUnreliableFailureDataLogger(string loggerName,
IPAddress host, int port);
public static IFieldFailureDataLogger CreateReliableFailureDataLogger(string loggerName,
string host, int port);
}
Per brevità non sono state riportate le specifiche implementazioni dei metodi della classe
Helper, tuttavia se né può fornire una breve descrizione al fine di comprenderne il funzionamento generale e le modalità di utilizzo da parte degli sviluppatori:
-
CreateFailureDataLogger: Crea un Logger FFDA a partire dalla configurazione e dal
nome. Se il logger è stato definito come statico ed è stato già istanziato in precedenza,
il metodo restituisce l’istanza corrente (utile per i logger di classe e/o di package).
-
CreateFailureAlerter: Crea un Logger FFDA per i monitor basandosi sul collector
definito nella configurazione xml. Si rivela molto utile se i monitor preposti a rilevare
gli alert e quindi a generare i vari COA, EIA e RIA, sono interessati ad immettere questi eventi all’interno del Logbus per condividerli con altri monitor in ascolto (si pensi
ad un caso distribuito in cui il monitor è locale al nodo su cui girà l’applicativo e si
voglia inviare gli alert generati ad uno o più monitor remoti).
60
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
-
CreateInstrumentedLogger: Crea un Logger per eseguire l’instrumentazione del codice sorgente mediante le regole definite nel terzo capitolo. Tale logger è basato sul
collector di default specificato in configurazione e in generale può effettuare anche
logging classico. Esso è il logger più semplice da utilizzare, per tale motivo dovrebbe
essere il logger preposto ad essere combinato ad un approccio automatico di instrumentazione del codice sorgente mediante compilatori ad-hoc.
-
CreateUnreliableFailureDataLogger: In entrambe le versioni (specificando cioè l’ip
o l’host), questo metodo consente di creare un logger basato su un collector non affidabile, cioè con trasporto UDP. Ovviamente questa tipologia di logger, seppure abbastanza veloce nel consegnare i messaggi, non dà nessuna garanzia allo sviluppatore,
pertanto è consigliabile utilizzarlo solo nei casi in cui un’eventuale perdita di messaggi
non possa compromettere i risultati delle analisi dei monitor, oppure nei casi in cui si
abbia ragionevole certezza che la rete non possa perdere o corrompere i pacchetti.
-
CreateReliableFailureDataLogger: Consente di creare un logger basato su un collector affidabile, cioè con trasporto TLS. Ovviamente questa tipologia di logger, seppure
molto reliable grazie al protocollo TCP, ha un impatto sulle prestazioni del software
instrumentato molto più sensibile rispetto al corrispettivo basato su UDP. Pertanto è
consigliabile utilizzare questa tipologia di logger quando il sistema da analizzare non
ha requisiti di tempo stringenti (ad esempio non deve essere real time), oppure quando
si ha ragionevole certezza che i tempi di elaborazione standard del software siano di
molti ordini di grandezza superiori ai tempi di invio dei messaggi mediante l’API.
Come si può quindi capire dalla descrizione dei metodi della classe Helper, è possibile
istanziare un logger sia a partire dalla configurazione xml sia scegliendo in fase di creazione i parametri desiderati. E’ importante sottolineare che quando un logger viene istanziato per la prima volta, dalla factory o anche direttamente dallo sviluppatore, esso genera
u messaggio SUP, mentre, quando va in Garbage, il suo distruttore invia un evento di
SDW. Inoltre, come già detto in fase di configurazione, la possibilità di specificare un
heartbeat automatico, consente di monitorare lo stato operativo dell’entità quando essa non
sta inviando messaggi al Logbus (e quindi ai monitor in ascolto) rendendo facile
61
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
l’individuazione degli hang. La API lato sorgente deve anche fornire alcune agevolazioni
per l’analisi dei monitor FFDA, ovvero deve rendere disponibili, se possibile, informazioni aggiuntive alla semplice tipologia di evento osservato. In particolare, in accordo con
l’RFC5424 si osserva che:
-
Il Message ID deve sempre essere uguale ad FFDA.
-
La Facility deve essere Local0 di default, ma deve essere possibile cambiarla.
-
La Severity deve essere Info per tutti i messaggi eccezion fatta per quello di COA che
deve avere Severity pari ad Alert.
-
La Structured Data deve contenere un’entry CallerData con i campi ClassName, MethodName e Logger valorizzati, se possibile, rispettivamente con il nome della classe,
il nome del metodo chiamante e il nome del Logger che ha generato il messaggio.
Il campo Text deve contenere il tipo di messaggio (SST, SEN, etc.) e, se è presente un ID
del flusso di esecuzione, esso verrà riportato alla fine dopo il separatore “-“ (SST-102935).
Tutte queste funzionalità descritte forniscono un’enorme versatilità di utilizzo, rendendo
molto agevole, agli sviluppatori, l’integrazione della libreria con la loro applicazione, inoltre rendono possibile, come già osservato in precedenza, la realizzazione di compilatori instrumentanti in grado di inserire nei punti cruciali del codice sorgente gli eventi di log, sulla base delle regole definite nell’approccio Rule-Based. Passiamo adesso ad analizzare la
API lato monitor. Essa deve fornire una serie di funzionalità volte a rendere possibile la
detection di eventuali fallimenti avvenuti durante la fase di raccolta della FFDA. In altre
parole deve fornire gli strumenti per applicare le regole di alerting già proposte
nell’approccio Rule-Based descritto in precedenza. Di conseguenza, quando si vanno ad
analizzare i log, bisogna tenere conto che la coppia di messaggi SST / SEN relativi alla
stessa entità stanno a significare che il servizio invocato è terminato con successo, mentre
altre situazioni possono indicare un fallimento in accordo con la trattazione teorica delle
logging rules fatta nel capitolo terzo. Un’altra cosa importante da tenere a mente è quella
che i messaggi di log non sono ordinati temporalmente in base ai loro timestamp originali,
quindi l’unico modo per ordinarli lato client, ovvero una volta recapitati dal Logbus al
monitor, è quello di utilizzare le relazioni happened-before. Infatti, se le regole di logging
62
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
sono implementate come mostrato in precedenza, allora per ogni flusso di esecuzione valgono le seguenti considerazioni:
1) L’evento SST è precedente ad ogni altro messaggio.
2) L’evento EIS è precedente all’evento EIE.
3) L’evento RIS è precedente all’evento RIE.
4) L’evento EIS è precedente all’evento SST del metodo chiamato.
5) L’evento SEN è precedente all’evento EIE del chiamante.
Ovviamente, la API lato monitor deve necessariamente dare la possibilità agli sviluppatori
di tracciare con precisione i flussi di una specifica entità, pertanto, all’interno del core
principale del logbus, è prevista la presenza di uno speciale plugin (Entity Plugin) il cui
scopo è proprio quello di memorizzare tutte le entità attive nel sistema, creando canali appositi (diciamo pure dei canali monotematici) a cui un monitor, mediante la API deve poter iscriversi in modo da poter visionare tutte le attività svolte dalla entità di interesse. Anche la creazione del monitor prevede una fase d’inizializzazione che può essere fatta mediante file xml (si è scelto un meccanismo del tutto simile a quello già analizzato lato
source in modo da rendere molto omogenei i file di configurazione). Nel dettaglio, la configurazione della libreria, lato monitor, può essere la seguente:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="logbus-client" type="It.Unina.Dis.Logbus.Configuration.LogbusClientConfigurationSectionHandler,
It.Unina.Dis.Logbus, Culture=neutral, PublicKeyToken=9bbc6deeaedcd38f" />
</configSections>
<logbus-client xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:filter=”http://www.dis.unina.it/logbus-ng/filters”
xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
xmlns:config=”http://www.dis.unina.it/logbus-ng/configuration/2.0”>
xmlns=”http://www.dis.unina.it/logbus-ng/configuration/2.0”>
<endpoint subscriptionUrl="http://127.0.0.1:8065/LogbusSubscription.asmx"
managementUrl="http://127.0.0.1:8065/LogbusManagement.asmx" />
</logbus-client>
</configuration>
In questa configurazione appena riportata, è possibile subito notare come la sezione xml di
riferimento sia ora quella client e non più quella logger, in accordo con il fatto che, lato
monitor, non si è necessariamente interessati ad inviare al Logbus gli eventi di log (fermo
63
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
restando che è comunque possibile farlo inserendo la section analizzata in precedenza). I
parametri necessari alla configurazione, in questo caso, sono semplicemente gli url di riferimento del Web Service del Logbus, ovvero, allo stato attuale, è necessario conoscere
l’endpoint del server prima di lanciare il monitor. Come si può notare non sono stati definiti ulteriori entry all’interno del file xml, questo perché l’interazione con l’EntityPlugin e
la gestione delle sottoscrizioni ai canali delle entità sono operazioni molto più semplici da
effettuare via codice, inoltre le entità stesse potrebbero non essere note prima della esecuzione del monitor, rendendo impossibile la specifica dei filtri in questa fase. Una volta assicuratesi che la configurazione sia esatta, è possibile creare un monitor tramite il solito
meccanismo di factory, grazie alla seguente classe:
public class ClientHelper {
public static IChannelManagement CreateChannelManager ();
public static IChannelManagement CreateChannelManager (string endpointurl);
public static IChannelSubscription CreateChannelSubscriber ();
public static IChannelSubscription CreateChannelSubscriber (string endpointurl);
public static ILogClient CreateUnreliableClient (FilterBase filter, IChannelManagement mgr,
IChannelSubscription subscr);
public static ILogClient CreateUnreliableClient (string channelId, IChannelSubscription subscr);
public static ILogClient CreateUnreliableClient (string channelId);
public static ILogClient CreateUnreliableClient (FilterBase filter);
public static ILogClient CreateReliableClient (FilterBase filter, IChannelManagement mgr,
IChannelSubscription subscr);
public static ILogClient CreateReliableClient (string channelId, IChannelSubscription subscr);
public static ILogClient CreateReliableClient (string channelId);
public static ILogClient CreateReliableClient (FilterBase filter);
}
Anche in questo caso non si è ritenuto utile riportare le implementazioni dei metodi della
classe Helper, e ci si limiterà a fornirne una breve descrizione:
-
CreateChannelManager: Crea il proxy del manager del Logbus a partire dalla configurazione specificata (managementUrl) oppure a partire dall’endpoint passato come
parametro. Questo proxy implementa l’interfaccia WSDL presentata nel terzo capitolo.
64
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
-
CreateChannelSubscriber: Questo metodo crea il proxy del manager delle sottoscrizioni ai canali del Logbus a partire dalla configurazione specificata (subscriptionUrl)
oppure a partire dall’endpoint passato come parametro. Questo proxy implementa
l’interfaccia WSDL presentata nel terzo capitolo.
-
CreateUnreliableClient: Questo metodo, in tutte le sue versioni, crea un monitor che
riceve messaggi dal Logbus mediante trasporto inaffidabile (UDP). E’ possibile creare
un monitor del genere specificando alcuni parametri di configurazione come il filtro da
applicare agli eventi, il proxy per la sottoscrizione oppure l’id univoco del canale (qualora esista già). In questo modo sarà creata dalla factory un’istanza già pronta per ricevere dal bus gli eventi desiderati. E’ consigliabile utilizzare un monitor come questo
nei casi in cui l’analisi debba essere quanto più veloce è possibile (al limite in tempo
reale) e nei casi in cui la perdita di messaggi costituisce un rischio minimo o comunque accettabile ai fini dell’integrità dell’analisi da portare a termine.
-
CreateReliableClient: Questo metodo, in tutte le sue versioni, crea un monitor che riceve messaggi dal Logbus mediante trasporto affidabile (TLS). E’ possibile creare un
monitor del genere specificando alcuni parametri di configurazione come il filtro da
applicare agli eventi, il proxy per la sottoscrizione oppure l’id univoco del canale (qualora esista già). In questo modo sarà creata dalla factory un’istanza già pronta per ricevere dal bus gli eventi desiderati. E’ consigliabile utilizzare un monitor come questo
nei casi in cui l’analisi non abbia requisiti temporali molto stringenti (in quanto il trasporto TLS presenta un overhead considerevole specie per carichi molto elevati di
messaggi) e nei casi in cui la perdita di messaggi costituisce un rischio non accettabile
ai fini dell’integrità della analisi da effettuare.
Come si può quindi capire dalla descrizione dei metodi della factory client, è possibile
istanziare un monitor e creare contestualmente il canale di interesse specificandone il filtro, oppure, più semplicemente, qualora il canale fosse già presente e noto a priori (come
accade nel caso dei canali gestiti dall’EntityPlugin), è possibile referenziarlo dal monitor
appena creato. In ogni caso i monitor forniti dalla classe Helper rendono trasparente allo
sviluppatore il meccanismo di refresh della sottoscrizione (qualora la tipologia di trasporto
65
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
lo richiedesse, come accade per UDP), evitando così problemi legati alla scelta di timeout
più o meno stringenti e gestendo in automatico, laddove possibile, il recovery a caldo del
monitor in caso di perdita momentanea di connessione o di problemi transienti lato server.
4.2 Porting della API lato Sorgente in C
Per implementare nella maniera più veloce la API prima descritta in linguaggio C è stata
fatta la scelta di rendere molto più snella e semplice la struttura generale, rinunciando al
pattern factory ed adeguandosi ai differenti meccanismi offerti dal linguaggio, realizzando
una libreria in grado di fornire agli sviluppatori un logger basato su trasporto UDP (in teoria unreliable) ed un logger basato su memoria condivisa (in teoria reliable, anche se, per
quest’ultimo, si dovranno necessariamente fare alcune considerazioni particolari). Cominciamo con il presentare l’interfaccia della API:
// Definizione dei codici FFDA
#define SST "SST"
#define SEN "SEN"
#define EIS "EIS"
#define EIE "EIE"
#define CMP "CMP"
#define RIS "RIS"
#define RIE "RIE"
#define SUP "SUP"
#define SDW "SDW"
#define HB "HB"
// Definizione dei metodi per il logging
int LogFFDA(char* Logger, char* CodiceFFDA, char* NomeFunzione);
unsigned long int StartHeartbeat(char* Logger);
int StopHeartbeat(unsigned long int tid_heart);
void StopAllHeartbeat();
Si capisce subito che questa interfaccia è molto più semplice rispetto alla sua corrispettiva
scritta in C#, seppure offra sostanzialmente le stesse funzionalità. Come già detto sono state realizzate due implementazioni di questo header file, una basata su trasporto UDP ed
una basata su una memoria condivisa. Prima di affrontare nel dettaglio le differenze delle
due versioni della libreria, è importante soffermarsi su alcuni aspetti funzionali. Dato che
66
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
il linguaggio C, com’è noto, non supporta gli oggetti, i messaggi di life-cycle delle varie
entità non possono essere collocati nel costruttore / distruttore del rispettivo Logger, in
quanto non si dispone di tale meccanismo. Per ovviare a ciò, si consiglia di generare i
messaggi di SUP ed SDW rispettivamente prima delle invocazioni metodi di StartHeartbeat e StopHeartbeat, le chiamate ai quali devono essere fatte appositamente all’inizio ed
alla fine della vita della entità associata (ad esempio nel caso di una macro entità rappresentata da un’intera applicazione è necessario inserire le chiamate come prima ed ultima
istruzione della funzione main). Come già accade per la versione C#, gli heartbeat saranno
mandati ad intervalli regolari definiti dallo sviluppatore per informare i monitor dello stato
di una particolare entità, ed in generale sono inviati da un thread differente da quello principale (a priorità minore) in modo da non impattare il normale funzionamento del codice
instrumentato, anche perché gli heartbeat sono decisamente più utili quando l’applicativo è
in fase quiescente, pittuosto che durante le fasi di picco della sua attività. Sarà possibile
per lo sviluppatore controllare il singolo thread creato mediante il suo tid, in modo da poterlo fermare senza influenzare gli altri, oppure stoppare contemporaneamente tutti i
threads avviati mediante la chiamata all’apposita funzione StopAllHeartbeat, che, ovviamente, non prende in ingresso nessun parametro, risultando utile quando la funzione
che ha invocato StartHeartbeat non è la stessa che invocherà alla fine lo stop (si pensi ad
esempio ai demoni unix che dopo il main del processo padre fanno dei fork e quindi passano il controllo ai processi figli).
Un’altra cosa da tenere presente riguarda il formato dei messaggi di log generati da questa
API. Si è volutamente scelto di non usare il formato Syslog lato sorgente, ritenuto eccessivamente pesante per applicazioni procedurali (i pacchetti Syslog possono raggiungere dimensioni più o meno elevate a seconda del numero di informazioni contenute), optando
per una struttura decisamente più compatta e con dimensione massima fissata (ad esempio,
512 byte). Ovviamente lato server deve essere presente un canale in grado di effettuare la
conversione del formato scelto verso quello standard accettato dal Logbus, ma questo non
costituisce assolutamente un problema o una limitazione, in quanto abbiamo già detto nel
67
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
terzo capitolo che il bus progettato è in grado di essere esteso in maniera molto semplice al
fine di supportare qualsiasi tipo di protocollo e trasporto. Di seguito riportiamo la notazione utilizzata per i messaggi di Log generati:
Formato messaggio di Log:
[Host]|[Pid]|[Logger]|[#CodiceFFDA]|[NomeFunzione]|[SequenceId]
Esempio di messaggio:
MioHost|6123|Entity_1|SST|MiaFunzione|1
Come si evince facilmente, seppure molto più compatto, questo formato di messaggio garantisce la presenza di tutte le informazioni necessarie al Logbus per instradare il messaggio e tutte quelle necessarie ai monitor in ascolto per effettuare le analisi. Ovviamente le
informazioni mancanti necessarie ad ottenere la piena compatibilità con lo standard Syslog
5424, come ad esempio il timestamp, sono aggiunte da un canale di ingresso al Logbus al
momento della ricezione di ogni evento e prima di essere inoltrate nella pipe del bus. Questo canale, denominato ApacheInChannel deve essere dichiarato insieme al plugin nella
sezione core del file XML di configurazione principale del Logbus. Analizziamo adesso
nel dettaglio le due implementazioni realizzate a cominciare da quella unreliable basata su
trasporto UDP. I parametri di configurazione necessari in questo caso sono ovviamente
l’indirizzo ip del Logbus e il porto di ascolto su cui inviare i messaggi generati, inoltre è
possibile configurare anche il timeout per gli heartbeat, la dimensione massima dei messaggi di log (nel caso si ritenesse di poter sforare i 512 byte) e infine è possibile controllare il rate di invio dei pacchetti UDP per effettuare una sorta di shaping lato sorgente mediante una sleep di pochi microsecondi (ovviamente questo, oltre a rendere leggermente
più reliable il meccanismo di invio, può gravare sulle prestazioni generali del software instrumentato e pertanto è una strategia di cui non abusare). Per effettuare l’invio dei pacchetti si è scelto di creare e distruggere una socket client ad ogni chiamata di LogFFDA, in
modo da non causare problemi di concorrenza tra processi e/o thread in esecuzione, questo
ha ovviamente un prezzo in termini di overhead, ma consente di non ricorrere a meccanismi di lock e sincronizzazione che renderebbero l’invio decisamente più lento in caso di
68
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
frequente accesso contemporaneo alle risorse condivise (in questo caso le socket UDP).
Nel caso dei thread di heartbeat si è scelto di ricorrere ad una socket differente da quella
del logger, in quanto, in generale, la frequenza di accesso a tale risorsa non è elevatissima
e, comunque, la priorità dei messaggi di heartbeat è decisamente minore di quella degli
eventi generati dalle varie entità durante il loro ciclo di vita. Per quanto riguarda
l’implementazione basata su memoria condivisa, è necessario fare una premessa. Questo
tipo di soluzione richiede un deploy particolare dell’architettura del logbus, in altre parole
è necessaria la presenza di un componente logbus (che chiameremo forwarder), locale al
software instrumentato, in grado di utilizzare con quest’ultimo la memoria condivisa, ed in
grado, successivamente, di inoltrare (da qui il temine forwarder) al logbus remoto i pacchetti mediante protocollo TLS (ecco perché questa implementazione è definita come reliable, in quanto si basa su due meccanismi affidabili quali sono la memoria condivisa tra
l’applicativo e il forwarder ed il trasporto TCP tra il forwarder ed il Logbus principale). La
configurazione del forwarder è ovviamente basata su un file xml come il seguente:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="logbus-core" type="It.Unina.Dis.Logbus.Configuration.LogbusCoreConfigurationSectionHandler,
It.Unina.Dis.Logbus, Culture=neutral, PublicKeyToken=9bbc6deeaedcd38f" />
</configSections>
<logbus-core xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:filter=”http://www.dis.unina.it/logbus-ng/filters”
xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
xmlns:config=”http://www.dis.unina.it/logbus-ng/configuration/2.0”>
xmlns=”http://www.dis.unina.it/logbus-ng/configuration/2.0”>
<forward-to>
<forwarder config:type="SyslogTlsCollector">
<param config:name="host" config:value="RemoteLogbusHost"/>
</forwarder>
</forward-to>
<in-channels>
<in-channel config:type="ApacheInChannel.MemoryInChannel, ApacheInChannel" />
</in-channels>
</logbus-core>
</configuration>
Come si può osservare dalla configurazione riportata sopra, la sezione di interesse è adesso quella core, in quanto stiamo configurando un componente del Logbus-ng vero e proprio (anche se questo dovrà poi essere installato sullo stesso nodo del software instrumen69
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
tato). I parametri da specificare in questo caso sono fondamentalmente due: l’indirizzo del
Logbus remoto verso cui inoltrare i messaggi Syslog in TLS e il canale di ingresso del
forwarder, che in questo caso è di tipo MemoryInChannel, in quanto deve ricevere gli
eventi su memoria condivisa dalla API lato C e convertirli in formato 5424 prima di inviarli in remoto. Ovviamente, questa è solo una parte della configurazione complessiva, in
quanto è possibile, per gli sviluppatori, andare a settare alcuni parametri anche all’interno
della API sorgente come accadeva nel caso della versione UDP, ovvero è possibile personalizzare alcuni aspetti tra cui la dimensione della memoria condivisa, la dimensione massima di un messaggio di log e l’intervallo di heartbeat. Questo meccanismo di generazione
dei log è molto affidabile, in quanto c’è la garanzia che nessun messaggio di log generato
venga perso durante l’analisi (a patto di dimensionare i parametri in maniera corretta), tuttavia, presenta alcune limitazioni dal punto di vista temporale, specie quando il software
instrumentato ha una natura interna fortemente concorrente, dato che, tutti i meccanismi di
sincronizzazione necessari a garantire il corretto funzionamento della memoria condivisa
tendono a rallentare (non poco come vedremo in seguito) il funzionamento complessivo
del sistema. Tuttavia questa implementazione è molto più affidabile di quella basta su trasporto UDP, e in realtà, per software a basso carico o comunque di natura non fortemente
concorrente, presenta prestazioni decisamente ottimali, quasi comparabili con quelle ottenute monitorando un software non instrumentato, ma ciò verrà discusso nel sesto capitolo.
4.3 Realizzazione di un Monitor per la FFDA
Si presenta adesso l’implementazione di un semplice tool grafico di analisi realizzato in
perfetto accordo con le regole descritte durante il terzo capitolo. Questo monitor, sfruttando appieno le funzionalità del Logbus, sarà in grado non solo di ricevere e storicizzare tutti
gli eventi generati da una o più entità di un sistema, ma sarà soprattutto in grado di fornire
indicazioni precise sullo stato operativo delle entità stesse, generando alert in caso di anomalie riscontrate e permettendo all’analista di rintracciare con estrema facilità il metodo o
la funzione che sta riscontrando problemi, in modo da effettuare una sorta di debug distribuito del sistema monitorato (vedremo in seguito che un entità può essere definita in ma70
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
niera arbitraria dal progettista del sistema mediante l’apposito file di configurazione
dell’EntityPlugin situato nel core del Logbus). Cominciamo con il presentare brevemente
la configurazione del Logbus-ng necessaria ad effettuare la FFDA mediante il monitor: innanzitutto è fondamentale che nel Logbus venga installato l’EntityPlugin, ovvero il componente sviluppato per tenere traccia delle entità attive nel sistema. Tale plugin oltre a
memorizzare in una tabella tutte le entità, espone tutta una serie di metodi (di fatto è configurato per essere un servizio Web) necessari al monitor per eseguire una o più query sulla tabella del plugin, per individuare i canali a cui sottoscriversi per ricevere gli eventi generati dalle singole entità per effettuare la gestione del componente stesso, (ad esempio per
realizzare il clean-up delle entry inattive da un certo periodo di tempo). Di seguito si riporta il file xml di configurazione del Logbus core, in cui viene configurato anche il plugin:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="logbus-core" type="It.Unina.Dis.Logbus.Configuration.LogbusCoreConfigurationSectionHandler,
It.Unina.Dis.Logbus, Culture=neutral, PublicKeyToken=9bbc6deeaedcd38f" />
<section name="logbus-entityplugin" type="It.Unina.Dis.Logbus.Configuration.EntityPluginConfigurationSectionHandler,
It.Unina.Dis.Logbus, Culture=neutral, PublicKeyToken=9bbc6deeaedcd38f" />
</configSections>
<logbus-entityplugin xmlns="http://www.dis.unina.it/logbus-ng/entities">
<entity-key>
<field>host</field>
<field>logger</field>
</entity-key>
</logbus-entityplugin>
<logbus-core xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:filter=”http://www.dis.unina.it/logbus-ng/filters”
xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
xmlns:config=”http://www.dis.unina.it/logbus-ng/configuration/2.0”>
xmlns=”http://www.dis.unina.it/logbus-ng/configuration/2.0”>
<in-channels>
<in-channel config:type=" SyslogUdpReciver" >
<param config:name="port" config:value="7514"/>
<param config:name="receiveBufferSize" config:value="1048576"/>
</in-channel>
</in-channels>
<plugins>
<plugin config:type="It.Unina.Dis.Logbus.Entities.EntityPlugin, It.Unina.Dis.Logbus.Extensions, Culture=neutral,
PublicKeyToken=9bbc6deeaedcd38f"/>
</plugins>
</logbus-core>
</configuration>
Come si può capire dal contenuto della configurazione di esempio, la sezione del core è
configurata per ricevere messaggi su un canale UDP (il fatto non è molto rilevante adesso), ed è configurata per istanziare il componente EntityPlugin. Cosa molto interessante è
71
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
però la presenza di una nuova sezione del file dedicata al plugin delle entità, essa infatti
permette la definizione della struttura di una entità del sistema (di fatto consente di realizzare una modellizzazione del sistema stessa mediante la sua suddivisione a grana fine
quanto si desidera). Nell’esempio riportato un’entità è identificata dalla coppia Host e
Logger, in altre parole, se due applicativi in esecuzione sullo stesso nodo di rete utilizzano
lo stesso nome per il loro LoggerFFDA, essi saranno identificati dal plugin come la stessa
entità, mentre due classi di una stessa applicazione che utilizzano LoggerFFDA differenti,
possono essere considerate come entità distinte all’interno del sistema (è prevista attualmente una granularità personalizzabile fino al singolo metodo, in modo da consentire un
controllo dei flussi di esecuzione quanto più dettagliato possibile). Una volta configurato il
core, è possibile rivolgere l’attenzione al monitor FFDA. Esso viene semplicemente configurato mediante l’inserimento nel file xml degli url dei servizi del Logbus (così come descritto nel primo paragrafo del presente capitolo), tuttavia può essere interessante andare
ad analizzarne la struttura interna ed il funzionamento generale. Il tool realizzato è composto da una GUI principale in cui poter scegliere le entità da monitorare in base a dei filtri
standard o personalizzati o selezionandola da un’apposita lista fornita dall’EntityPlugin:
Fig. 4.1 – Scelta delle entità da monitorare.
Una volta scelte le entità da monitorare o comunque i filtri con cui scremare i messaggi in
arrivo dal Logbus, si potrà visualizzare il resoconto generale mediante una tabella riassun72
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
tiva dello stato generale del sistema in analisi. In questa tabella saranno riportati tutti gli
eventi di ogni singola entità, comprese le marche temporali e gli heartbeat, inoltre, in caso
di anomalie (anche temporanee) sono visualizzati tutti i messaggi di alert in accordo alle
regole descritte nel terzo capitolo. Di seguito si mostra un possibile resoconto del monitor
in assenza di errori per tutte le entità scelte nella schermata precedente:
Fig. 4.2 – Stato generale delle entità del sistema.
In questa finestra è possibile richiedere un aggiornamento real-time (ovvero ad ogni singola ricezione di un evento) oppure ad intervalli regolari(per applicazioni con vincoli temporali meno stringenti). Quando il monitor rileva una o più anomalie, esso le segnala modificando coerentemente lo stato delle entità (ultima colonna sulla destra), inoltre provvede a
colorare la riga della tabella in base alla tipologia di alert:
-
Viola: per segnalare la presenza di uno o più messaggi di CMP.
-
Rosso: per segnalare la presenza di uno o più messaggi di COA.
-
Giallo: per segnalare la presenza di uno o più messaggi di EIA
-
Verde: per segnalare la presenza di uno o più messaggi di RIA.
-
Blu: nel caso sia rilevata la perdita di uno o più pacchetti sulla rete (evento MISS).
-
Arancione: nel caso in cui non si riceva l’heartbeat di un’entità entro un certo timeout.
Ovviamente l’alert che si riferisce alla mancanza di heartbeat è legato al fatto sia che
l’entità sia stata dotata di tale meccanismo (dalla figura di evince che le entità monitorate
non ne hanno inviato nessuno in quanto la colonna relativa è valorizzata ad una timestamp
73
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
di default), sia al fatto che l’entità stessa non sia più attiva da un certo periodo ti tempo (è
inutile infatti controllare l’heartbeat di una entità se questa produce attivamente i messaggi
di log, perché evidentemente non può essere andata in crash). E’ importante notare inoltre
che le colonne che si riferiscono agli eventi di Startup e Shutdown rendono molto semplice la stima dell’uptime e del downtime del sistema nonché l’individuazione dei reboot del
software: se le colonne hanno valore uguale si parla di clean reboot, viceversa si è in presenza di un riavvio dirty. Facendo doppio click su una qualsiasi delle righe si possono ottenere i dettagli che si riferiscono ai metodi (o funzioni, a seconda del linguaggio), ovvero
è possibile capire, in caso di errore, quali sono i punti del codice instrumentato che stanno
sperimentando difficoltà, consentendo una sorta di debug distribuito. Questo strumento di
fatto consente agli sviluppatori e agli analisti, di individuare con estrema facilità non solo
le entità che non stanno funzionando a dovere (ovvero che si trovano nello stato BAD del
loro ciclo di vita), ma anche di comprendere in quale punto preciso del codice sorgente intervenire per trovare e quindi risolvere il problema riscontrato (ovviamente maggiore è la
granularità dell’instrumentazione e maggiore sarà il suo impatto sulle prestazioni del software). Di seguito si riporta il dettaglio dell’entità LoggerA presente nella schermata presentata in precedenza, nel caso in cui non si siano riscontrati problemi di nessun tipo:
Fig. 4.3 – Dettaglio relativo ad una delle entità monitorate.
Anche in questa schermata vengono utilizzate le stesse regole di segnalazione di errore
della schermata precedente, ovvero si utilizzano gli stessi colori per le singole righe e la
74
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
stessa semantica per la colonna di stato (ovviamente, la somma degli stati dei singoli metodi, nel nostro modello, deve restituire sempre lo stato generale della entità del sistema di
cui i metodi fanno parte). Nel caso, ad esempio, in cui uno dei metodi di un’entità monitorata generi un Complaint, due Computation Alert, due Entity Interaction Alert ed un Resource Interaction Alert, la riga ad esso corrispondente sarà colorata di Viola (poiché un
CMP, essendo inserito dallo sviluppatore per segnalare un fallimento di valore, è considerato sempre più grave dei fallimenti di timing, ovvero quelli segnalati dagli eventi di COA,
EIA e RIA), mentre la colonna di stato del metodo stesso sarà valorizzata dall’interfaccia
grafica del monitor mediante la seguente dicitura:
•
CMP - 1 | COA - 2 | EIA - 2 | RIA - 1
Questo strumento appena descritto verrà utilizzato in seguito durante il caso di studio presentato nel corso del sesto ed ultimo capitolo del presente lavoro di tesi, e ci consentirà di
arrivare ad un log FFDA molto più semplice da consultare in quanto privo di tutti gli eventi superflui e contenente solo le entry di alert delle singole entità monitorate. In questo
modo le effettive misure di dependability di un sistema potranno essere realizzate con un
effort di tempo notevolmente inferiore rispetto a quello richiesto dagli attuali approcci
proposti in letteratura. Il monitor consente all’analista di visualizzare nel log filtrato anche
eventi di failure più gravi dei COA e degli EIA, quali possono essere i Crash o i fallimenti
della rete. Infatti, al verificarsi di un HB_MISS da parte di un’entità, il tool provvede da
solo a eseguire il ping del nodo di rete in cui l’entità stessa risiede per capirne il problema:
<134>1 2010-11-24T16:29:38+01:00 - Monitor - FFDA - Entity: HostA-ClassA SUP
<134>1 2010-11-24T16:29:58+01:00 - Monitor - FFDA - Entity: HostB-ClassB SUP
<129>1 2010-11-24T16:30:08+01:00 - Monitor - FFDA - Entity: HostB-ClassB PING_FAILED
<134>1 2010-11-24T16:31:58+01:00 - Monitor - FFDA - Entity: HostA-ClassA SDW
<134>1 2010-11-24T16:32:28+01:00 - Monitor - FFDA - Entity: HostB-ClassB SDW
Questo log file altro non è che il report Rule Based. Grazie ad esso capiamo subito che
HostB è andato offline per un certo periodo e in seguito è tornato disponibile, permettendo
all’entità in esecuzione su di esso di terminare correttamente le sue operazioni.
75
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
5. Un caso di studio: Apache Web Server-1.3.41
L’ultima fase del presente lavoro di tesi è stata incentrata sullo studio di un caso reale. In
particolare, si è voluta testare l’efficacia degli strumenti sviluppati al fine di effettuare la
Field Failure Data Analysis di un sistema di larga diffusione quale è sicuramente il web
server fornito dalla Apache Software Foundation[20]. Dato che questo software non è stato scritto da sviluppatori Logbus aware, si è resa necessaria una fase preliminare di adattamento del codice sorgente originale in modo che potesse essere integrato con la API descritta ed implementata in linguaggio C nel capitolo precedente. In sostanza è stata realizzata un’instrumentazione del codice, di cui si parlerà nel prossimo paragrafo, in modo da
poter raccogliere tutti gli eventi di log descritti dalle regole di logging, in seguito questi
eventi sono stati inoltrati sul Logbus e quindi analizzati mediante il monitor FFDA progettato e realizzato in precedenza. Sono stati quindi raccolti dati sull’impatto della API utilizzata sulle prestazioni del web server e si sono infine osservati alcuni risultati sperimentali
sull’efficacia pratica delle regole di logging descritte al capitolo terzo iniettando dei guasti
controllati all’interno del codice ed analizzando i log finali prodotti dal monitor.
5.1 Instrumentazione del codice sorgente
La fase d’instrumentazione del codice sorgente di Apache Web Server è seguita ad una fase di profilazione del codice sorgente dello stesso software mediante il software gcov[21].
In altre parole si sono individuate le entità da sottoporre al monitoraggio e si sono individuate le funzioni di tali entità da associare a servizi, interazioni e risorse del modello proposto nel terzo capitolo. In pratica per ottenere il profiling è necessario compilare Il codice
76
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
con dei flag speciali (‐fprofile, ‐arcs, ‐ftest, ‐coverage). In seguito viene eseguito il programma ed in automatico vengono creati dei file che contengono le informazioni sulla
profilazione (contenute in dei file .gcda, .gcno). Lanciando il comando gcov –f nome_file.c è anche possibile ottenere una versione facilmente interpretabile delle suddette
informazioni. Alla fine, il file che si ottiene (nome_file.c.gcov) è uguale al sorgente originale, però contiene delle informazioni aggiuntive. Nel nostro caso di studio (Apache Web
Server-1.3.41), sotto la directory /src/main sono presenti i principali sorgenti, supponiamo
quindi che si vogliano modellare come entità logiche solo alcuni di essi. In particolare ne
andiamo a considerare sei:
-
http_config.c: modella la nostra prima entità.
-
http_core.c: modella la nostra seconda entità.
-
http_main.c: modella la nostra terza entità.
-
http_protocol.c: modella la nostra quarta entità.
-
http_request.c: modella la nostra quinta entità.
-
http_vhost.c: modella la nostra sesta entità.
Per ottenere i servizi che sono effettivamente invocati in una certa entità (supponiamo di
considerare http_core.c), è sufficiente prendere il relativo file gcov (ad esempio
http_core.c.gcov) ed estrarre le righe che hanno function called > 0 (per fare ciò basta fare
il cat del file, filtrare il risultato con una regular expression e stampare i nomi delle funzioni con awk). Ripetendo questo procedimento per ogni entità, le funzioni ottenute saranno instrumentate con le regole SST - SEN. Per le interazioni EIS - EIE il discorso è leggermente più complesso (ma non tantissimo). Prima di tutto, per un fatto di comodità,
supponiamo esista un’entità logica che contiene tutti i file che non sono stati associati a
nessuna entità nello step precedente. Le informazioni che ci servono ora sono: lista dei
servizi per ogni entità, sorgenti relativi ad ogni entità (per vedere se da questi parte
un’invocazione verso un determinato servizio). Un esempio, a questo punto, dovrebbe
chiarire tutto. Supponiamo di voler capire se l’entità A (file: srcA.c, servizi s1_A, s2_A,
s3_A) invoca qualche servizio di B (file: srcB.c, servizi, s1_B, s2_B, s3_B). Per fare ciò,
per ogni servizio di B, è necessario fare un cat/grep sul sorgente di A. Facendo in questo
77
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
modo, si riescono a ricavare tutte le interazioni cercate.
Per il caso di studio d’interesse (Apache Web Server-1.3.41), ad esempio, è possibile ottenere una situazione del genere:
==== interazioni dalla seconda entità verso la prima ====
ap_create_per_dir_config 3 ______________________ http_core.c
ap_find_linked_module 1 ______________________ http_core.c
ap_srm_command_loop 4 ______________________ http_core.c
ap_server_root_relative 2 ______________________ http_core.c
ap_process_resource_config 3 ______________________ http_core.c
==== interazioni dalla terza entità verso la prima ====
ap_log_transaction 1 ______________________ http_main.c
ap_setup_prelinked_modules 2 ______________________ http_main.c
ap_read_config 5 ______________________ http_main.c
ap_init_modules 7 ______________________ http_main.c
ap_child_init_modules 3 ______________________ http_main.c
ap_child_exit_modules 3 ______________________ http_main.c
…
Lo snippet sopra riportato riguarda solo le interazioni dalla seconda e dalla terza entità
verso la prima. Tutto il report completo è ovviamente molto più lungo, in quanto sono presenti anche le interazioni della quarta entità verso la prima, della quinta verso la prima,
etc. E’ possibile vedere questo report come una matrice quadrata delle interazioni, in quanto ogni entità scelta invoca servizi di tutte le alter entità del sistema. Tornando all’ultima
entità logica (quella che contiene tutti i file non modellati).
Se la diamo in pasto
all’algoritmo che trova le interazioni come fosse una normale entità, siamo in grado di
trovare le interazioni che “escono” dalle entità del modello verso l’esterno. Queste ultime
devono essere sicuramente instrumentate. Ovviamente non possiamo instrumentare le interazioni che dall’esterno entrano nel modello), siccome significherebbe instrumentare del
codice esterno a quello d’interesse, il che, oltre a non essere necessario, può risultare del
tutto impossibile nel caso di componenti proprietari o ad esempio di servizi web remoti. In
78
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
questa instrumentazione non sono state rilevate interazioni di entità con risorse del sistema
da segnalare con i codici RIS –RIE, pertanto non sono stati inseriti i relativi eventi in nessuna riga di codice. Per completare il discorso, è necessario affrontare la problematica legata agli eventi di SUP ed SDW, nonché dell’inserimento dei thread di heartbeat per le entità da monitorare. Per quanto riguarda gli eventi di startup, si è ritenuto utile collocarli
tutti all’avvio della funzione main del server (di fatto sono stati inseriti tanti messaggi
quante sono le entità modellate come prima istruzione del programma, in modo che fossero ragionevolmente i primi messaggi ad essere inviati al Logbus), in accordo con quanto
detto nello scorso capitolo. I messaggi di SDW (devono essere comunque sei), sono stati
inseriti, come ultime istruzioni, nella funzione handler del SIGTERM, ovvero quella che
gestisce per conto dell’applicazione, il segnale di terminazione inviato dal sistema operativo (di fatto quello scatenato quando si digita Ctrl+C sulla console). In questo modo si è sicuri che essi siano inoltrati al Logbus durante la fase di uscita “pulita” del programma (è
evidente che un segnale di SIGKILL non scatenerà l’invio questi messaggi, rendendo così
possibile la simulazione di un fallimento per crash del server). Infine, la questione degli
heartbeat è stata affrontata mediante la creazione di sei distinti thread che in parallelo
all’esecuzione normale del software provvedono ad inviare periodicamente al Logbus i
messaggi indicativi dello stato vitale delle entità (ecco perché ne sono stati utilizzati sei).
Va comunque notato che, di fatto, sarebbe bastato anche un solo thread concernente la
macro entità Apache, visto che, le singole entità del modello, non possono esistere o incorrere in un crash senza che esista o vada in crash il loro container principale. Alla fine
dell’esecuzione del server si è provveduto ad inserire la chiamata di stop dei threads avviati (di fatto tale chiamata segue quelle di SDW delle entità nella funzione di uscita prima citata), anche se, tale operazione può risultare comunque superflua, in quanto, alla terminazione del processo principale, sarebbero terminati comunque tutti i thread da esso lanciati.
79
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
5.2 Overhead introdotto dallʼinstrumentazione
In seguito alla fase d’instrumentazione del codice sorgente, sono stati realizzati alcuni test
per verificare l’impatto dell’infrastruttura di logging sul server Apache. In altre parole si è
cercato di capire quanto il processo di analisi inficiasse le normali prestazioni del sistema,
in più si sono confrontate le prestazioni ottenute con entrambi gli approcci implementati
nella libreria C, ovvero quello unreliable basato su UDP e quello reliable basato su memoria condivisa e TLS. E’ giusto premettere che tutte le misurazioni sono state realizzate mediante un tool di testing di larga diffusione quale è Jmeter[22]. Questo strumento, disponibile per piattaforma Java, permette la creazione di scenari di test molto articolati grazie
alla presenza di moltissimi elementi completamente configurabili quali scheduler di thread
per richieste concorrenti, gestori dei task da eseguire a ogni richiesta, timer per creare distribuzioni temporali di vario tipo (uniformi, gaussiane, etc.). Lo stesso jmeter permette di
raccogliere dati molto utili a ogni esecuzione dei test, infatti, è possibile conoscere i ritardi
di elaborazione per ogni singola richiesta, la media e la varianza di queste ultime e persino
il throughput con il quale il software testato è riuscito a gestire le richieste inviategli. Tutti
i valori temporali ottenuti mediante quest’analisi sono espressi in millisecondi mentre il
throughput è calcolato come richieste al secondo servite. Cominciamo con il mostrare il
grafico relativo al throughput ottenuto con una versione di Apache non instrumentata:
Fig. 5.1 – Throughput del server non instrumentato.
Come si evince facilmente dal grafico, il Web Server riesce a servire tutte le richieste pervenute nel tempo voluto, in altre parole al crescere delle richieste al secondo, esso non
80
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
presenta difficoltà a servirle tutte se non quando queste sono superiori a 450, che, di fatto,
rappresenta una sorta di valore di saturazione delle prestazioni del software, e pertanto può
essere considerato come fondo scala di riferimento per tutte le altre misurazioni che verranno proposte in seguito, in quanto a partire da quel volume di richieste al secondo le
prestazioni del server degradano a prescindere dal fatto che esso venga o meno instrumentato e quindi non sono di interesse per i nostri scopi. Un altro grafico molto interessante,
sempre relativo alla versione di Apache non instrumentata, è quello che mostra i ritardi
con il quali il server riesce ad evadere le richieste inviate al crescere del numero stesso di
richieste al secondo, in modo da avere poi un’idea tangibile di quanto la nostra instrumentazione incide sulla percezione che hanno gli utenti delle performance del sistema:
Fig. 5.2 – Ritardo medio del server non instrumentato.
Come si poteva già intuire, il ritardo medio che un utente percepisce durante una richiesta
web aumenta sensibilmente in base al numero totale di richieste che il server deve evadere
contemporaneamente. Il dato interessante è che questi ritardi sono molto contenuti
(nell’ordine massimo dei 100 ms) fintanto che le richieste al secondo si mantengono al di
sotto di 300, mentre si ha un aumento repentino della pendenza del grafico al di sopra di
questa soglia. Ciò sta a indicare che i processi generati da Apache per servire le richiesta
cominciano ad essere così tanti da raggiungere il limite del trade-off concorrenza/ritardo,
in altre parole lo scheduler di sistema comincia ad incidere in maniera più significativa sui
81
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
tempi di attività dei singoli processi di servizio, portando ad un’inevitabile degradazione
delle prestazioni complessive del sistema. Questi grafici ci servono come riferimento per
valutare l’impatto delle due implementazioni della API C sul comportamento del server.
E’ anche giusto premettere che le architetture delle macchine sulle quali sono stati eseguiti
questi test non sono assolutamente pensate per gestire workloads importanti, pertanto tutti
i risultati ottenuti migliorerebbero sensibilmente se si potesse disporre di server fisici con
più interfacce di rete, più processori e più memoria RAM. Detto ciò, presentiamo il grafico relativo ai ritardi del server instrumentato mediante la API unreliable basata su UDP:
Fig. 5.3 – Ritardo medio del server instrumentato (tecnica UDP).
Come era ampiamente prevedibile, anche in questo caso il grafico dei ritardi subisce un
aumento repentino della pendenza a partire dal workload di 300 richieste al secondo, in
perfetta sintonia con quanto accadeva nel caso non instrumentato, poiché molto probabilmente questa è una peculiarità della architettura interna del Web Server, e non un comportamento anomalo dovuto all’invasività della tecnica di instrumentazione, che tuttavia incide mediamente sui ritardi complessivi del software. Questa è sicuramente un risultato molto positivo, poiché dimostra che la API basata sul trasporto UDP quantomeno non altera
massicciamente il comportamento generale del sistema ma al massimo lo può far discostare leggermente da quello che abbiamo osservato nel caso precedente.
82
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Più interessante è sicuramente il grafico riguardante il throughput del server instrumentato
con la medesima tecnica:
Fig. 5.4 – Throughput del server instrumentato (tecnica UDP).
Dalla figura riportata si vede chiaramente che il software instrumentato riesce a tenere il
passo delle richieste sempre crescenti fino ad un workload di 60 al secondo (di fatto molto
inferiore a quello limite del caso non instrumentato), dopodiché le prestazioni del server
cominciano a degradare sensibilmente. Questo è dovuto molto probabilmente al fatto che,
per ogni richiesta, il server instrumentato deve effettuare numerose chiamate di log e questo incide sul numero massimo di richieste al secondo che può servire. Va anche detto che
la grana dell’instrumentazione utilizzata è volutamente finissima proprio per mettere a dura prova il sistema, e che, quindi, riducendo sensibilmente il numero di log da inviare per
ogni singola richiesta, si avrebbero risultati notevolmente migliori di quelli ottenuti finora.
Passiamo ora ad analizzare i risultati ottenuti con la API implementata mediante la memoria condivisa ed il forwarder TLS locale al server instrumentato. E’ doveroso fare una
premessa molto importante: questo tipo di implementazione è molto reliable, pertanto per
definizione si presta ad avere un impatto decisamente maggiore di quello visto con la tecnica UDP. Inoltre questo tipo di soluzione è fortemente soggetta a problemi di concorrenza, in quanto la memoria condivisa altro non è che una semplice coda circolare statica in
cui uno o più produttori (in questo caso i processi generati da Apache) vanno ad inserire le
entry di log. Pertanto quando il numero di processi e/o threads che fanno accesso alla coda
83
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
diventa grande, i meccanismi di sincronizzazione basati su lock diventano un serio collo di
bottiglia per le prestazioni del software e questo emergerà chiaramente nei grafici successivi. Riportiamo di seguito il grafico relativo ai ritardi ottenuti con la tecnica reliable:
Fig. 5.5 – Ritardo medio del server instrumentato (tecnica a memoria condivisa).
Si vede chiaramente che i ritardi ottenuti sono enormemente più elevati rispetto ai casi
analizzati in precedenza, e questo è assolutamente giustificato dai problemi di concorrenza
prima citati, che, nel caso specifico di Apache sono fortemente esacerbati, come è anche
visibile nel grafico relativo al throughput del server instrumentato con la stessa tecnica:
Fig. 5.6 – Throughput del server instrumentato (tecnica a memoria condivisa).
In questo caso è ancora più evidente che i risultati ottenuti mostrano un impatto a dir poco
disastroso della tecnica di instrumentazione. Vale anche in questo caso il discorso della
grana eccessiva utilizzata, tuttavia la degradazione e tale da suggerire una totale inadegua84
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
tezza dell’implementazione a memoria condivisa per questa tipologia di software così
concorrente e così repentina nel loggare le sue attività. Infatti, per bassissimi carichi di lavoro del server, l’instrumentazione garantisce una buona velocità tutto considerato il grado
di sicurezza e affidabilità che offre la soluzione, tuttavia è inutile dire che non è accettabile
che una tecnica di logging o di analisi sia così invasiva e soprattutto così deleteria per le
prestazioni del software instrumentato. Si riportano di seguito alcuni grafici di confronto
delle soluzioni in modo da evidenziarne il contributo nella degradazione delle prestazioni:
Fig. 5.7 – Confronto tra i ritardi senza API e con tecnica UDP.
Fig. 5.8 – Confronto tra i ritardi senza API e con tecnica a memoria condivisa.
La differenza è abissale, mentre nel primo caso si parla di uno massimo due ordini di
grandezza, nel secondo caso si è di fronte ad una situazione di palese peggioramento delle
85
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
attese delle singole richieste, tanto che il grafico di confronto mostra una curva con un andamento più che lineare (la memoria condivisa) ed una curva praticamente piatta rispetto a
quella precedente (il server senza API), proprio a testimoniare l’inadeguatezza della tecnica nel contesto operativo del Web Server. Ancora più significativo è il confronto riassuntivo dei throughput relativi alle tre situazioni presentate in precedenza:
Fig. 5.9 – Confronto tra i throughput del server con e senza instrumentazione.
Ancora una volta è palese come la soluzione a memoria condivisa non sia applicabile al
caso di studio trattato, inoltre si evince chiaramente il punto di rottura in cui il comportamento del server instrumentato diverge significativamente da quello senza API. Infatti,
superate le 60 richieste al secondo il contributo della libreria di logging diviene tale da
impedire al server di reagire come dovrebbe, degradando conseguentemente le prestazioni
del sistema. Come detto in precedenza questi risultati tendono a migliorare in caso di architetture più performanti e instrumentazioni meno corpose del software, tuttavia è anche
pensabile di intervenire sulla struttura della API cercando i individuarne i colli di bottiglia
ed i possibili punti di rallentamento, in modo da risparmiare significativamente tempo durante le numerose operazioni di logging, che poi di fatto sono quelle che maggiormente
tendono a rallentare complessivamente il sistema in analisi.
86
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
5.3 Validazione dellʼapproccio Rule-Based
Andiamo adesso a presentare dei risultati sperimentali ottenuti utilizzando il tool di analisi
FFD descritto e realizzato nel precedente capitolo. Cominciamo con il dire che saranno
mostrati solo alcuni casi di alert generati dal monitor, in quanto non è possibile presentare
l’intera copertura effettuata sul software. In pratica si prenderanno ad esempio delle particolari iniezioni di fault in punti del Web Server interni ed esterni al modello realizzato nel
primo paragrafo, ottenendo così un idea di come l’approccio Rule Based realizzato è efficace nel rilevare anomalie del software quali fallimenti di timing, hang e crash. Prima di
tutto presentiamo uno scenario di corretto funzionamento di Apache, ovvero ne eseguiamo
lo startup e lo shutdown pulito andando ad osservare quanto riportato dal monitor:
Fig. 5.10 – Startup e shutdown puliti del web server Apache.
Come si può vedere dalla figura lo stato di tutte le entità è “OK” e nessuna riga riporta colori di alerting, inoltre, le colonne Startup e Shutdown contengono gli stessi valori, proprio
ad indicare che si ha avuto un’esecuzione ed una terminazione normali senza nessun riavvio (né clean né tantomeno dirty). Il conteggio dei messaggi durante le fasi di avvio e terminazione pulita del server hanno mostrato, durante gli esperimenti effettuati, un carattere
deterministico del server, il che rende possibile una sorta di profilazione del comportamento del software durante le normali fasi di esecuzione. Questa caratteristica del monitor,
seppure non esplicitamente definita come requisito funzionale, è incredibilmente utile ed
apre scenari di utilizzo del Logbus, e quindi delle sue API, non necessariamente contemplati nell’approccio Rule Based, essendo volto questo principalmente al supporto della
Field Failure Data Analysis. Come primo scenario di fallimento è interessante mostrare un
87
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
hang del server ottenuto mediante l’iniezione di un guasto all’interno di un file appartenete
al modello, precisamente http_protocol.c (la quarta entità), che si attiva ad ogni richiesta
HTTP effettuata da un client verso la porta 8080:
Fig. 5.11 – Hang rilevato in seguito ad un fault in un file interno al modello.
Come si evince dalla figura, il monitor ha sollevato alcuni alert non solo nel modulo in cui
era stato iniettato il fault, ma anche in altri due file del modello, ovvero http_main.c e
http_request.c a riprova della presenza di un fenomeno di propagazione del guasto. In questo specifico caso la catena guasto-fallimento è cominciata da una funzione di http_main.c
che richiama una funzione di http_request.c che a sua volta richiama quella di
http_protocol.c in cui è stato inserito il fault (nel dettaglio si tratta della funzione
ap_send_http_header). Il dettaglio dei metodi relativi alla entità http_protocol.c mostra
proprio quali metodi hanno avuto un fallimento, rendendo semplicissimo per uno sviluppatore agire sul codice al fine di isolare e correggere la root cause, permettendo un utilizzo
del tool ai fini di un semplice debug post esecuzione del software. Un altro risultato interessante si è avuto in corrispondenza di un hang causato da un fault iniettato nel file buff.c
esterno al modello. Infatti, anche se non instrumentato, questo file propaga l’errore iniettato verso il modello rendendo possibile al monitor FFDA la detection del fallimento del
server. Questo è importante poiché ci mostra come, anche non eseguendo una instrumentazione completa del codice sorgente (che spesso oltre ad essere difficile è proprio impossibile) si riesce ad ottenere una copertura dei failure anche se questi si verificano in punti
del codice di cui non ci si era preoccupati in fase di profiling del software o in componenti
88
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
magari di terze parti di cui non si possedevano affatto i sorgenti al momento della modellizzazione del sistema in entità. Si riporta di seguito quanto rilevato dal tool in questo specifico caso di fallimento:
Fig. 5.12 - Hang rilevato in seguito ad un fault in un file esterno al modello.
La figura mostra appunto che l’hang avutosi a causa del fault iniettato nel file buff.c ha
portato ad una serie di COA ed EIA di tre funzioni instrumentate del modello (nello specifico si è avuto un fallimento delle entità http_request.c, http_main.c e http_config.c). Si
mostra ora un esempio di crash rilevato dal monitor in seguito ad una iniezione di un fault
nel file http_protocol.c appartenete al modello:
Fig. 5.13 - Crash rilevato in seguito ad un fault in un file interno al modello.
In questo caso si vede subito che tutte le entità hanno avuto un alert di tipo Heartbeat Missing, coerentemente con il fatto che il loro Container (il web server Apache) è terminato in
modo anomalo (si può anche evincere dal fatto che si hanno i SUP ma non gli SDW), inol89
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
tre una delle entità, nello specifico http_protocol.c (quella in cui è stato iniettato il fault)
ha generato anche due CMP nei metodi in cui si è verificato l’errore fatale al server. Ancora più interessante, tuttavia, è il caso di un crash dovuto ad un fault inserito in un file
esterno al modello, in quanto è rappresentativo della reale efficacia del tool realizzato: se
riusciamo infatti a rilevare crash in punti non instrumentati, allora abbiamo ottime speranza che anche i crash dovuti a cause esterne al software (perdita di connessione alla rete,
esaurimento della memoria, accesso non riuscito al FileSystem, etc.) possano essere intercettati con l’approccio proposto:
Fig. 5.14 - Crash rilevato in seguito ad un fault in un file esterno al modello.
Nell’esempio presentato il fault era stato inserito nel file alloc.c (esterno al modello), ma il
failure si è propagato sino alle entità instrumentate causando una serie di 3 COA in
http_config.c (di cui si è visualizzato il dettaglio dei metodi) ed un EIA in http_main.c
prima ancora di causare il crash dell’intero server rilevato comunque dal monitor mediante
i sei avvisi di Heartbeat Missing (colore arancione) associati alle singole tuple visualizzate
nella tabella. E’ evidente che anche in questo caso la colonna degli shutdown non ha lo
stesso valore di quella degli startup in quanto il crash è una modalità di terminazione non
gestita e quindi non porta alla produzione degli eventi SDW: questo causerà al prossimo
avvio del software una valorizzazione della colonna dei SUP a 2 lasciando quella degli
SDW a 0. Tale informazione renderà palese il fatto che si è avuto in sostanza un riavvio
del sistema anomalo e quindi una alert di dirty reboot. Può essere interessante adesso mostrare il report Rule Based generato dal monitor FFDA durante la rilevazione del crash
90
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
prima riportato, in modo da rendere ancora più evidente l’utilità di questo strumento ai fini
della semplificazione del processo di analisi:
<134>1 2010-11-24T16:29:58+01:00 - Monitor - FFDA - Entity: Host-http_main SUP
<134>1 2010-11-24T16:29:58+01:00 - Monitor - FFDA - Entity: Host-http_config SUP
<134>1 2010-11-24T16:29:58+01:00 - Monitor - FFDA - Entity: Host-http_protocol SUP
<134>1 2010-11-24T16:29:58+01:00 - Monitor - FFDA - Entity: Host-http_request SUP
<134>1 2010-11-24T16:29:58+01:00 - Monitor - FFDA - Entity: Host-http_core SUP
<134>1 2010-11-24T16:29:58+01:00 - Monitor - FFDA - Entity: Host-http_vhost SUP
<129>1 2010-11-24T16:30:08+01:00 - Monitor - FFDA - Entity: Host-http_main EIA
<129>1 2010-11-24T16:30:08+01:00 - Monitor - FFDA - Entity: Host-http_config COA
<129>1 2010-11-24T16:32:08+01:00 - Monitor - FFDA - Entity: Host-http_main CRASHED
<129>1 2010-11-24T16:32:08+01:00 - Monitor - FFDA - Entity: Host-http_request CRASHED
<129>1 2010-11-24T16:32:08+01:00 - Monitor - FFDA - Entity: Host-http_protocol CRASHED
<129>1 2010-11-24T16:32:08+01:00 - Monitor - FFDA - Entity: Host-http_core CRASHED
<129>1 2010-11-24T16:32:08+01:00 - Monitor - FFDA - Entity: Host-http_vhost CRASHED
<129>1 2010-11-24T16:32:08+01:00 - Monitor - FFDA - Entity: Host-http_config CRASHED
Com’era facile immaginare, il file di log prodotto è del tutto analogo alla screenshot mostrata in precedenza, con l’unica differenza che, in caso di riavvio del sistema o comunque
in caso di recovery del server, la GUI utente tenderà a evolvere di nuovo verso lo stato
corrente del software perdendo traccia del fallimento (essa, infatti, mira a una real-time detection), mentre nel file di log resteranno tracciate tutte le anomalie riscontrate vola per
volta dal tool di analisi.
Per concludere questa fase di sperimentazione dell’approccio Rule Based va detto che non
tutti i fault introdotti nel sistema sono stati rilevati, anche se la percentuale di falsi negativi
è stata inferiore al 5% e, generalmente tutta localizzata in file non appartenenti al modello,
in quanto è praticamente impossibile avere un fault in una funzione instrumentata senza
averne riscontro nell’interfaccia grafica o quantomeno nel file di log prodotto.
91
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
5.4 Conclusioni e sviluppi futuri
Il presente lavoro di tesi è stato svolto con lo scopo di proporre e realizzare un nuovo approccio al logging mirato a supportare la Field Failure Data Analysis. Ovviamente questo
risultato finale è stato conseguito grazie al passaggio tra varie fasi intermedie di rilevante
importanza.
Il primo passo è stato quello di effettuare un attento studio degli attuali framework di logging disponibile in commercio, successivamente, dopo aver esposto i principali limiti delle
classiche tecniche di logging ai fini della FFDA, si è passati a proporre un nuovo approccio basato sulla definizione di un modello rappresentativo del sistema da analizzare e la
descrizione di alcune semplici regole da seguire in fase di logging.
In seguito si è proposta una strategia pratica per l’implementazione delle suddette regole
nei moderni linguaggi di programmazione e si è presentata una piattaforma chiamata
Logbus-ng volta alla raccolta ed elaborazione dei log in modo da rendere possibile e supportare la FFDA log-based.
Il passo successivo è stato quello di realizzare una API, compatibile con l’infrastruttura
Logbus, che rendesse possibile agli sviluppatori realizzare l’approccio Rule Based durante
le classiche fasi di logging del software. Utilizzando questa stessa API è stato anche realizzato un tool di analisi chiamato monitor in grado di rilevare anomalie nel funzionamento del software sotto osservazione praticamente in tempo reale.
Infine è stato affrontato un caso di studio reale nel quale si è applicata la politica prima descritta a 360°, dall’instrumentazione del codice sorgente, alle misure di prestazione del
software così instrumentato sino ad analizzare i dati ottenuti dal tool dopo una serie di
iniezioni di guasti volutamente inserite in punti del codice al fine di testare l’efficacia
dell’approccio proposto e la correttezza dello strumento realizzato.
92
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Di fatto gli esperimenti effettuati sul caso trattato hanno largamente dimostrato la reale
applicabilità dell’approccio proposto per effettuare la Field Failure Data Analysis, tuttavia
rimangono ancora da affrontare alcune questioni aperte:
- Realizzare un compilatore automatico che provveda all’instrumentazione del codice
sorgente sulle indicazioni di un profiler, come ad esempio Gcov.
- Sviluppare delle tecniche di invio dei messaggi meno invasive per il codice sorgente, in
modo da ridurre significativamente i ritardi introdotti dalla API di logging.
- Pensare a canali di trasporto che garantiscano allo stesso tempo una consegna veloce ed
affidabile degli eventi generati dalle API di logging, in modo da rendere possibile una
analisi in tempo reale del sistema senza che essa possa essere inficiata dalla perdita di
uno o più pacchetti sulla rete.
Resta inoltre necessario sviluppare tool di analisi sempre più raffinati capaci di automatizzare le fasi più laboriose di manipolazione e scrematura dei dati provenienti dalle applicazioni monitorate. Infine, allo scopo di rendere quanto più interoperabile possibile
l’infrastruttura di logging presentata, sarebbe opportuno nonché interessante affrontare la
questione del porting delle API sorgente e monitor verso i linguaggi di programmazione
diffusi come Java, Objective-C, C++, PHP, etc. in modo da poter testare l’approccio e
l’architettura proposta in scenari sempre più complessi e reali, con problematiche differenti e quindi richiedenti soluzioni studiate ah-hoc.
93
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Ringraziamenti
Ci sono state molte persone che hanno avuto un ruolo più o meno importante nel il mio
percorso di studi, qualcuna la conoscevo già da tempo, qualche altra l’ho incontrata strada
facendo. Sperando di non dimenticare nessuno, e sperando di risultare davvero sincero e
non ruffiano, volevo esprimere, dal profondo del cuore, i miei ringraziamenti:
Alla mia famiglia, perché ha sempre creduto in me, lo fa tuttora e so che non smetterà mai
di farlo. Anche quando ero io stesso a dubitare di me, loro non hanno mai esitato a ricordarmi chi ero e quali erano le mia capacità, profondendomi una sicurezza che nessun’altro
mi potrà mai dare. Grazie per avermi regalato la possibilità di rendervi fieri di me.
Alla mia fidanzata, Ilaria, che ha avuto il grande merito di rimanermi accanto quando ne
avevo davvero bisogno e di lasciarmi i miei spazi quando sentivo l’esigenza di isolarmi
nei miei studi e nel mio lavoro. Non credo esista per me una persona tanto giusta come lei,
o comunque non mi interessa, perché con lei ho trovato un mio equilibrio, che mi ha tenuto in piedi anche nei momenti in cui mi sembrava di cadere ad ogni passo.
Volevo anche ringraziare tutta la famiglia Rinaldo: Antonio ed Anna, che mi hanno accolto come un figlio e a cui voglio un bene dell’anima, Francesco, che è una persona a cui
tengo moltissimo, Teresa e Rosaria, che, a dire il vero, sono delle Sirleto purosangue, ma
che, cosa davvero importante per me, mi trattano sempre come uno di famiglia, e questa è
una cosa che non succede spesso.
94
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Ai miei professori di Università, tutti voi avete contribuito a formarmi nel bene e nel male.
Soprattutto i miei relatori Stefano Russo e Domenico Cotroneo, riferimenti assoluti per la
mia carriera professionale nonché bravissimi docenti. Marcello Cinque, validissimo supporto per tutto il mio lavoro, disponibile sempre per ogni mia esigenza, anche se questa
volta non figura come mio correlatore, senza il suo aiuto adesso non sarei qui a scrivere
questi ringraziamenti, spero che un giorno abbia la cattedra che merita. Un saluto inoltre a
tutti i ragazzi del Mobilab: Antonio Pecchia, Catello Di Martino, Christian Esposito e
Vincenzo Vecchio, siete tutti delle persone preparatissime e molto simpatiche, sono stato
bene in vostra compagnia, spero possiate voi dire lo stesso di me. Un ringraziamento particolare va inoltre ad uno dei miei professori preferiti, Porfirio Tramontana, ricercatore
brillante e preparato con cui ho avuto il piacere di seguire molti corsi. Mi ha colpito di lui
la grande umiltà e la grande simpatia che lo rende molto vicino agli studenti e gli fa guadagnare grande rispetto da parte di chi lo segue. Se tanti professori prendessero a modello
la sua visione della didattica, l’università sarebbe di gran lunga migliore e più piacevole.
Ai miei colleghi ma soprattutto amici di università, con loro ho condiviso forse alcuni tra i
momenti più belli e più brutti che la mia vita mi ha regalato e mi regalerà. Lo so che questi
anni trascorsi insieme non torneranno più, ma sono felice di averli spesi in vostra compagnia, e, se potessi tornare indietro, rifarei tutto quello che ho fatto con voi, compreso lo
studiare di sabato e domenica (solo quando era necessario però). Vi ringrazio davvero, e
spero di non perdervi lungo la strada. Un bacio a Claudio, Gaia e al loro gioiellino Francesco Alberto. Un abbraccio fortissimo anche a Giuseppe, Francesca, Christian e Martina.
Un grosso in bocca al lupo ad Antonio, mio collega anche di tesi: è stata una bella esperienza lavorare con te, non è detto che non capiti più in futuro, ti auguro di essere felice.
Ai miei compagni di vita Ruben e Gabriella Fiore, perché sono sempre stati presenti per
anche se non vicini geograficamente. Sono felice di essere stato il vostro testimone di
Nozze, e spero di poter trascorrere con voi ancora un’infinità di tempo. Sappiate che vi sarò sempre vicino come le siete stati voi con me: noi possiamo contare gli uni sugli altri.
95
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Un ringraziamento speciale va anche alle famiglie di questi due splendidi ragazzi: Sergio,
Sonia e Dalila Fiore, che con me sono stati gentili e ospitali sin dal primo giorno in cui ho
avuto la fortuna di incontrarli. Teresa, che manca a tutti noi, Nicola e Gloria Capoluongo,
che ho imparato a conoscere grazie a Gabriella e ai quali non posso che augurare la serenità che da tempo sembra mancare, vedrete che tutto andrà bene, ve lo meritate.
A tutti i miei amici, quelli di vecchia data che magari non vedo più spesso e quelli più recenti e a tutti i miei cugini. Tutti voi non mi avete mai fatto mancare l’affetto e il sostegno
quando ne avevo bisogno, per questo vi porto nel mio cuore: Pasquale, Giuseppina, Marco, Patrizia, Alfredo, Dino, Giovanni, Enzo, Daniela, Luigi, Lello, Antonio, Carmelo, Laura, Carmine, Gaetano, Graziana, Giuseppe, Piero, Francesco, Daniela, Nino, Autilia, Teresa, Adriana, Vittorio, Michele, Anna, Marianna, Simona, Paolo Michele, vi saluto tutti.
Ai miei colleghi di lavoro, di tutte le aziende in cui sono stato, mi avete fatto crescere come persona e come professionista, e siete stati sempre disponibili per me come io lo sono
stato per voi, e questa cosa non la potrò mai dimenticare. Vi ringrazio tutti, ma voglio citare in particolare: Giacomo Gargiulo, Maurizio Verde, Nicola Zanfardino, Brunella Scognamiglio, Guido Genovesi, Francesco Fucito, Luca Bruno, siete stati molto importanti.
Un ringraziamento voglio farlo anche ad una persona per cui attualmente lavoro, Enrico
Nocivelli. Anche se il nostro è un rapporto lavorativo, non posso nascondere che durante i
mesi passati a lavorare per lui e con lui si è instaurata una profonda stima e un grande rispetto, credo reciproco, che magari non può definirsi propriamente amicizia, ma che per
me è sullo stesso piano, e che mi sta portando a crescere sotto tantissimi aspetti, per me è
un vanto conoscere una persona così. Inoltre, cosa non da poco, grazie a lui e alla sua generosità nel lavoro, ho potuto affrontare i miei studi in maniera molto più serena dal punto
di vista economico, ciò mi ha consentito di concentrarmi maggiormente sulle cose che
erano più importanti, completando gli studi e portando avanti un lavoro stimolante e molto
gratificante. Grazie di tutto, spero di essere per te un buon collaboratore anche in futuro.
96
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Bibliografia
1. Wikipedia. Log. http://en.wikipedia.org/wiki/Log. [Online].
2. Marcello Cinque. "Dependability Evaluation of Mobile Distributed Systems via Field
Failure Data Analysis". Novembre 2006.
3. J. Hansen, D. P. Siewiorek. "Models for Time Coalescence in Event Logs". Proceedings of the 22nd IEEE Symposium on Fault Tolerant Computing. Giugno 1992.
4. Gerhards, Rainer. "The Syslog Protocol". IETF. RFC 5424.
5. Lonvick, Chris. "The Syslog BSD Protocol". IETF. RFC 3164.
6. Crocker, Dave e Overell, Paul. "Augmented BNF for Syntax Specifications: ABNF".
Internet Engineering Task Force. RFC 5234. 2008.
7. Internet Assigned Numbers Authority. "Enterprise Numbers". [Online].
8. IANA. "official site". http://www.iana.org. [Online].
9. Wikipedia. "Log4C". http://en.wikipedia.org/wiki/Log4c. [Online].
10. Apache Foundation. "Log4J". http://logging.apache.org/log4j/. [Online].
11. Apache Foundation. "Log4Net". http://logging.apache.org/log4net/. [Online].
12. A. Avizienis, J. Laprie and B. Randell, “Fundamental Concepts of Dependability”.
Research Report N01145, LAAS-CNRS. 2001.
13. D. Powell. "Failure Mode Assumptions and Assumption Coverage", in B. Randell, J.C.
Laprie, H. Kopetz and B. Littlewoods (Eds.). "Predictably Dependable Computing Systems". Springer-Verlag ed. 1995.
14. W. H. Sanders. “Computer System Analisys”. ECE 441, 2001.
15. M. Cinque, D. Cotroneo, S. Russo, A. Pecchia. "Improving FFDA of Web Servers
trough a Rule-Based logging approach". 2008.
97
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
16. C. Simache, M. Kaâniche. "Measurement-based availability analysis of unix systems
in a distributed environment". 12th International Symposium on Software Reliability Engineering, 2001.
17. M. Cinque, D. Cotroneo, A. Pecchia. "Enabling Effective Dependability Evaluation
of Complex Systems via a Rule-Based Logging Framework". 2009.
18. J. F. Meyers, “On Evaluating the Performability of Degradable Computing Systems”,
Proc. of 8th Int’l Symp. on Fault-Tolerant Computing, pp. 44-49. 1978.
19. SourceForge. "Logbus-ng repository". http://logbus-ng.svn.sourceforge.net/. [Online].
20. Apache Foundation. " Apache Web Server ". http://www.apache.org. [Online].
21. GCC. "The Gcov tool". http://gcc.gnu.org/onlinedocs/gcc/Gcov.html. [Online].
22. Jmeter. "The Jmeter tool". http://jakarta.apache.org/jmeter/. [Online].
98
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Lista delle figure presentate nel testo
Figura 1.1 – Gli stati di un sistema ........................................................................................ 7
Figura 1.2 – Il processo di propagazione degli errori .......................................................... 10
Figura 1.3 – La catena guasto-errore-fallimento ................................................................. 11
Figura 1.4 – Categorizzazione dei fallimenti rispetto alla safety ........................................ 12
Figura 1.5 – La metodologia FFDA..................................................................................... 20
Figura 1.6 – Rilevazione di eventi di fallimento multipli .................................................... 21
Figura 3.1 – Il modello del sistema...................................................................................... 40
Figura 3.2 – La rilevazione di un alert ................................................................................. 45
Figura 3.3 – Gli stati operativi di un’entità .......................................................................... 46
Figura 3.4 – Un esempio di report di log Rule-Based ......................................................... 48
Figura 3.5 – La pipeline del core di Logbus-ng ................................................................... 54
Figura 4.1 – Scelta delle entità da monitorare ..................................................................... 72
Figura 4.2 – Stato generale delle entità del sistema ............................................................. 73
Figura 4.3 – Dettaglio relativo ad una delle entità monitorate ............................................ 74
Figura 5.1 – Throughput del server non instrumentato ....................................................... 80
Figura 5.2 – Ritardo medio del server non instrumentato ................................................... 81
Figura 5.3 – Ritardo medio del server instrumentato (tecnica UDP) .................................. 82
Figura 5.4 – Throughput del server instrumentato (tecnica UDP) ...................................... 83
Figura 5.5 – Ritardo medio del server instrumentato (tecnica a memoria condivisa) ......... 84
Figura 5.6 – Throughput del server instrumentato (tecnica a memoria condivisa) ............. 84
99
Realizzazione di una API di logging per la Field Failure Data Analysis di sistemi software distribuiti
Figura 5.7 – Confronto tra i ritardi senza API e tecnica UDP ............................................. 85
Figura 5.8 – Confronto tra i ritardi senza API e con tecnica a memoria condivisa ............. 85
Figura 5.9 – Confronto tra i throughput del server con e senza instrumentazione .............. 86
Figura 5.10 – Startup e shutdown puliti del web server Apache ......................................... 87
Figura 5.11 – Hang rilevato in seguito a un fault in un file interno al modello................... 88
Figura 5.12 – Hang rilevato in seguito a un fault in un file esterno al modello .................. 89
Figura 5.13 – Crash rilevato in seguito a un fault in un file interno al modello .................. 89
Figura 5.14 – Crash rilevato in seguito a un fault in un file esterno al modello .................. 90
100

Documenti analoghi

Facoltà di Ingegneria

Facoltà di Ingegneria erogazione di un servizio da parte dello stesso, si definisce guasto o fault: uno stato improprio dell‟hardware e/o del software, derivante dal guasto di un componente, da fenomeni di interferenza ...

Dettagli