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
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 ...