[CA80][V543] Elasticsearch/Logstash/Kibana vs Meratronik V543 i CA80 - dwa światy razem.
♫ ♩ ♪ Behemoth ⚡ ☘ ⚡ Blow Your Trumpets Gabriel ♪ ♩ ♫
https://youtu.be/Czx-OIyrQwQ
Mam wrażenie, że kiedyś o tym już wspominałam - łączenie techniki współczesnej i klamotów w klimatach retro to dla mnie frajda sama w sobie, a osiągalne efekty są czasem dość ciekawe. Z wymienionym w tytule oprogramowaniem Elasticsearch, Kibana i Logstash spotkałam się na projekcie w fabryce, oj spory czas temu, okazało się to na tyle intrygujące, że zaczęłam też drążyć temat w tak zwanych domowych pieleszach, no i mamy co następuje - zabawowo zorientowany system akwizycji danych wykorzystujący nowoczesne i "modne" oprogramowanie, a w formie końcówek obiektowych znajdziemy znane już dinozaury CA80 i V543.
W moim odczuciu, to forum ma środek ciężkości przeniesiony na elektronikę i oprogramowanie embedded zatem, niejako z konieczności dwa słowa rozjaśnienia czym są składniki stosu ELK, tak zupełnie skrótowo.
Logstash - to oprogramowanie przyjmujące dane z różnych źródeł i sprowadzające pakiet informacji do wspólnego mianownika (kanonizacja, uspójnienie formatu) akceptowalnego przez bazę Elasticsearch. Pozyskiwanie danych odbywa się w sposób pasywny, tylko czytając ze źródła (stdin,http,log4j) lub aktywny (exec, pipe, http_pool) gdzie potencjalne źródło zdarzeń jest odpytywane z ewentualnych nowości.
Elasticsearch - megacwane narzędzie stanowiące silnik wyszukiwania, są kryteria kwalifikujące go jako `non-SQL database` i to jest mi najbliższe. Dane przechowywane są w tzw. indeksach (analogia tabel), pojedyncze rekordy to odpowiednio sformatowane dokumenty JSON. Do tego dochodzi cała sprytna infrastruktura zarządzająca tymi danymi, zapewniająca szybki dostęp zarówno do informacji bieżących jak i archiwalnych (hot-warm-cold architecture).
Kibana - fajne, estetyczne i bardzo funkcjonalnie opakowanie graficzne do powyższych, stanowiące zarówno warstwę prezentacji (logi,wykresy, dashboardy) jaki i GUI do podstawowych zadań utrzymaniowych czyli zarządzanie zasobami i cyklem życia indeksów.
Raczej nie ma sensu powielać informacji już umieszczonych w sieci, zatem poniżej garść linków z których ja się uczyłam, proszę przeglądnać i ewentualnie googlować dalej:
https://logz.io/learn/complete-guide-elk-stack/
https://blog.gutek.pl/2017/01/23/elastic-logstash/
http://krzysztofjelonek.net/elk-stack-d ... ia-logami/
https://www.elastic.co/guide/en/logstas ... -ruby.html
https://dzone.com/articles/kibana-tutor ... ted-part-1
https://grokconstructor.appspot.com/do/automatic
W tym opisie opisie wykorzystałam oprogramowanie w wersji 5.6, biorąc pod uwagę dynamiczny rozwój całego stosu (bo za tym stoi naprawdę spora kasa) jest to edycja nieco zapyziała i nie do końca nadaje się do ludzi. No ale ma tę zaletę, że cały stack uruchomi mi się na maszynie z 4GB RAM i 32-bitowym Linux Mint 17. Jeżeli ktoś zechce podejść do zagadnienia i ma zaplecze sprzętowe, proszę korzystać z najnowszych nitek 7.1.x, choćby w przypadku GUI Kibana różnice w funkcjonalnościach są drastyczne i momentami wyrywają z majtek.
przygotowania - CA80
Cała zabawka ma mierzyć i logować zmiany temperatury, zacznijmy zatem od czujnika i pobierania z niego informacji. Tu wybór był oczywisty, choćby z racji tego, że instalacja ciągle siedzi za oknem na parapecie (od południowej strony) - kostka MCP9700 od Microchip. Ktoś już tu na forum to tym układzie pisał, więc [szukaj], a w połączeniu z CA80 sprawuje się wyśmienicie http://bienata.waw.pl/ca806.php , druciaczek z ośmiobitowym TLC549 wygląda pociesznie:
Oprogramowanie dla CA80 zostało przygotowane mocno na motywach opowieści o kostce Z80-SIO, w sumie to wydarzyły się trzy ważne funkcje - odczytywanie przetwornika A/C, odczytywanie pokładowego czasu i daty oraz logowanie w ustalonym formacie na zdalnej, szeregowej konsoli. A zatem mamy kolejno - przetwornik:
sio_demo_irq4_logstash.c pisze:Kod: Zaznacz cały
unsigned char getADC(void) __naked {
__asm
ld A,#0x00
out (0xE2),A ; PC-0xE2
; reset vars
ld B,#8 ; bits
ld C,#0 ; keep A/D val.
getADC$0:
; get bit from ADC
in A,(0xE2)
and A,#0x80
or A,C ; A := A|C
rlca ; >> wziuuuu
ld C,A ; put in C back
; clock _/~\_
ld A,#0x02
out (0xE2),A ; /cs = L, CLK = H
ld A,#0x00
out (0xE2),A ; /cs = L, CLK = L
; get next bit
djnz getADC$0
; /CS = H
ld A,#0x01
out (0xE2),A ; /cs = H, CLK = L
ld L,C ; result
ret
__endasm;
}
Odczytywanie czasu lokalnego z zegarka napędzanego przerwaniem NMI:
sio_demo_irq4_logstash.c pisze:Kod: Zaznacz cały
void getSystemTime(struct tm *pT) {
pT->tm_sec = *((unsigned char*)0xFFED);
pT->tm_min = *((unsigned char*)0xFFEE);
pT->tm_hour = *((unsigned char*)0xFFEF);
pT->tm_mday = *((unsigned char*)0xFFF1);
pT->tm_mon = *((unsigned char*)0xFFF2);
pT->tm_year = *((unsigned char*)0xFFF3);
pT->tm_hundredth = *((unsigned char*)0xFFEC);
}
No i wisienka na torcie - logowanie na konsole:
sio_demo_irq4_logstash.c pisze:Kod: Zaznacz cały
void log( const char *fmt, ... ) {
va_list argptr;
struct tm t;
getSystemTime( &t );
sprintf(
szLogTxtBuff,
"[20%02X-%02X-%02XT%02X:%02X:%02X.%02X][%s][%d]",
t.tm_year,
t.tm_mon,
t.tm_mday,
t.tm_hour,
t.tm_min,
t.tm_sec,
t.tm_hundredth,
__FILE__,
__LINE__
);
putStr (1, szLogTxtBuff );
va_start(argptr, fmt);
vsprintf( szLogTxtBuff, fmt, argptr );
va_end(argptr);
putStr (1, szLogTxtBuff );
putStr (1, "\n" );
}
Tu jak widać podziękowałam wszelkim perwersyjnym makrom, jest sobie zwykła funkcja ze zmienną ilością argumentów i póki co działa to całkiem sprawnie.
Zależało mi niezmiernie, aby CA80 był w stanie pisać po konsoli, ale z drugiej strony niespecjalnie uśmiechało mi się dłubanie nowego, dedykowanego programiku. Z pomocą przyszedł Linux, poleceniem
bash pisze:Kod: Zaznacz cały
stty -F /dev/ttyUSB1 speed 38400
ustawiłam sobie szybkość transmisji, a ciągły odczyt strumienia danych realizowała jedna linijka:
bash pisze:Kod: Zaznacz cały
cat /dev/ttyUSB1
przygotowania - Meratronik V543
Tu także nieco na skróty, bo ja z tych w gorącej wodzie kąpanych - przeróbka (klon) istniejącego już oprogramowania dla Raspberry PI-Zero, będącego sercem interfejsu SCPI ( https://github.com/bienata/piv543scpi/b ... /v543lxi.c ) Po prostu wywaliłam całą cześć odpowiedzialną za komunikację sieciową, polecenie SCPI podaje się jako parametr wywołania programiku, wynik wystawiany jest na stdout, co umożliwia jego zabranie do dalszej obróbki. Kod po zabiegach sprowadza się do poniższego:
v543lxicli.c pisze:Kod: Zaznacz cały
int main( int argc, char *argv[] ) {
char commandBuffer[ 64 ];
char responseBuffer[ 64 ];
wiringPiSetup () ;
pinMode ( LINE_READY, INPUT );
pinMode ( LINE_CLK, OUTPUT );
pinMode ( LINE_LOAD, OUTPUT ); digitalWrite( LINE_LOAD, HIGH );
pinMode ( LINE_DATA, INPUT ); digitalWrite( LINE_CLK, HIGH );
pinMode ( LED_READY, OUTPUT );
pinMode ( LED_SCPI, OUTPUT );
memset( commandBuffer, 0x00, sizeof(commandBuffer) );
memset( responseBuffer, 0x00, sizeof(responseBuffer) );
onMeterReadyInterrupt();
strcpy ( commandBuffer, argv [1] );
makeLower( commandBuffer );
processScpiCommand ( trim( commandBuffer ), responseBuffer );
printf( responseBuffer );
return 0;
}
A jego zdalne wywołanie wygląda równie prosto:
bash pisze:Kod: Zaznacz cały
ssh -i ~/.ssh/otoja-dev pi@v543 '/home/pi/piv543scpi/v543lxicli ":measure:voltage:dc?" '
Aha, bo tu może ktoś mieć problem - aby uniknąć męczącej palcówki z hasłem do PI (co z resztą przy systemowym wywoływaniu poleceń byłoby cokolwiek trudne) dorobiłam logowanie via kluczyk, czyli na moim dużym linuksie - generacja i zaaplikowanie klucza:
bash pisze:Kod: Zaznacz cały
ssh-keygen -f ~/.ssh/otoja-dev
ssh-copy-id -i ~/.ssh/otoja-dev pi@v543
a potem, jazda! wszystko pięknie się woła:
bash pisze:Kod: Zaznacz cały
ssh -i ~/.ssh/otoja-dev pi@v543 'ls -l'
ssh -i ~/.ssh/otoja-dev pi@v543 'ps -A'
ssh -i ~/.ssh/otoja-dev pi@v543 'df -h'
Polecam powyższe, naprawdę fajnie to działa i można zdalne komendy wołać z poziomu skryptu zupełnie jak lokalne.
Na koniec obrazek jaki to war sobie był wtedy za oknem - 1.0045V to jest jak łatwo obliczyć - 50`C, no masakra normalnie.
dwa światy
Mamy jakoś ogarniętą niskopoziomową cześć sprzętową, można zatem przejść do obłaskawiania nowobogackiego oprogramowania stosu Elastic - ELK. Wymowny rysunek za poziomie praca-technika klasa 5 szkoły podstawowej mamy poniżej:
I tu kolejno - jak ta maszyneria działa.
Logstash zbiera dane (zdarzenia-eventy) z dwóch rozłącznych strumieni CA80 i V543, konfiguracja je koordynująca jest następująca:
logstash-6.5.0/config/pipelines.yml pisze:Kod: Zaznacz cały
- pipeline.id: ca80
queue.type: persisted
pipeline.workers: 1
pipeline.batch.size: 1
path.config: "ca80.conf"
- pipeline.id: v543
queue.type: persisted
pipeline.workers: 1
pipeline.batch.size: 1
path.config: "v543.conf"
Dane z Meratronika są pozyskiwane w sposób aktywny, Logstash z zadanym interwałem czasu wykonuje zdalne polecenie i asymiluje jego wyniki, tym zajmuje się sekcja input konfiguracji strumienia. Drobna trudność polega na tym, że idąc na łatwiznę pozostawiłam naukowy format float xE+/-y, co jest tak na wprost średnio zjadliwe przez funkcje formatujące Logstasha. Tak więc z sekcji filter konieczne było dopisanie konwertera formatów danych (tu w roli głównej Ruby), przy okazji odjęłam te 500mV czujnika MCP9700 wynikowo dostając temperaturę z stopniach C. Wyjście potoku - to przekazanie danych do instancji Elasticsearch, pod ustaloną arbitralnie nazwę indeksu. Wskazanie dodatkowego kodera rubydebug ułatwia niezmiernie wszelkie poprawki, na konsoli będziemy mieli ładnie sformatowane dokumenty JSON, dokładnie takie, jak polecą do bazy.
logstash-6.5.0/v543.conf pisze:Kod: Zaznacz cały
input {
exec {
command => "/home/otoja/elk/logstash-6.5.0/callV543.sh"
interval => 10
}
}
filter {
grok {
match => { "message" => "%{GREEDYDATA:fRawValue}" }
}
ruby {
code => "event.set( 'fTemperature', (( event.get('fRawValue').to_f - 0.5 ) * 100).round(2) )"
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => ["v543daq-%{+YYYY.MM.dd}"]
}
stdout {
codec => rubydebug
}
}
No i oczywiście próbka danych:
stdout pisze:Kod: Zaznacz cały
{
"fTemperature" => 23.42,
"@timestamp" => 2019-06-22T17:41:40.277Z,
"host" => "icequeen",
"command" => "/home/otoja/elk/logstash-6.5.0/callV543.sh",
"message" => "+7.342000E-01\n",
"@version" => "1",
"fRawValue" => "+7.342000E-01\n"
}
{
"fTemperature" => 23.41,
"@timestamp" => 2019-06-22T17:41:49.633Z,
"host" => "icequeen",
"command" => "/home/otoja/elk/logstash-6.5.0/callV543.sh",
"message" => "+7.341000E-01\n",
"@version" => "1",
"fRawValue" => "+7.341000E-01\n"
}
{
"fTemperature" => 23.4,
"@timestamp" => 2019-06-22T17:41:59.618Z,
"host" => "icequeen",
"command" => "/home/otoja/elk/logstash-6.5.0/callV543.sh",
"message" => "+7.340000E-01\n",
"@version" => "1",
"fRawValue" => "+7.340000E-01\n"
}
Teraz CA80, tu sprawa jest prosta, choć uprzedzam - wejście typu PIPE działa tylko na linux, konfiguracja:
logstash-6.5.0/ca80.conf pisze:Kod: Zaznacz cały
input {
pipe {
command => "cat /dev/ttyUSB1"
}
}
filter {
grok {
match => { "message" => "\[%{TIMESTAMP_ISO8601:localTime}\]\[%{GREEDYDATA:sourceFile}\]\[%{NUMBER:sourceLine}\]%{GREEDYDATA:anyText}" }
}
if [anyText] =~ /Tout/ {
mutate {
split => ["anyText", "= "]
add_field => { "outDoorTemp" => "%{anyText[1]}" }
}
mutate {
convert => { "outDoorTemp" => "integer" }
remove_field => [ "anyText" ]
remove_field => [ "sourceLine" ]
add_field => { "type" => "DAQ" }
}
}
}
output {
if [type] == "DAQ" {
elasticsearch {
hosts => ["localhost:9200"]
index => ["ca80daq-%{+YYYY.MM.dd}"]
}
} else {
elasticsearch {
hosts => ["localhost:9200"]
index => ["ca80log-%{+YYYY.MM.dd}"]
}
}
stdout {
codec => rubydebug
}
}
Tu wielkich czarów nie ma, choć sztuczka drobna jest - separacja strumienia na dwa indeksy. Zerknijmy na dane, jakich dostarcza CA80 - to pole `message`:
stdout pisze:Kod: Zaznacz cały
{
"localTime" => "2019-06-22T19:53:26.44",
"@version" => "1",
"outDoorTemp" => 23,
"type" => "DAQ",
"command" => "cat /dev/ttyUSB1",
"host" => "icequeen",
"@timestamp" => 2019-06-22T17:53:36.636Z,
"message" => "[2019-06-22T19:53:26.44][sio_demo_irq4_logstash.c][35]Tout = 23",
"sourceFile" => "sio_demo_irq4_logstash.c"
}
{
"command" => "cat /dev/ttyUSB1",
"localTime" => "2019-06-22T19:53:26.33",
"host" => "icequeen",
"anyText" => "raw ADC value: 49",
"@version" => "1",
"@timestamp" => 2019-06-22T17:53:36.539Z,
"sourceLine" => "35",
"message" => "[2019-06-22T19:53:26.33][sio_demo_irq4_logstash.c][35]raw ADC value: 49",
"sourceFile" => "sio_demo_irq4_logstash.c"
}
Widać, że raz jest to zwykły log aplikacji i wypisanie surowej wartości z konwertera A/C a innym razem (na zmianę) to wyliczona wartość temperatury w formie `Tout = xx`. Parser grok obrabia nadesłaną linijkę tak czy inaczej, ale potem filtr mutate sprawdza, czy zdarzenie zawiera informacje o temperaturze, jeżeli tak - dokonuje jej ekstrakcji (split, nowe pole outDoorTemp, konwersja na integer). Oczywiście, aby nie robić z indeksu ca80daq śmietniczki usuwane są informacje o nazwie pliku źródłowego i bieżącej linijce kodu oraz dodawany jest specjalny znacznik pozwalający sekcji out odseparować strumienie. W rzeczonej sekcji wyjściowej mamy prosty warunek - prawo/lewo i otrzymujemy w miarę typowy log aplikacyjny oraz zestaw wartości temperatury w dziedzinie czasu.
Pierwsze próby analizy zebranych danych (tu z Meratronika) wyglądały nieco pokracznie, ale wykazały sensowność dalszego drążenia tematu:
Aktywność strumieni danych z Meratronika i CA80 prezentuje się dobrze na domyślnym widoku Kibany - polecenie discovery:
No i oczywiście najfajniejsze - wszelkiej maści wykresy i statystyki, które możemy opracowywać na bazie już zgromadzonych i ciągle napływających danych. Zauważmy, że Kibana analizuje dane z wirtualnego zbioru `*daq*`, który powstał ze złączenia (przy pomocy zapytań do Elastica) dwóch zbiorów - od CA80 i V543, to jest właśnie potęga tego ustrojstwa:
Cała instalacja pracuje w czasie rzeczywistym, a automatycznie odświeżanie wykresów daje bardzo przyjemne efekty, naprawdę polecam zerknąć z oprogramowanie Elastic-a, jest doskonałe do poważnych zastosowań jak i do zwykłej, kreatywnej zabawy szczególnie jeżeli posiadamy już urządzonka potrafiące raportować jakieś fizyczne i zmienne w czasie wartości.
#slowanawiatr