The Grinder

The Grinder e' un programma Open Source per effettuare test di carico e per misurare i tempi di risposta dei programmi rivolto principalmente, ma non solo, ad applicazioni web. E' un ottimo prodotto distribuito gratuitamente con i sorgenti con una licenze BSD style. E' realizzato in Java con librerie standard, quindi eseguibile virtualmente su qualsiasi sistema. I test vengono generati/programmati con il linguaggio Jython, l'implementazione Java di Python, quindi sono facilmente modificabili ed adattabili a casi anche molto complessi.

Anche se i test effettuabili con Grinder possono venir programmati a piacere, il modo piu' semplice per utilizzarli su un sito web e' quello di registrare una sessione di lavoro di un utente e quindi replicarla per centinaia o migliaia di utenti. In questo breve documento vedremo come utilizzare il Grinder soprattutto in questo modo.
In realta' il Grinder e' uno strumento molto completo ed, oltre ad una gestione molto completa dell'HTTP (eg. cookies, SSL) puo' essere sfruttato nel test di applicazioni complesse (eg. SOAP). Personalmente l'ho utilizzato molto per effettuare benchmark verso database relazionali acceduti via JDBC...

I componenti principali del Grinder sono tre:

Questo documento fa riferimento alla versione 3 (Production), le ultime novita' sono riportate in questo capitolo (NdE aggiornato alla versione 3.4 rilasciata in Aprile 2010).

Come introduzione basta cosi'. Ora iniziamo a lavorare!

Installazione

L'installazione e' molto semplice. Scaricato il software (in formato .ZIP), dal sito ufficiale e' sufficiente decomprimerlo (eg. in C:\grinder3\).
Basta: l'installazione e' terminata!

