Docker part three — Grafana, MySQL, sysbench
15.07.2018 | aktual.: 15.07.2018 21:32
Testowanie jakiegoś obiektu elementu i patrzenie na surowe dane może nie przemówić do nas za bardzo. Najlepiej posłużyć się tu jakąś graficzna reprezentacją danych, któa przedstawi nam dane w sposób wizualny. Ułatwi to analizę i pomoże wyciągnąć odpowiednie wnioski. Ja jestem themamuth i zapraszam was na trzecia cześć wpisu o wykorzystaniu kontenerów.
Previously in docker
W poprzednim moim wpisie zatrzymaliśmy się na uruchomieniu testu na naszej bazie danych z wykorzystaniem oltp_insert.lue w narzędziu sysbench. Jako output otrzymaliśmy następujące dane:
- Histogram prezentujący opóźnienie w milisekundach:
Latency histogram (values are in milliseconds) value ------------- distribution ------------- count 0.608 | 2 0.619 | 8 0.630 | 31 0.642 |* 114 0.654 |**** 336 0.665 |********* 766 0.677 |***************** 1412 0.690 |*************************** 2271 0.702 |************************************* 3081 0.715 |**************************************** 3366 0.728 |*************************************** 3262 0.741 |********************************** 2847 0.755 |***************************** 2439 0.768 |************************ 2005 0.782 |******************** 1660 0.797 |**************** 1352 0.811 |************** 1220 0.826 |*********** 950 0.841 |*********** 893 0.856 |********* 760 0.872 |******* 614 0.888 |******* 622 0.904 |****** 476 0.920 |***** 395 0.937 |**** 374 0.954 |**** 322 0.971 |*** 227 0.989 |** 165 1.007 |** 129 1.025 |* 112 1.044 |* 76 1.063 |* 66 1.082 | 41 1.102 |* 46 1.122 | 34 1.142 | 36 1.163 | 33 1.184 | 17 1.205 | 24 1.227 | 19 1.250 | 10 1.272 | 13 1.295 | 14 1.319 | 21 1.343 | 13 1.367 | 13 1.392 | 16 1.417 | 9 1.443 | 13 1.469 | 18 1.496 | 23 1.523 | 19 1.551 | 12 1.579 | 19 1.608 | 13 1.637 | 28 1.667 |* 53 1.697 |* 94 1.728 |** 205 1.759 |***** 435 1.791 |********* 748 1.824 |************* 1068 1.857 |**************** 1369 1.891 |***************** 1435 1.925 |***************** 1421 1.960 |*************** 1251 1.996 |************ 1000 2.032 |********** 810 2.069 |******** 657 2.106 |****** 488 2.145 |***** 385 2.184 |**** 301 2.223 |*** 214 2.264 |** 139 2.305 |** 135 2.347 |* 81 2.389 |* 70 2.433 |* 81 2.477 |* 61 2.522 | 39 2.568 |* 44 2.615 |* 49 2.662 | 42 2.710 | 40 2.760 | 36 2.810 | 35 2.861 | 40 2.913 | 22 2.966 | 33 3.020 | 29 3.075 | 21 3.130 | 30 3.187 | 38 3.245 | 26 3.304 | 32 3.364 | 39 3.425 | 24 3.488 | 34 3.551 | 20 3.615 | 17 3.681 | 21 3.748 | 30 3.816 | 18 3.885 | 30 3.956 | 30 4.028 | 25 4.101 | 35 4.176 | 41 4.252 |* 59 4.329 | 37 4.407 | 42 4.487 |* 44 4.569 | 39 4.652 | 32 4.737 | 24 4.823 | 19 4.910 | 22 4.999 | 21 5.090 | 23 5.183 | 16 5.277 | 16 5.373 | 14 5.470 | 16 5.570 | 11 5.671 | 21 5.774 | 3 5.879 | 9 5.986 | 11 6.095 | 15 6.205 | 19 6.318 | 14 6.433 | 11 6.550 | 10 6.669 | 13 6.790 | 9 6.913 | 7 7.039 | 5 7.167 | 5 7.297 | 9 7.430 | 8 7.565 | 3 7.702 | 1 7.842 | 6 7.985 | 7 8.130 | 2 8.277 | 3 8.428 | 4 8.581 | 5 8.737 | 7 8.895 | 6 9.057 | 8 9.222 | 5 9.389 | 9 9.560 | 9 9.734 | 13 9.910 | 5 10.090 | 12 10.274 | 8 10.460 | 9 10.651 | 6 10.844 | 12 11.041 | 6 11.242 | 8 11.446 | 4 11.654 | 5 11.866 | 1 12.081 | 11 12.301 | 3 12.524 | 2 12.752 | 2 12.984 | 2 13.219 | 2 13.460 | 4 13.704 | 2 13.953 | 1 14.465 | 2 14.728 | 3 14.995 | 6 15.268 | 3 15.545 | 4 15.828 | 3 16.115 | 2 16.408 | 7 16.706 | 4 17.010 | 3 17.319 | 3 17.633 | 3 17.954 | 2 18.280 | 2 18.612 | 4 18.950 | 1 19.295 | 2 19.645 | 2 20.002 | 2 21.496 | 1 22.689 | 2 24.827 | 1 32.525 | 1 56.839 | 1 108.685 | 1 112.670 | 1 114.717 | 1 130.128 | 1 139.846 | 2 147.608 | 1
Oraz pewne statystyki SQL:
SQL statistics: queries performed: read: 0 write: 47146 other: 0 total: 47146 transactions: 47146 (785.43 per sec.) queries: 47146 (785.43 per sec.) ignored errors: 0 (0.00 per sec.) reconnects: 0 (0.00 per sec.) General statistics: total time: 60.0236s total number of events: 47146 Latency (ms): min: 0.61 avg: 1.27 max: 148.58 95th percentile: 2.22 sum: 59831.08 Threads fairness: events (avg/stddev): 47146.0000/0.00 execution time (avg/stddev): 59.8311/0.00
Dowiedzieliśmy się z niego ile transakcji zapisu zostało wykonanych. Ile wykonanych w czasie jednej sekundy oraz jakie były opóźnienia (podawane w milisekundach). Spróbujmy teraz te dane przedstawić za pomocą narzędzia jakim jest Grafana. Grafana to narzędzie umożliwiające wizualizacje zebranych wyników/danych. Ale o tym trochę później.
Na początek postarajmy się wyciągnąć jakieś dane z naszego sysbench na których to, będziemy mogli popracować.
W wpisie tym będę dosyć dużo bazował na wiedzy przekazanej w poprzednich wpisach. Środowisko a dokładnie sposób jego utworzenia też jest poruszane w innym w wpisie blogowym. Informacje na temat wszystkich zagadnień odnajdziecie w dolnej części wpisu jako materiały powiązane oraz tu:
Wszystkie o docker:
Bash:
- Bash(ujący) w zbożu cz. 3 — namespace
- Bash(ujący) w zbożu cz. 2 — automatyzacja użytkownika
- Bash(ujący) w zbożu cz.1 - Zaczynamy
Wykonajmy nasz pierwszy test
Tak naprawdę to drugi :). Składnia testu jest nam znana. Przyjrzyjmy się jednej opcji -‑report-interval=N która, pozwala nam na generowanie dodatkowych informacji co N‑ty interwał czasowy. Zobaczmy jak to będzie wyglądało na naszym poprzednim przykładzie. Usuńmy z niego opcję histogramu, zmniejszmy czas wykonania z 60 na 30 sekund i dodajmy naszą opcję -‑report-interval.
root@cd980a8f8023:~# sysbench /usr/share/sysbench/oltp_insert.lua --threads=1 --mysql-host=mysql_db --mysql-user=root --mysql-password=rutek --mysql-port=3306 --mysql-db=sysbench_my_own_test --tables=4 --table-size=1000000 --report-interval=1 --time=30 --events=0 run sysbench 1.0.15 (using bundled LuaJIT 2.1.0-beta2) Running the test with following options: Number of threads: 1 Report intermediate results every 1 second(s) Initializing random number generator from current time Initializing worker threads... Threads started! [ 1s ] thds: 1 tps: 893.59 qps: 893.59 (r/w/o: 0.00/893.59/0.00) lat (ms,95%): 2.07 err/s: 0.00 reconn/s: 0.00 [ 2s ] thds: 1 tps: 945.20 qps: 945.20 (r/w/o: 0.00/945.20/0.00) lat (ms,95%): 2.00 err/s: 0.00 reconn/s: 0.00 [ 3s ] thds: 1 tps: 942.97 qps: 942.97 (r/w/o: 0.00/942.97/0.00) lat (ms,95%): 1.96 err/s: 0.00 reconn/s: 0.00 [ 4s ] thds: 1 tps: 948.04 qps: 948.04 (r/w/o: 0.00/948.04/0.00) lat (ms,95%): 2.00 err/s: 0.00 reconn/s: 0.00 [ 5s ] thds: 1 tps: 934.97 qps: 934.97 (r/w/o: 0.00/934.97/0.00) lat (ms,95%): 2.03 err/s: 0.00 reconn/s: 0.00 [ 6s ] thds: 1 tps: 934.04 qps: 934.04 (r/w/o: 0.00/934.04/0.00) lat (ms,95%): 2.00 err/s: 0.00 reconn/s: 0.00 [ 7s ] thds: 1 tps: 927.97 qps: 927.97 (r/w/o: 0.00/927.97/0.00) lat (ms,95%): 2.03 err/s: 0.00 reconn/s: 0.00 [ 8s ] thds: 1 tps: 934.98 qps: 934.98 (r/w/o: 0.00/934.98/0.00) lat (ms,95%): 2.00 err/s: 0.00 reconn/s: 0.00 [ 9s ] thds: 1 tps: 947.05 qps: 947.05 (r/w/o: 0.00/947.05/0.00) lat (ms,95%): 1.96 err/s: 0.00 reconn/s: 0.00 [ 10s ] thds: 1 tps: 943.99 qps: 943.99 (r/w/o: 0.00/943.99/0.00) lat (ms,95%): 2.00 err/s: 0.00 reconn/s: 0.00 [ 11s ] thds: 1 tps: 927.00 qps: 927.00 (r/w/o: 0.00/927.00/0.00) lat (ms,95%): 2.00 err/s: 0.00 reconn/s: 0.00 [ 12s ] thds: 1 tps: 911.92 qps: 911.92 (r/w/o: 0.00/911.92/0.00) lat (ms,95%): 2.07 err/s: 0.00 reconn/s: 0.00 [ 13s ] thds: 1 tps: 924.10 qps: 924.10 (r/w/o: 0.00/924.10/0.00) lat (ms,95%): 2.03 err/s: 0.00 reconn/s: 0.00 [ 14s ] thds: 1 tps: 926.01 qps: 926.01 (r/w/o: 0.00/926.01/0.00) lat (ms,95%): 2.03 err/s: 0.00 reconn/s: 0.00 [ 15s ] thds: 1 tps: 932.00 qps: 932.00 (r/w/o: 0.00/932.00/0.00) lat (ms,95%): 2.00 err/s: 0.00 reconn/s: 0.00 [ 16s ] thds: 1 tps: 872.01 qps: 872.01 (r/w/o: 0.00/872.01/0.00) lat (ms,95%): 2.11 err/s: 0.00 reconn/s: 0.00 [ 17s ] thds: 1 tps: 841.95 qps: 841.95 (r/w/o: 0.00/841.95/0.00) lat (ms,95%): 2.18 err/s: 0.00 reconn/s: 0.00 [ 18s ] thds: 1 tps: 860.01 qps: 860.01 (r/w/o: 0.00/860.01/0.00) lat (ms,95%): 2.11 err/s: 0.00 reconn/s: 0.00 [ 19s ] thds: 1 tps: 849.02 qps: 849.02 (r/w/o: 0.00/849.02/0.00) lat (ms,95%): 2.22 err/s: 0.00 reconn/s: 0.00 [ 20s ] thds: 1 tps: 880.02 qps: 880.02 (r/w/o: 0.00/880.02/0.00) lat (ms,95%): 2.07 err/s: 0.00 reconn/s: 0.00 [ 21s ] thds: 1 tps: 878.97 qps: 878.97 (r/w/o: 0.00/878.97/0.00) lat (ms,95%): 2.07 err/s: 0.00 reconn/s: 0.00 [ 22s ] thds: 1 tps: 896.01 qps: 896.01 (r/w/o: 0.00/896.01/0.00) lat (ms,95%): 2.07 err/s: 0.00 reconn/s: 0.00 [ 23s ] thds: 1 tps: 894.02 qps: 894.02 (r/w/o: 0.00/894.02/0.00) lat (ms,95%): 2.03 err/s: 0.00 reconn/s: 0.00 [ 24s ] thds: 1 tps: 932.91 qps: 932.91 (r/w/o: 0.00/932.91/0.00) lat (ms,95%): 2.00 err/s: 0.00 reconn/s: 0.00 [ 25s ] thds: 1 tps: 890.11 qps: 890.11 (r/w/o: 0.00/890.11/0.00) lat (ms,95%): 2.07 err/s: 0.00 reconn/s: 0.00 [ 26s ] thds: 1 tps: 868.94 qps: 868.94 (r/w/o: 0.00/868.94/0.00) lat (ms,95%): 2.14 err/s: 0.00 reconn/s: 0.00 [ 27s ] thds: 1 tps: 908.81 qps: 908.81 (r/w/o: 0.00/908.81/0.00) lat (ms,95%): 2.03 err/s: 0.00 reconn/s: 0.00 [ 28s ] thds: 1 tps: 875.17 qps: 875.17 (r/w/o: 0.00/875.17/0.00) lat (ms,95%): 2.11 err/s: 0.00 reconn/s: 0.00 [ 29s ] thds: 1 tps: 905.04 qps: 905.04 (r/w/o: 0.00/905.04/0.00) lat (ms,95%): 2.00 err/s: 0.00 reconn/s: 0.00 [ 30s ] thds: 1 tps: 891.99 qps: 891.99 (r/w/o: 0.00/891.99/0.00) lat (ms,95%): 2.07 err/s: 0.00 reconn/s: 0.00 SQL statistics: queries performed: read: 0 write: 27221 other: 0 total: 27221 transactions: 27221 (907.20 per sec.) queries: 27221 (907.20 per sec.) ignored errors: 0 (0.00 per sec.) reconnects: 0 (0.00 per sec.) General statistics: total time: 30.0035s total number of events: 27221 Latency (ms): min: 0.62 avg: 1.10 max: 15.08 95th percentile: 2.03 sum: 29904.48 Threads fairness: events (avg/stddev): 27221.0000/0.00 execution time (avg/stddev): 29.9045/0.00
Wygląda to obiecująco. Mamy informację ile w danej jednostce czasowej mieliśmy transakcji na sekundę (tps), zapytań (qps), odczyt, zapis, inne, opóźnienie, liczba błędów oraz powtórnych połączeń.
Grafana ma możliwość reprezentacji danych z rożnych źródeł. Jednym z nich może być mysql. Spróbujmy zatem nawiązać połączenie z naszą bazą danych.
Dane do Grafany
Dodajmy nasz kontener z grafaną
user@piotrskoska1:~$ docker container run -d --name=grafana -p 3000:3000 grafana/grafana
Przechodzimy pod adres http://localhost:3000 i powinniśmy zobaczyć okno logowania do naszej grafany - domyślne login i hasło to admin/admin
Zalogujmy się i dodajmy nasz data source który, będzie potrzebny do wyświetlania naszych danych.
Uzupełnijmy nasze dane w ustawieniach data source
Patrząc od góry pole Name to dowolna nazwa naszego źródła danych, obok tego pola możemy zaznaczyć czy będzie to domyślne źródło danych automatycznie wybierane do nowych wykresów. Idąc dalej natrafimy na pole Type, gdzie musimy określić nasz silnik baz danych. Tu wybieramy mysql. Jako Host w polu o takiej samej nazwie użyjmy nazwy naszego kontenera. Docker korzystając z własnego "serwera dns" rozwiąże nam tą nazwę i przypisze jej odpowiadający adres IP w danej chwili.
Dla osób nie w temacie jestem winien wyjaśnienia. Instalując docker w pierwszym wpisie na pewno zauważyłeś, że w naszych interfejsach sieciowych pojawił się nowy interfejs o nazwie docker0.
Jest to interfejs bridge którego, również możemy zobaczyć po wydaniu polecenia:
user@piotrskoska1:~$ docker network ls NETWORK ID NAME DRIVER SCOPE d45f1c685920 bridge bridge local e69b8dfa8878 host host local 3eebdb065e9f none null local
IP dla naszych kontenerów jest nadawane dynamicznie. Więc przy każdym zatrzymaniu kontenera i jego ponownym uruchomieniu adres IP zostanie przypisany pierwszy wolny. Zaczynając w naszym przypadku od 172.17.0.2. Może więc się tak zdarzyć że nasze kontenery uruchomią się w odwrotnej lub po prostu innej kolejności. Użycie nazwy hosta spowoduję, że zabezpieczymy się przed tą zmianą.
Wracając dalej do naszych ustawień Musimy wskazać naszą bazę danych. Możemy wybrać dowolną z już dostępnych, na razie jest to test połączeniowy. Na końcu nazwa użytkownika i hasło. Kikami przycisk Save & Test.
I do jasnej ciasnej nie działa, lookup nie odnajduje hosta - co do k....
Tu z mojej strony kolejne wyjaśnienie. Czy wydając polecenie docker network ls zauważyłeś coś nowego?
b01fff67d8d6 yaml_file_default bridge local
Oczywiście ID może być inne, ale jest to nasza sieć która, została utworzona dla naszych kontenerów stworzonych przy użyciu docker-compose. Bridge ten odnajdziemy również w naszych interfejsach sieciowych. Jego część nazwy powinna być identyczna do ID naszego bridge.
Teraz zobacz jaki adres ip ma nasz kontener z grafaną. Potem porównaj ten adres z adresem mysql_db.
user@piotrskoska1:~$ docker container inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' grafana 172.17.0.2 user@piotrskoska1:~$ docker container inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' mysql_db 172.18.0.3
Możemy wtedy zobaczyć, że hosty te są w rożnych sieciach. Tworząc kontener i nie wskazując sieci, zostanie on podpięty domyślnie pod bridge docker0. Usuńmy zatem nasz kontener z grafaną i podepnijmy go pod naszą sieć.
user@piotrskoska1:~$ docker container run -d --name=grafana --network="yaml_file_default" -p 3000:3000 grafana/grafana
Wykonajmy połączenie testowe do naszej bazy danych.
Jak widać teraz działa to bez zarzutów. Teraz tylko dodajmy nasz Testowy dashboard. U mnie wygląda to tak:
Mało to finezyjne i trochę bardziej dla sprawdzenia czy działa niż jakiś konkretny wykres. Poniżej też prezentuje przykładowy SQL
SELECT UNIX_TIMESTAMP(comment_date) as time_sec, comment_post_ID as value, 'comment_post_ID' as metric FROM wp_comments WHERE $__timeFilter(comment_date) ORDER BY comment_date ASC
Wykonajmy nasz trzeci już taki poważny test i spróbujmy zapisać zebrane dane do pliku. Wykorzystamy do tego celu to co już wiemy oraz zebraną wiedzę z poprzednich moich wpisów tworząc skrypt który, nam zapisze dane z sysbench do pliku. Plik wynikowy przerobimy na zrozumiały dla MySQL by na koniec wyświetlić nasze dane. Podsumujmy co będzie zawierał nasz skrypt:
- Skrypt będzie sprawdzał czy jest zainstalowany pakiet sysbench, jeżeli nie to zainstaluje go. Doinstaluje też dodatkowe biblioteki które, wykorzystamy w późniejszej edycji wyników by łatwiej je zapisać.
- Sprawdzi czy mamy połączenie z bazą danych oraz czy dana baza danych istnieje w celu wykonania opcji "prepare" przez sysbench. Jeżeli takie bazy nie będzie, to skrypt utworzy ją.
- Wykona sysbench z opcją prepare dla wybranego testu - testy nasze będziemy definiować jako parametry do naszego skryptu.
- Wykonanie sysbench opcji "run" i zapis danych do pliku.
- Wykonanie sysbench opcji "clenup"
- Przerobienie danych w pliku wynikowym, na dane zrozumiałe przez import mysql.
- Ostatecznie import danych do naszego serwera MySQL.
A zatem zabieram się do roboty.
Bash(ujący) w Docker
Zacznijmy od zdefiniowania kilku zmiennych globalnych. Które ułatwią nam późniejsze zmiany w naszych skryptach testowych.
#!/bin/bash # Globals var dbhost=mysql_db dbuser=root dbpass=rutek tables=4 table_size=2000000
Zmienna dbhost będzie przechowywać adres naszego serwera z bazą SQL. Oczywiście wiemy, że możemy odnieść się do naszego kontenera po nazwie i to też wykorzystamy. Zmienne dbuser i dbpass to dane pozwalające nam zalogować się do SQL odpowiednio nazwa użytkownika i hasło. Zmienna tables i table_size odnosi się do ilości tabel i rekordów w bazie danych wykorzystanych do naszego testu.
Nasza pierwsza funkcja to sprawdzenie czy sysbench jest zainstalowany. Funkcja jest uniwersalna i może posłużyć do sprawdzenia każdego innego oprogramowania. Zobaczmy jak taka przykładowa funkcja może wyglądać:
# Functions function is_installed () { local return_=1 if [[ $# -eq 1 ]]; then local return_=0 type $1 >/dev/null 2>&1 || return_=1 else echo "Wrong number off parameters. Example is_installed vim." exit 1 fi return $return_ }
Funkcja jest dosyć prosta. Sprawdza czy polecenie type zwróci, czy podane w tym przypadku jako parametr funkcji $1 jest aliasem, poleceniem, funkcją shellową czy skryptem. Jeżeli polecenie type nie odnajdzie szukanego polecenia podanego jako parametr wykona polecenie po || i zmienna lokalna return_ przyjmie wartość 1. W dalszym wpisie zobaczymy dlaczego. Postaramy się wykorzystać tą zwracaną wartość.
is_installed sysbench if [[ $? -eq 1 ]]; then apt-get update apt-get install -y curl curl -s <span id=<span class="hljs-string">"<span id="https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh">https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh</span>"</span>>https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh</span> | bash apt-get -y install sysbench fi
Wykorzystanie powyższej funkcji is_installed() może być następujące. Zwróci nam ona wartość 1 lub 0. My możemy ją przechwycić i w przypadku braku w naszym systemie komendy sysbench możemy ją wtedy zainstalować. Plus dodatkowe zależności potrzebne do instalacji naszego narzędzia.
Wykonaliśmy pierwsze nasze ustalenie o tym by w przypadku braku narzędzia sysbench doinstalować je. Zobaczmy jak nam pójdzie z innymi funkcjami.
Teraz dodajmy naszą bazę testową pod polecenie sysbench prepare.
function sql_connection () { if [[ $# -eq 0 ]]; then echo "Wrong number off parameters. Example sql_connection <DATABASE NAME>" exit 1 fi # set local dbname frome outside. local dbname=$1 # mysql - check if i can use a $dbname. mysql -h ${dbhost} -u ${dbuser} -p${dbpass} -e "USE ${dbname};" > /dev/null 2>&1 if [[ $? -eq 0 ]]; then # OK, I can use. local return_=0 else # NOT OK, i can't use. local return_=1 fi return $return_ } function create_db () { if [[ $# -eq 0 ]]; then echo "Wrong number off parameters. Example create_db <DATABASE NAME>" exit 1 fi # set local dbname from outside. local dbname=$1 # mysql - create database $dbname. mysql -h ${dbhost} -u ${dbuser} -p${dbpass} -e "CREATE DATABASE ${dbname} CHARACTER SET utf8 COLLATE utf8_general_ci;" > /dev/null 2>&1 if [[ $? -eq 0 ]]; then # created. echo "DATABASE ${dbname} created." local return_=0 else # fail. echo "DATABASE ${dbname} not created." local return_=1 fi return $return_ }
W powyższym fragmencie kodu mamy dwie funkcje sql_connection() i create_db(). Pierwsza funkcja sql_connection() ma za zadanie sprawdzić czy podana nazwa jako parametr opcja $1 dla tej funkcji istnieje jako baza danych. Zwracając odpowiednio 0 - jeżeli istnieje lub 1 jeżeli nie istnieje. Druga funkcja create_db() ma za zadanie utworzyć nasza bazę danych. Oczywiście ta funkcja zwraca odpowiednio 0 gdy utworzy naszą bazę lub 1 gdy z jakiś powodów naszej bazy nie utworzy. W obu przypadkach wszelkie komunikaty z mysql są wyłączone po przez opcję >/dev/null - możesz w każdej chwili komunikaty włączyć usuwając to przekierowanie. Dodajmy nasza funkcje main która, będzie nam to wszystko spinać.
function main () { is_installed sysbench if [[ $? -eq 1 ]]; then apt-get update apt-get install -y curl curl -s <span id=<span class="hljs-string">"<span id="https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh">https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh</span>"</span>>https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh</span> | bash apt-get -y install sysbench fi for dbname in "$@"; do sql_connection sysbench_$dbname if [[ $? -eq 1 ]]; then create_db sysbench_${dbname} fi sysbench_prepare_db ${dbname} sysbench_run_test ${dbname} sysbench_cleanup_db ${dbname} done }
Mamy już sprawdzenie czy jest zainstalowane nasze sysbench. Potem w pętli for przechwytujemy nasze parametry przekazane do funkcji main(). Weryfikujemy czy przekazane nazwy mają swój odpowiednik w postaci bazy danych. Potem wykorzystujemy jeszcze nie stworzone funkcje sysbench_prepare_db(), sysbench_run_test() oraz sysbench_cleanup_db(). Nasze trzy funkcje sysbench to nic innego jak polecenie sysbench przygotowujące bazę pod test. Dodając do naszej wcześniej utworzonej bazy tabele i rekordy w tych tabelach. Potem na tych rekordach wykonuje test, tu funkcja sysbench z opcją run. Na koniec czyści naszą bazę danych. Funkcja sysbench z opcją cleanup.
function sysbench_prepare_db () { # set test name for sysbench. local test_name=$1 sysbench /usr/share/sysbench/${test_name}.lua \ --threads=1 \ --mysql-host=${dbhost} \ --mysql-user=${dbuser} \ --mysql-password=${dbpass} \ --mysql-port=3306 \ --mysql-db=sysbench_${test_name} \ --tables=4 \ --table-size=${table_size} \ prepare } function sysbench_run_test () { # set test name for sysbench local test_name=$1 sysbench /usr/share/sysbench/${test_name}.lua \ --threads=1 \ --mysql-host=${dbhost} \ --mysql-user=${dbuser} \ --mysql-password=${dbpass} \ --mysql-port=3306 \ --mysql-db=sysbench_${test_name} \ --tables=${tables} \ --table-size=${table_size} \ --report-interval=1 \ --time=10 \ --events=0 \ run | tee -a ${test_name}.result } function sysbench_cleanup_db () { # set test name for sysbench local test_name=$1 sysbench /usr/share/sysbench/${test_name}.lua \ --threads=1 \ --mysql-host=${dbhost} \ --mysql-user=${dbuser} \ --mysql-password=${dbpass} \ --mysql-port=3306 \ --mysql-db=sysbench_${test_name} \ --tables=${tables} \ --table-size=${table_size} \ --report-interval=1 \ --time=10 \ --events=0 \ cleanup }
Gdy mamy już nasz wynik przydało by się go obrobić. Zabieg ten pomoże nam zaimportować dane do mysql. Zobaczmy jak to będzie wyglądać:
function import_to_mysql () { # set up temp file. oldresult="${1}.result" newresult="new.${oldresult}.result" # remove and change. cat ./${oldresult} | grep "\[" > ./${newresult} cat ./${oldresult} | sed -i -e 's/\[ //g' ./${newresult} cat ./${oldresult} | sed -i -e 's/s ] thds: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ tps: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ qps: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ lat (ms,95%): /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ err\/s: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ reconn\/s: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ (r\/w\/o: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/\//;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/);/;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/;/,/g' ./${newresult} # get information about line number. line_number=$(wc -l ./${newresult} | awk {'print $1'}) # we need to add timestemp to end of each line. for (( i=1; i<=$line_number; i+=1 )); do nowtime=$(echo -e "" | ts '%Y-%m-%d %H:%M:%S') cat ./${newresult} | sed -i -e "${i}s/$/,${nowtime}/" ./${newresult} echo $i sleep 1 done cat ./${oldresult} | sed -i -e 's/ *$//' ./${newresult} sql_connection result_$dbname if [[ $? -eq 1 ]]; then create_db result_${dbname} create_table result_${dbname} result_${dbname} fi while IFS="," read -r f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11; do mysql -h ${dbhost} -u ${dbuser} -p${dbpass} -e "USE result_${dbname}; INSERT INTO result_${dbname} (test_time_duration, test_thds, test_tps, test_qps, test_read, test_write, test_other, test_lat, test_err_s, test_reconn_s, test_timestamp) VALUE ('$f1', '$f2', '$f3', '$f4', '$f5', '$f6', '$f7', '$f8', '$f9', '$f10', '$f11');" > /dev/null 2>&1 done <"./${newresult}" }
Funkcja sysbench_run_test() wszystkie wyniki które, wyświetla zapisuje do pliku o nazwie $1.result. Plik ten jest otwierany i parsowany przez funkcje import_to_mysql(). By lepiej zrozumieć co się w nim dzieje zobaczmy nasz plik wynikowy $1.result:
sysbench 1.0.15 (using bundled LuaJIT 2.1.0-beta2) Running the test with following options: Number of threads: 1 Report intermediate results every 1 second(s) Initializing random number generator from current time Initializing worker threads... Threads started! [ 1s ] thds: 1 tps: 126.66 qps: 2031.47 (r/w/o: 1777.16/0.00/254.31) lat (ms,95%): 11.24 err/s: 0.00 reconn/s: 0.00 [ 2s ] thds: 1 tps: 170.05 qps: 2725.86 (r/w/o: 2385.75/0.00/340.11) lat (ms,95%): 7.70 err/s: 0.00 reconn/s: 0.00 [ 3s ] thds: 1 tps: 171.99 qps: 2752.84 (r/w/o: 2408.86/0.00/343.98) lat (ms,95%): 7.30 err/s: 0.00 reconn/s: 0.00 [ 4s ] thds: 1 tps: 174.01 qps: 2780.15 (r/w/o: 2432.13/0.00/348.02) lat (ms,95%): 7.56 err/s: 0.00 reconn/s: 0.00 [ 5s ] thds: 1 tps: 178.00 qps: 2845.96 (r/w/o: 2489.96/0.00/355.99) lat (ms,95%): 7.04 err/s: 0.00 reconn/s: 0.00 [ 6s ] thds: 1 tps: 176.00 qps: 2825.00 (r/w/o: 2473.00/0.00/352.00) lat (ms,95%): 7.43 err/s: 0.00 reconn/s: 0.00 [ 7s ] thds: 1 tps: 181.00 qps: 2893.06 (r/w/o: 2531.06/0.00/362.01) lat (ms,95%): 7.70 err/s: 0.00 reconn/s: 0.00 [ 8s ] thds: 1 tps: 178.00 qps: 2850.99 (r/w/o: 2495.00/0.00/356.00) lat (ms,95%): 7.43 err/s: 0.00 reconn/s: 0.00 [ 9s ] thds: 1 tps: 187.01 qps: 2981.10 (r/w/o: 2607.09/0.00/374.01) lat (ms,95%): 7.30 err/s: 0.00 reconn/s: 0.00 [ 10s ] thds: 1 tps: 183.98 qps: 2943.67 (r/w/o: 2575.71/0.00/367.96) lat (ms,95%): 7.30 err/s: 0.00 reconn/s: 0.00 SQL statistics: queries performed: read: 24192 write: 0 other: 3456 total: 27648 transactions: 1728 (172.62 per sec.) queries: 27648 (2761.87 per sec.) ignored errors: 0 (0.00 per sec.) reconnects: 0 (0.00 per sec.) General statistics: total time: 10.0087s total number of events: 1728 Latency (ms): min: 3.67 avg: 5.79 max: 48.70 95th percentile: 8.13 sum: 10001.95 Threads fairness: events (avg/stddev): 1728.0000/0.00 execution time (avg/stddev): 10.0020/0.00
Widzimy, że polecenie sysbench zapisało do tego pliku mnóstwo danych. Jakieś informacje nagłówkowe, wyniki w poszczególnych jednostkach czasowych oraz samo podsumowanie na koniec. Nas z tego pliku interesować będą parametry w poszczególnych jednostkach czasowych. Postarajmy się wydobyć tylko to co nas interesuje. Zadanie to realizuje pierwsze polecenie cat. Które zwraca nam tylko te rekordy które, zaczynają się na "[ " i zapisane do nowego tymczasowego pliku. Później po kolei pozbywamy się niepotrzebnych znaków. Interesują nas tylko wartości. Zatem wszystkie thds:, tps:, qps:, itd usuwamy a w ich miejsce wstawiamy znak który, będzie oddzielał nam poszczególne wartości. W pętli for dodajemy do każdego wiersza na końcu wartość timestamp, która pomoże nam wyświetlać wartości w przedziale czasowym. Opóźnienie czasowe ustawiłem na jedną sekundę. Tak samo jak nasz interwał czasowy w teście. Ostatecznie importujemy nasze dane do mysql po przez pętle while.
function main () { is_installed sysbench if [[ $? -eq 1 ]]; then apt-get update apt-get install -y curl curl -s <span id=<span class="hljs-string">"<span id="https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh">https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh</span>"</span>>https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh</span> | bash apt-get -y install sysbench apt-get -y install moreutils fi for dbname in "$@"; do sql_connection sysbench_$dbname if [[ $? -eq 1 ]]; then create_db sysbench_${dbname} fi sysbench_prepare_db ${dbname} sysbench_run_test ${dbname} sysbench_cleanup_db ${dbname} import_to_mysql ${dbname} done }
Możemy już naszą funkcje main() wzbogacić o import_to_mysql(). A tak prezentuje się całość.
#!/bin/bash # Globals var dbhost=mysql_db dbuser=root dbpass=rutek tables=4 table_size=2000000 # Functions function is_installed () { # set return_ to 1. local return_=1 # chceck if script has one parameter. if [[ $# -eq 1 ]]; then # set return_ to 0 if exists. local return_=0 # set return_1 to 1 if command not exists. type $1 >/dev/null 2>&1 || return_=1 else # display a error message. echo "Wrong number off parameters. Example is_installed vim." exit 1 fi return $return_ } function sql_connection () { if [[ $# -eq 0 ]]; then echo "Wrong number off parameters. Example sql_connection <DATABASE NAME>" exit 1 fi # set local dbname frome outside. local dbname=$1 # mysql - check if i can use a $dbname. mysql -h ${dbhost} -u ${dbuser} -p${dbpass} -e "USE ${dbname};" > /dev/null 2>&1 if [[ $? -eq 0 ]]; then # OK, I can use. local return_=0 else # NOT OK, i can't use. local return_=1 fi return $return_ } function create_db () { if [[ $# -eq 0 ]]; then echo "Wrong number off parameters. Example create_db <DATABASE NAME>" exit 1 fi # set local dbname from outside. local dbname=$1 # mysql - create database $dbname. mysql -h ${dbhost} -u ${dbuser} -p${dbpass} -e "CREATE DATABASE ${dbname} CHARACTER SET utf8 COLLATE utf8_general_ci;" > /dev/null 2>&1 if [[ $? -eq 0 ]]; then # created. echo "DATABASE ${dbname} created." local return_=0 else # fail. echo "DATABASE ${dbname} not created." local return_=1 fi return $return_ } function create_table () { if [[ $# -lt 2 ]]; then echo "Wrong number off parameters. Example create_table <TABLE NAME><DATABASE NAME>" exit 1 fi # set table name and db use for created table. local table_name=$1 local db=$2 # mysql - created table. mysql -h ${dbhost} -u ${dbuser} -p${dbpass} -e "USE ${db}; CREATE TABLE ${table_name} (test_time_duration INT(10), test_thds FLOAT(10), test_tps FLOAT(10), test_qps FLOAT(10), test_read FLOAT(10), test_write FLOAT(10), test_other FLOAT(10), test_lat FLOAT(10), test_err_s FLOAT(10), test_reconn_s FLOAT(10), test_timestamp DATETIME NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;" > /dev/null 2>&1 if [[ $? -eq 0 ]]; then # created. echo "TABLE ${table_name} created in ${db}" local return_=0 else # not created echo "TABLE ${table_name} created in ${db}" local return_=1 fi return $return_ } function sysbench_prepare_db () { # set test name for sysbench. local test_name=$1 sysbench /usr/share/sysbench/${test_name}.lua \ --threads=1 \ --mysql-host=${dbhost} \ --mysql-user=${dbuser} \ --mysql-password=${dbpass} \ --mysql-port=3306 \ --mysql-db=sysbench_${test_name} \ --tables=4 \ --table-size=${table_size} \ prepare } function sysbench_run_test () { # set test name for sysbench local test_name=$1 sysbench /usr/share/sysbench/${test_name}.lua \ --threads=1 \ --mysql-host=${dbhost} \ --mysql-user=${dbuser} \ --mysql-password=${dbpass} \ --mysql-port=3306 \ --mysql-db=sysbench_${test_name} \ --tables=${tables} \ --table-size=${table_size} \ --report-interval=1 \ --time=10 \ --events=0 \ run | tee -a ${test_name}.result } function sysbench_cleanup_db () { # set test name for sysbench local test_name=$1 sysbench /usr/share/sysbench/${test_name}.lua \ --threads=1 \ --mysql-host=${dbhost} \ --mysql-user=${dbuser} \ --mysql-password=${dbpass} \ --mysql-port=3306 \ --mysql-db=sysbench_${test_name} \ --tables=${tables} \ --table-size=${table_size} \ --report-interval=1 \ --time=10 \ --events=0 \ cleanup } function import_to_mysql () { # set up temp file. oldresult="${1}.result" newresult="new.${oldresult}.result" # remove and change. cat ./${oldresult} | grep "\[" > ./${newresult} cat ./${oldresult} | sed -i -e 's/\[ //g' ./${newresult} cat ./${oldresult} | sed -i -e 's/s ] thds: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ tps: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ qps: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ lat (ms,95%): /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ err\/s: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ reconn\/s: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/ (r\/w\/o: /;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/\//;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/);/;/g' ./${newresult} cat ./${oldresult} | sed -i -e 's/;/,/g' ./${newresult} # get information about line number. line_number=$(wc -l ./${newresult} | awk {'print $1'}) # we need to add timestemp to end of each line. for (( i=1; i<=$line_number; i+=1 )); do nowtime=$(echo -e "" | ts '%Y-%m-%d %H:%M:%S') cat ./${newresult} | sed -i -e "${i}s/$/,${nowtime}/" ./${newresult} echo $i sleep 1 done cat ./${oldresult} | sed -i -e 's/ *$//' ./${newresult} sql_connection result_$dbname if [[ $? -eq 1 ]]; then create_db result_${dbname} create_table result_${dbname} result_${dbname} fi while IFS="," read -r f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11; do mysql -h ${dbhost} -u ${dbuser} -p${dbpass} -e "USE result_${dbname}; INSERT INTO result_${dbname} (test_time_duration, test_thds, test_tps, test_qps, test_read, test_write, test_other, test_lat, test_err_s, test_reconn_s, test_timestamp) VALUE ('$f1', '$f2', '$f3', '$f4', '$f5', '$f6', '$f7', '$f8', '$f9', '$f10', '$f11');" > /dev/null 2>&1 done <"./${newresult}" } function main () { is_installed sysbench if [[ $? -eq 1 ]]; then apt-get update apt-get install -y curl curl -s <span id=<span class="hljs-string">"<span id="https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh">https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh</span>"</span>>https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh</span> | bash apt-get -y install sysbench apt-get -y install moreutils fi for dbname in "$@"; do sql_connection sysbench_$dbname if [[ $? -eq 1 ]]; then create_db sysbench_${dbname} fi sysbench_prepare_db ${dbname} sysbench_run_test ${dbname} sysbench_cleanup_db ${dbname} import_to_mysql ${dbname} done } # Main main oltp_read_only
Po wywołaniu tego skryptu w naszej bazie danych powinna pojawić się baza danych prezentująca się w następujący sposób:
Spróbujmy wyświetlić nasze dane w grafanie. Połączmy się z nowo utworzoną baza danych.
Ustawiamy nasz graf po przez zdefiniowanie dwóch przykładowych zapytań dla read oraz qps:
Zapytanie SQL dla wyświetlenia wartości read:
SELECT UNIX_TIMESTAMP(test_timestamp) as time_sec, test_read as value, 'read' as metric FROM result_oltp_read_only WHERE $__timeFilter(test_timestamp) ORDER BY test_timestamp ASC
Zapytanie SQL dla wyświetlenia wartości qps:
SELECT UNIX_TIMESTAMP(test_timestamp) as time_sec, test_qps as value, 'qps' as metric FROM result_oltp_read_only WHERE $__timeFilter(test_timestamp) ORDER BY test_timestamp ASC
A o to jak można te dane zaprezentować:
Oczywiście wykres jest przykładowy. Odkrywanie Grafana zostawię już wam. Oczywiście czekam na wsze opinie i uwagi pod wszelką postacią.
W następnym wpisie z serii
To już koniec. Zapraszam do eksperymentowania z narzędziami przedstawionymi w wpisie. Następny wpis kontynuujący serię pokażę się nice później. Przygotowanie jego bardzo mnie pochłonęło i trochę w nim ugrzęzłem :) :) :)