Gnuplot for sysadmins
Am invatat zilele astea o gramada de lucruri pe care trebuia sa le stiu de mult, unul din ele fiind sa fac niste grafice simple cu gnuplot.
Problema pe care o aveam (ma rog, nu chiar problema, dar asa fac eu cand ma plictisesc, imi dau de lucru) era ca asteptam dupa restaurarea unui backup maricel si voiam sa stiu cam cand e gata, bazat pe dimensiunea datelor si timestampuri. Dupa cateva minute de chin cu arithmetic expansion din bash si mai apoi cu bc, mi-am adus aminte ca am vazut acum vreo doua zile un post interesant pe un blog despre grafice cu gnuplot si am zis ca oricum n-aveam nimic mai bun de facut duminica dupa-masa decat sa invat si eu.
Prima chestie a fost sa pun datele colectate pana atunci intr-un fisier text la forma timestamp, tab, marime. Ca sa obtin usor citiri in continuare, am ajuns din cateva iteratii la echo -ne "`date +%s`\t" ; df -k /data | awk '/data$/ {print $3}' , pe romaneste scot utilizarea in kilobytes a partitiei care ma interesa. Stiam dimensiunea la care trebuie sa ajunga, asa ca am desenat cu gnuplot punctele citite pana atunci, o linie orizontala la valoarea finala, dupa care am invatat sa fortez limitele graficului, dupa care am descoperit ca gnuplot stie de fitting functions, si asa mai departe, astfel ca dupa ce mi-am rezolvat problema initiala ("cand se termina, aproximativ?") in cca. 20 de minute, am mai stat vreo 4-5 ore sa ma joc si sa ajung la versiunea pe care o sa o dau mai jos.
Pentru cei interesati, cateva lectii invatate cu greu:
- operatiile cu timp sunt al naibii de derutante pana intelegi ca %s din gnuplot nu inseamna secunde de la 1 ianuarie 1970 00:00 UTC, ca in unix, ci 1 ianuarie 2000 00:00 UTC. Valoarea magica ce trebuie scazuta este 946684800, adica `date +%s -d'1 Jan 2000 00:00 UTC'`. Daca vrei ora Romaniei, trebuie sa aduni 3*3600 vara si 2*3600 iarna (cred ca se poate face o scamatorie cu comenzi externe sa obtii offsetul corect in functie de $TZ, n-am incercat).
- nu mi-am batut capul sa aflu limita, dar operatiile cu numere intregi sunt pe un numar restrans de biti (31+semn, cred), asa ca daca niste valori nu ies cum trebuie, trebuie fortate la float (inmultire cu 1.0 ar merge, probabil e si functie de typecast, n-am cautat)
- plot pare a fi singura comanda care scrie pe canvas, asa ca orice etichete sau sageti trebuie facute inainte
- daca ai mai multe comenzi plot se rescriu cele anterioare, asa ca probabil vrei replot (ceea ce practic adauga functia la plot-ul anterior, cu avantajul ca e usor de comentat)
- desi terminalele wxt (default) si x11 se lauda ca ar sti zooming, uneori dau eroare sau crapa la replotare si nu m-am prins de ce, poate e ceva din hackurile pe care le-am facut
Mai intai datasetul, ar trebui salvat ca "aux.data" (sau dat un search/replace cu numele nou in fisierul de control):
#timestamp #size in KB
1215341625 1214104
1215341819 3202268
1215343333 16721172
1215344294 27510464
1215345314 39026564
1215345968 45206372
1215348133 69642340
1215348677 75828732
1215351729 110504584
1215353076 125790396
1215356183 159291284
1215357589 175025032
1215358546 185594388
1215360157 203809928
1215361294 216551588
1215364138 237378336
1215366047 248816868
1215367939 260543892
1215369395 271462788
1215370895 282300944
1215374518 315826136
1215376667 335455004
1215378218 348836788
1215380769 372323748
Apoi scriptul efectiv, se poate seta executabil si rulat direct sau incarcat din gnuplot cu load "aux.plt". Special nu am setat terminalul in fisier ca sa poata fi dat override inainte de load. Incercati set terminal dumb 100 40 (sau cat permite fereastra terminalului) inainte de load "aux.plt", pentru a vedea cum arata in ascii. Sper sa aiba wrap suficient si sa fie suficient de inteligibil, comentariile le-am adaugat mai tarziu.
#!/usr/bin/gnuplot -persist
# constants
max = 6.743e11 # target value
# ugly hack to obtain EEST times from unix times.
# 946684800 is the unix time of the gnuplot epoch (1 jan 2000 00:00 UTC)
rotime(x) = x-946684800+3*3600
# start and delta are rough estimates to seed the fit algorithm
start = 1.2153415e9
delta = 20.6*3600
f(x) = (x - start) * max/delta #linear fit, delta is time from zero to max, in seconds
num_points = `grep -c . aux.data`
fit f(x) "aux.data" every ::(num_points-10) using 1:($2*1024) via start, delta
# if you want to estimate using the whole dataset, comment the above and uncomment the below
#fit f(x) "aux.data" using 1:($2*1024) via start, delta
alfa = max/delta # get the slope of the fitting line, helps calculation
# ugly way to get last datapoint
lastx=`grep . "aux.data" | tail -1 | cut -f1`
lasty=`grep . "aux.data" | tail -1 | cut -f2`
set xdata time
set timefmt "%H:%M %d/%m/%y"
# you may want to adjust the ranges and the tics
set xrange ["12:00 06/07/08":"14:00 07/07/08"]
set yrange [0:8e11]
# tics every 6 hours, minor tics every hour
set xtics "12:00 06/07/08", 21600
set mxtics 6
set format x "%a\n%H:%M"
set format y "%.0s %cB"
# do some labelling and put some arrows
set label "to go: %.2s %cB", (max-lasty*1024.0) at rotime(1.215345e9),5.5e+11
ETR=(max-lasty*1024.0)/alfa
ETA=rotime(lastx+ETR)
set label sprintf("ETA: %02.0f:%02.0f (~%.2fh)", tm_hour(ETA), tm_min(ETA), ETR/3600 ) \
at rotime(1.215345e9),5e+11
set arrow from rotime(lastx),lasty*1024.0 to ETA,max nohead ls 4
set arrow from ETA,max to ETA,0.0 lw 3 lc rgb 'red'
set xtics add (ETA)
set timefmt "%s"
#plotting must come last
plot "aux.data" using (rotime($1)):($2*1024.0) title "size readings" with points 3,\
"aux.data" using (rotime($1)):($2*1024.0) notitle with lines
replot max with lines lt 0 lw 2 lc rgb 'blue'
#uncomment this to see the computed line
#replot "aux.data" using (rotime($1)):(f($1)) title "fitting line" with line
Stiu ca e urat, ca e nenecesar de complicat, ca se putea mai simplu, mai frumos, cu altceva, dar asa e cand esti incepator, faci tampenii inutil de complicate. Poza nu pun, instalati si voi gnuplot. Have fun.