Na začátku byla poněkud zvláštní chyba, která se vyskytovala náhodně na různých serverech našeho aplikačního clusteru (cca 10 strojů) v různých časech. Na konci byla funkční instalace Graphite a jednoduchý nástroj pro nahrávání historických dat do Graphite napsaný v Groovy.

Problém

Chyba při práci s DB session, která nás trápila delší dobu. V našem clusteru běží cca 10 strojů, logováni je pěkně po staru do souborů s denní rotací - jediný pokrok proti pravěku je fakt, že jsou logy ze všech strojů dostupné z jednoho adresáře. Věděli jsme, že se chyba vyskytuje na různých strojích v různém čase. Chtěl jsem pochopit, kde se chyba projevuje, ideálně v grafické podobě. Vzpomněl jsem si na Graphite, o kterém jsem slyšel spoustu pěkných věcí na GeeCONu.

Graphite

Graphite je výkonný a škálovatelný nástroj pro sběr metrik (resp. libovolných čísel) v realném čase. Kromě samotného sběru metrik je umí šikovně analyzovat a zobrazovat. Sestává se z několika komponent, které není vždy snadné nainstalovat. Naštěstí existuje několik Vagrant souborů, které instalaci podstatně zjednoduší. Vybral jsem si Synthesize, protože měl nejdelší readme :) Instalace se mi z počátku nedařila, ale po mnoha úderech hlavou o stůl jsem zjistil, že stačí na Vagrant kuchařku poštvat dos2unix příkaz, protože běžím na Windows (tento problém je už nyní popsaný v readme Synthesize). Instalace vypada takto:

1
2
3
$ cd synthesize
$ vagrant plugin install vagrant-vbguest
$ vagrant up

Data loader AKA Krmič

Graphite běží a carbon démon je připraven konzumovat data. Jenže data jsou zatím na dalekém sereru v souborech.

Krok první - vyhledání chyby

Povolal jsem starý dobrý grep:

1
grep -n "unique error descriptor" vmx-??/server-2015-07-??.log > skareda-chyba.txt

Grep projde všechny logy na všech serverech v červnu a naháže je do jednoho souboru. Přepínač -n zařídí, že každý řádek bude obsahovat identifikaci souboru, ve kterém byla chyba nalezena. Řádek s chybou pak vypadá takto:

1
vmx-www01/catalina.2015-06-22.log:9953:Jun 22, 2015 9:16:02 AM com.domain.something Error

Krok druhý - nakrmení carbonu.

Mám velký soubor, potřebuju ho projít a každý záznam nahrát do Graphite. Jelikož jsem z JVM světa, zvolil jsem Groovy, které je méně ukecané než Java.

Carbon očekává data v jednoduchém formátu: nejaka.moje.data hodnota timestamp\n, například database.error.log.www02 1437928247\n. Jelikož jsem přehlédl konec řádku v definici formátu, stálo mě nahrávání dat opět několik úderů hlavou o stůl. Název metriky je důležitý, protože Graphite vám pozdějí umožní pracovat se skupinou metrik pomocí hvězdičkové notace (database.error.log.*) a pro zajímavost také data ukládá do adresářové struktury, která odpovídá jménu metriky (database/error/log/www02).

Chroustač

Loader.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
def graphiteUploader = new GraphiteUploader(basePath: "hibernate.server.error")
graphiteUploader.withCloseable {
def dateFormatter = DateTimeFormatter.ofPattern("MMM dd, uuuu h:mm:ss a", Locale.ENGLISH)
new File('hibernate-errors.txt')
.readLines()
.collect {
def matched = it =~ /(vmx-www\d*).*:(\w{3}.*) com\.domain/
String dateString = matched[0][2]
def date = LocalDateTime.parse(dateString, dateFormatter)
new Notice(machine: matched[0][1], date: ZonedDateTime.of(date, ZoneId.of("GMT")))
}
.each graphiteUploader.&upload
}
GraphiteUloader.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class GraphiteUploader implements Closeable {
def basePath
def graphite = new Graphite()
def upload(Notice notice) {
graphite.send("$basePath.${notice.machine}", 1, notice.date)
}
@Override
void close() throws Exception {
graphite.close();
}
}
Graphite.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Graphite implements Closeable {
Socket conn = null
public Graphite() {
conn = new Socket("127.0.0.1", 22003);
}
public send(String path, int value, ZonedDateTime time) {
def message = "$path $value ${(long) (time.toInstant().toEpochMilli() / 1000)}\n"
try {
DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
dos.writeBytes(message);
} catch (Exception e) {
println "Error when sending message $message, ${e}"
}
}
@Override
void close() throws Exception {
try {
conn.close()
} catch (Exception ex) {
println "Error when closing connection: ${ex.getCause()}"
}
}
}

Spustíme, počkáme a data jsou v Graphite.

Výsledek

Pokud data rovnou zobrazíme, nebude toho moc vidět, protože jsem je nahrál jako body s hodnotou jedna. Přinejlepším z toho bude plot ze samých tenkých čárek. Pro lepší viditelnost jsem hodnoty sečetl po hodinách s pomocí funkce summarize. Přehled podporovaných funkcí je zde : http://graphite.readthedocs.org/en/latest/functions.html. Výsledný příkaz je pak

1
summarize('hibernate.error.*', '1hour', 'sum')

Finální graf

Graphite po expanzi hvězdičky přiřadí jednotlivým serverům barvu a výsledný graf vypadá takto:

Výše uvedený příklad není ani zdaleka typické použití Graphite. Potěšilo mě, že s pomocí Vagrantu a Groovy bylo možné realizovat nápad v řádu hodin.

Další čtení

http://matt.aimonetti.net/posts/2013/06/26/practical-guide-to-graphite-monitoring/

Comments