Si tratta di programmi 100% Pure Java e' quindi necessario che sul sistema ospite sia installato il JRE (Java Runtime Environment). E' consigliabile la versione 1.4 o successive (puo' cambiare nel tempo).
Per rendere piu' semplice il lancio dei programmi e' utile settare il CLASSPATH in modo che raggiunga le librerie di Grinder. Nello strano mondo di Windows si configura su Pannello di controllo - Sistema - Avanzate - Variabili d'ambiente
CLASSPATH=%CLASSPATH%;C:\grinder3\lib\grinder.jar

Quanto riportato e' un esempio, naturalmente i path dipendono dalle versioni (eg. sul alcune versioni precedenti era necessario inserire jython.jar) e dalle directory scelte per l'installazione del software...

Naturalmente ciascuno puo' effettuare la configurazione come preferisce, per quello che mi riguarda su PC trovo comodo utilizzare una directory specifica per il lancio delle applicazioni (eg. \test_grinder) e creare qualche icona sul desktop associata a semplici script (eg. agent.bat):
set CLASSPATH=%CLASSPATH%;C:\grinder3\lib\grinder.jar;C:\grinder3\lib\mysql-connector-java-5.0.4-bin.jar;C:\grinder3\lib\ojdbc14.jar
cd \test_grinder
java net.grinder.Grinder

Grinder e' un programma Java 100%, quindi gira praticamente ovunque. Tuttavia se lo utilizzate su un benchmark impegnativo installatelo su un sistema o su una batteria di sistemi sufficientemente potenti e vicini vicini ai server in esame. Ovviamente sistemi Unix/Linux vanno benissimo, anzi meglio!

PATH=$PATH:/opt/java1.5/bin
CLASSPATH=$CLASSPATH:/home/grinder3/lib/grinder.jar
export CLASSPATH

cd /home/test_grinder
java net.grinder.Grinder

Registrazione percorsi

Il modo piu' semplice per definire uno scenario di utilizzo di un'applicazione web e' quello di registrare la navigazione di un utente sul browser. Per fare questo e' sufficiente attivare il programma proxy di Grinder, modificare la configurazione del browser per farlo puntare al proxy e registrare una normale navigazione dell'utente.

Quanto riportato e' sufficiente per registrare un percorso su una applicazione locale. Il TCP Proxy ha diverse opzioni che consentono di utilizzare porte differenti per protocollo, utilizzare un ulteriore Proxy Server, ... che consentono di registrare il percorsi anche nei casi piu' complessi.

La navigazione e' salvata sul file di output file.py. Il file e' uno script Jython (l'implementazione Java del diffuso linguaggio di scripting Python) che contiene tutti i dettagli della sessione HTTP. Nelle versioni precedenti erano due file: httpscript.py e httpscript_tests.py.

Console

Il lancio dei test avviene generalmente coordinando le diverse attivita' e monitorando i risultati da una console grafica. Per attivare la Console lanciare:

java net.grinder.Console

Le funzionalita' principali della Console sono:

Usare l'interfaccia grafica e' piu' semplice che spiegarla...

Nelle versioni precedenti per attivare la Console nazionalizzata in italiano occorreva lanciare il comando:

java -Duser.language="it" net.grinder.Console
Ora la selezione del linguaggio e' automatica, ma e' sempre possibile cambiare il linguaggio di default con la sintassi indicata. Naturalmente, prima di poter vedere i grafici prestazionali e' necessario attivare gli Agent!

Agent

Eseguire il test e' il compito dell'Agent Grinder.
Per attivare l'agente il comando e':

java net.grinder.Grinder
Una volta attivato l'agente questo attende i comandi dalla Console ed esegue o, per essere piu' precisi, fa eseguire i test richiesti.

Per descrivere in modo compiuto le possibilita' del Grinder abbiamo bisogno di un po' di terminologia...
Gli script Jython contengono gli scenari ovvero i percorsi di test. Questi possono essere stati registrati con il TCPProxy, come abbiamo visto prima, o programmati con un normale editor.
Una singola esecuzione di un percorso di test viene chiamata run. Le misure prestazionali sono raccolte misurando i tempi di esecuzione di ogni run.
Una sessione di test utilizza piu' run eseguiti in sequenza. I run sono eseguiti da thread in parallelo. Il numero massimo di thread utilizzabili e' limitato dalla memoria della Java Virtual Machine (JVM) che li ospita.
E' possibile utilizzare piu' JVM chiamati Worker Process. L'Agent Grinder coordina le attivita' dei vari Worker Process sul sistema e comunica con la Console.
Il carico che un agente riesce a produrre e' tipicamente limitato dalla CPU del sistema che lo ospita o dalla capacita' della connessione in rete. La Console puo' essere locale (localhost di default) oppure ospitata su un altro sistema in rete. E' cosi' possibile coordinare dalla Console agenti che operano su sistemi diversi utilizzando una batteria di sistemi che eseguono il test.
Il Think Time, che e' stato registrato durante la registrazione degli scenari, viene tipicamente reso variabile secondo la distribuzione normale di cui si puo' impostare la devizione standard. Oppure puo' essere ridotto a piacere (anche annullato) per effettuare uno stress test.
La fase di attivazione dei processi e dei thread viene chiamata Ramp-up e viene generalmente esclusa dalle statistiche poiche' e' una fase necessaria porre il sistema sotto test con un carico a regime. Le attivazioni dei processi e dei thread possono essere scalate nel tempo secondo una distribuzione random uniforme.
Tutte queste scelte sono parametrizzate nel file XXX.properties associato allo script da lanciare. Un file di esempio e' il seguente:

# Process:
#	Console -> Agent -> Worker Process (JVM) -> Thread -> Run
grinder.processes=5
grinder.threads=10
grinder.runs=60

### Timing: distribuzione dei tempi di think
#	Uniforme sull'initial e con distribuzione normale sugli sleep
grinder.initialSleepTime=100
grinder.sleepTimeFactor=0
# grinder.sleepTimeVariation=0.2

### Ramp up: partenza graduale dei processi
grinder.initialProcesses=1
grinder.processIncrement=1
grinder.processIncrementInterval=1000

### Log
grinder.logDirectory=log
grinder.numberOfOldLogs=2

### Console: 
# grinder.consoleHost=localhost
# grinder.script=grinder.py
grinder.useConsole=true
grinder.script=myScript

Nell'esempio di configurazione vengono eseguite 50 sessioni utenti contemporanee (5 processi x 10 thread) che eseguono lo script scelto per 60 volte (se vale 0 per sempre) senza tempi di pensamento per ogni Agent attivo. Monitorando i sistemi sotto test e' possibile rilevare il carico cosi' generato.

I risultati raccolti dalla Console possono sono salvati su un file di testo (delimitato da TAB, quindi immediatamente caricabile su uno Spreadsheet). I dati raccolti sono molto completi e consentono di identificare immediatamente le transazioni di maggior durata.

Quando deve essere simulato un carico significativo e' necessario utilizzare una batteria di Agent che agiscono in parallelo. Ovviamente gli Agent vanno collocati in rete "il piu' vicino possibile" ai sistemi da esaminare. In questo caso la configurazione degli agent remoti richiede due parti: lo script di lancio ed il file grinder.properties. Ecco una configurazione d'esempio:

# Agent.sh script

# mysql-connector-java-5.1.14-bin.jar ojdbc6.jar postgresql-9.0-801.jdbc4.jar
CLASSPATH=$CLASSPATH:/bench/grinder-3.4/lib/grinder.jar:/bench/postgresql-9.0-801.jdbc4.jar
export CLASSPATH

cd /bench
java net.grinder.Grinder
# grinder.properties for remote agent

grinder.consoleHost=10.0.0.1
grinder.useConsole=true

Dove ovviamente l'IP indicato deve essere quello del sistema su cui gira la console del Grinder. Nella configurazione dei test e' importante controllare il parametro grinder.consoleHost: quando si utilizzano Agent remoti non va usato il valore localhost ma l'IP della console.
Ovviamente il sistema su cui opera la console deve essere raggiungibile dagli Agent. La porta utilizzata (per un eventuale apertura del firewall) e' la 6372.

Script

I run sono costituiti da script Python. E' quindi possibile programmare test complessi a piacere, tuttavia un modo semplice per iniziare e' quello di utilizzare gli script registrati con il TCPProxy modificandoli secondo le necessita'. I file registrati sono di semplice comprensione poiche' corrispondenti all'attivita' di un utente. Una volta terminata la registrazione e' possibile modificarlo ritoccando ad esempio tempi di pensamento (anche se generalmente si utilizza il parametro grinder.sleepTimeFactor) o le URL richieste.

Una nota: a seconda delle versioni del plugin il formato ed il numero di file utilizzati puo' cambiare... ma la logica resta sempre la stessa.

etc

Le indicazioni riportate fino ad ora dovrebbero consentire l'esecuzione della maggior parte dei test. Per esigenze piu' complesse e' opportuno dare un'occhiata alla documentazione in linea sul sito ufficiale. Molto utili sono anche gli esempi che vengono distribuiti con i sorgenti.
Nel seguito e' riportata, in modo non strutturato, qualche indicazione che puo' essere utile in qualche caso particolare...

Eseguire un test sulle prestazioni e' come andare da un oracolo. La cosa piu' importante non sono le risposte che ci da' ma le domande che gli facciamo!
Sembra una banalita' filosofica ma prima di analizzare al millesimo di secondo i risultati ottenuti su un complesso scenario eseguito da migliaia di utenti... e' meglio prima capire a cosa servono il sistema e le applicazioni, comprendere l'architettura dei vari componenti e realizzare cosi' un test significativo! Quindi attenzione alle domande che si fanno: cosa vogliamo veramente controllare?
Anche l'interpretazione dei risultati e' importante. Spesso le indicazioni del thoughput e del tempo medio di servizio vanno interpretate con la dovuta cautela per non giungere a conclusioni errate.

Per un test di carico significativo e' necessario disporre di piu' macchine su cui lanciare gli agenti. Altrettanto importanti sono le connessioni di rete che debbono essere ad alta velocita' e connesse direttamente, o il piu' direttamente possibile, ai sistemi che si vogliono misurare.

Quando si effettuano i test e' opportuno (leggi necessario) monitorare il comportamento dei sistemi coinvolti con gli strumenti di controllo delle performance. Questo vale sia per i sistemi che ospitano le applicazioni sotto test (eg. web server, application server, DB Server), che per i sistemi su cui viene generato il carico (eg. le stazioni che ospitano gli agenti e la Console di Grinder), che per eventuali sistemi di supporto (eg. proxy server, load balancer, ...).

Programmare scenari multipli e' semplice con gli script Jython. Se abbiamo due script funzionanti j1.py e j2.py il seguente script realizza uno scenario in cui il 25% degli utenti esegue il primo test ed il 75% il secondo (con un numero di thread multiplo di 4 per Worker Process). L'unica avvertenza, se vogliamo raccogliere i risultati delle transazioni in modo disgiunto, e' quella di definire numeri di transazione differenti (nelle versioni piu' recenti e' possibile impostare il numero di partenza quando si effettuano le registrazioni).

from net.grinder.script.Grinder import grinder

scripts = ["j1", "j2"]

for script in scripts: exec("import %s" % script)

def createTestRunner(script):
    exec("x = %s.TestRunner()" % script)
    return x

class TestRunner:
    def __init__(self):
        tid = grinder.threadID

        if tid % 4 == 0:
            self.testRunner = createTestRunner(scripts[0])
        else:
            self.testRunner = createTestRunner(scripts[1])
    
    def __call__(self):
        self.testRunner()

Solo un poco piu' complessi sono gli esempi di programmazione per effettuare test su DB con connessioni JDBC: Benchmark EMP7, Benchmark TPC-B.

Oltre alla documentazione sul sito ufficiale ho trovato molto chiaro e semplice questo articolo.

Grinder e' un ottimo tool Open Source per la valutazione delle prestazioni di siti web e per effettuare test di carico. Alternative sono prodotti commerciali come Load Runner: probabilmente il miglior prodotto commerciale per effettuare stress/load test distribuito da Mercury/HP)... Oppure la creazione di semplici programmi di test in C, SQL, shell, lynx (un browser testuale che e' possibile utilizzare da script), wget, ... Ecco un semplice esempio con lynx:

#!/bin/sh
for i in 1 2 3 4 5 6 7 8 9 10
do
   for j in 1 2 3 4 5 6 7 8 9 10
   do
      lynx -cache=0 -dump -accept_all_cookies -reload \
        meotec.da.ru:80/index.htm > /dev/null
   done
done
Ma la flessibilita' e facilita' di utilizzo di Grinder lo rendono utilissimo e presenta diversi vantaggi rispetto alle alternative.

Novita'

Grinder e' disponibile da anni come Beta. In realta' la qualita' del programma e' ottima ed e' stato perfettamente adatto allo scopo fin dalle prime versioni.

A gennaio 2008 e' uscita la versione 3 definita come prima versione di "produzione" che ha introdotto una novita'. Quando si effettuano i test con la Console, anziche' selezionare uno script Jython da far eseguire agli Agent, deve essere selezionato un file properties. Il file properties dovra' richiamare uno script valorizzando la proprieta' grinder.script.
In questo modo il controllo della Console sugli Agent e' molto maggiore. Non solo la Console definisce lo script da eseguire e raccoglie i risultati, ma intervenendo centralmente su un unico file si definiscono il numero di processi, di thread, di esecuzioni, il timing, ... di tutti gli Agent coordinati dalla Console.
Altre utili funzionalita' sono la configurazione di un editor esterno e l'utilizzo del locale dell'utente per le impostazioni sull'I18N...

Il Grinder e' un progetto "vivo" e viene periodicamente aggiornato: conviene scaricare sempre la versione piu' aggiornata da SourceForge.

Glossario

Nello strano mondo della simulazione, dell'analisi delle prestazioni, dello stress test, ... si utilizzano una serie di termini che e' importante riconoscere:


Testo: The Grinder
Data: 31 Giugno 2006
Versione: 1.0.13 - 31 Giugno 2010
Autore: mail@meo.bogliolo.name