Obsah
1. Monitoring služeb a mikroslužeb psaných v Go nástrojem Prometheus
4. Konfigurace dashboardů s výsledky monitoringu
5. Upozornění v případě, že se detekuje problém
6. Základní konfigurace služby poskytující standardní metriky
7. První varianta služby poskytující standardní metriky
8. Přidání čítače (counter) do množiny sledovaných metrik
10. Automatická registrace čítače
11. Metrika reprezentující libovolnou číselnou hodnotu
12. Metrika založená na sledování sekvence číselných hodnot
14. Intervaly s rozdílnou šířkou
15. Mapa čítačů a její použití pro sledování počtu přístupů na stránky
16. Výpočet doby trvání vytvoření odpovědi HTTP serveru
17. Komplikovanější příklad – HTTP server poskytující více metrik
18. Repositář s demonstračními příklady
1. Monitoring služeb a mikroslužeb psaných v Go nástrojem Prometheus
„Java is 20 years old, mature, and comes with unbeatable tooling and monitoring capabilities. At the very beginning, Java already incorporated microservice concepts with the Jini / JXTA frameworks mixed with no-SQL databases like e.g. JavaSpaces. As often – Java was just 15 years too early. The market was not ready for the technology back then. However, all the design principles from 1999 still do apply today. We don't have re-invent the wheel.“
Ve třicáté sedmé části seriálu o programovacím jazyce Go se seznámíme s některými možnostmi nabízenými balíčkem github.com/prometheus/client_golang/prometheus/. Již samotný název tohoto balíčku naznačuje, že se jedná o klienta určeného pro nástroj Prometheus, jenž se v současnosti velmi často používá pro sbírání metrik z běžících služeb, mikroslužeb atd. Tyto metriky (metrikou může být počet připojených klientů, aktuálně obsazená kapacita haldy – heapu, atd.) je následně možné filtrovat a analyzovat, přičemž výsledky mohou být vizualizovány například nástrojem Grafana (propojení Prometheus + Grafana je ostatně taktéž velmi časté). Jedno z typických použití Promethea je sledování (mikro)služby nasazené například v clusteru. Taková služba kromě své vlastní funkcionality nabízí jednoduché rozhraní REST API (HTTP/HTTPS) s typicky jediným koncovým bodem nazvaným /metrics. Nástroj Prometheus z tohoto koncového bodu metriky načítá a následně je nakonfigurovaným způsobem zpracovává.
Samozřejmě je možné implementovat nabídku metrik pro výše zmíněný koncový bod /metrics ručně, přesněji řečeno explicitně naprogramovaným handlerem HTTP serveru, ovšem použití klienta github.com/prometheus/client_golang/prometheus/ je mnohem jednodušší, umožňuje vytvářet histogramy a další složitější mechanismy a navíc je ve světě Go dnes již téměř idiomatické, takže zdrojovým kódům budou dobře rozumět i další vývojáři.
Obrázek 1: Logo nástroje Prometheus.
2. Nástroj Prometheus
Systém Prometheus používá databázi, do které se ukládají prakticky libovolné (číselné) hodnoty, které jsou opatřeny časovým razítkem, kromě toho i jménem metriky (ta musí být unikátní, navíc by neměla obsahovat mezery) a návěštím (label) umožňujícím podrobnější dělení hodnot, například podle toho, v jakém prostředí je měření prováděno, jakého koncového bodu REST API se měření týká atd. To znamená, že pro zvolenou metriku, popř. pro metriku a návěští je možné získat celou časovou posloupnost s hodnotami, vracet se do minulosti, získat informace pro zvolené časové období apod. Samotné hodnoty jsou interně zpracovávány jako datový typ double/float64 (konkrétní jednotka již záleží na interpretaci dat) a časová razítka mají milisekundovou přesnost, což by mělo být pro účely tohoto nástroje dostačující, už jen z toho důvodu, že samotné pořízení záznamu přes API klienta Promethea má určitou časovou složitost a zpoždění.
Prometheus se od některých podobně koncipovaných nástrojů odlišuje zejména tím, že používá takzvaný PULL model: jednotlivé služby pouze vystavují své metriky na určeném a nakonfigurovaném koncovém bodě REST API (typicky se jedná o koncový bod /metrics zmíněný v úvodní kapitole) a Prometheus sám aktivně tyto metriky čte. To je poměrně důležitá vlastnost, protože zjednodušuje tvorbu samotné aplikace – ta totiž může být pasivní, nemusí se jí v konfiguračním souboru nebo v proměnné prostředí předávat adresa Promethea, nemusí se řešit situace, kdy není Prometheus dostupný atd. Rozdíl mezi PUSH a PULL modelem jsou stručně popsány v článku Go App Monitoring: expvar, Prometheus and StatsD.
Pro nástroj Prometheus v současnosti existují čtyři oficiálně podporovaní klienti:
- Go
- Java, popř. Scala (a tím pádem i další jazyky nad JVM, včetně Clojure)
- Python
- Ruby
Dnes se zaměříme na popis klienta určeného pro programovací jazyk Go.
Kromě toho však existuje i velké množství sice oficiálně nepodporovaných, ale většinou plně funkčních klientů určených pro další programovací jazyky:
- BASH
- C++
- Common Lisp
- Elixir
- Erlang
- Haskell
- Lua
- .NET / C#
- Node.js
- Perl
- PHP
- Rust
3. Dotazovací jazyk PromQL
Důležitou součástí nástroje Prometheus je i PromQL, což je relativně snadno použitelný dotazovací jazyk určený pro získání potřebných metrik, agregaci výsledků přečtených z těchto metrik apod. Můžeme si ostatně uvést příklad jednoduchého dotazu vytvořeného v tomto jazyce, který vrátí časovou posloupnost hodnot trvání přípravy odpovědi na HTTP požadavky (předpokládejme, že jméno této metriky je „http_requests_total“):
http_requests_total
Celkovou dobu trvání všech požadavků a průměrnou dobu vytvoření jednoho požadavku získáme stejně snadno:
sum(http_requests_total) avg(http_requests_total)
V dotazu ovšem můžeme provést i jemnější dělení, například podle návěští:
http_requests_total{job="prometheus",group="canary"}
V jazyku PromQL je možné využívat například i regulární výrazy, což nám umožňuje získat časy odpovědí na HTTP dotazy typu GET, ovšem pouze pro zvolená prostředí (staging, testing, development):
http_requests_total{environment=~"staging|testing|development",method!="GET"}
Dotazovací jazyk PromQL je primárně určen pro práci s časovými řadami, takže nepřekvapí ani dobrá podpora specifikace časového období, pro které potřebujeme data získat. Výsledky trvání vyřízení HTTP dotazů typu GET za posledních pět minut by se získaly takto:
http_requests_total{job="prometheus"}[5m]
Výsledky za posledních třicet minut, ovšem s rozlišením jedné minuty, přečteme následujícím dotazem:
rate(http_requests_total[5m])[30m:1m]
4. Konfigurace dashboardů s výsledky monitoringu
Kromě dotazů zapisovaných v jazyku PromQL je podporován již zmíněný výstup ve formě plně konfigurovatelných grafů, z nichž se posléze vytváří různé dashboardy, které sledují stav celého systému či jeho jednotlivých částí. Pro tento účel se používá Grafana. Pokud chcete vidět, jak může vypadat výstup z kombinace nástrojů Prometheus+Grafana, můžete se podívat například na obrázek na adrese https://prometheus.io/assets/grafana_prometheus.png, popř. na celou galerii různých dashboardů na adrese https://duckduckgo.com/?q=grafana+board&t=ffab&iax=images&ia=images.
Obrázek 2: Logo nástroje Grafana.
Další podporovanou možností výstupu (reportu) představují standardní šablony Go (https://golang.org/pkg/text/template/).
5. Upozornění v případě, že se detekuje problém
Možnost zobrazení záznamů (časových řad s hodnotami) je samozřejmě velmi užitečná, ovšem v praxi mnohdy potřebujeme, aby byli administrátoři nebo tým devops varováni ve chvíli, kdy dojde k určité události, například když klesne hodnota volné RAM, markantně se zvýší čas odezvy serveru atd. Pro tento účel používá systém Prometheus další komponentu nazvanou příhodně Alertmanager, kterou je možné nakonfigurovat takovým způsobem, aby na naprogramované události (či mnohem častěji na jejich souběh) nějakým vhodným způsobem reagovala. Samozřejmě je možné zvolit například poslání zprávy přes připravená rozhraní (Slack, HipChat, včetně běžného e-mailu) nebo lze nakonfigurovat poslání obecnějšího webhooku do prakticky libovolné služby.
6. Základní konfigurace služby poskytující standardní metriky
Nyní se konečně podíváme na demonstrační příklady ukazující, jakým způsobem je možné metriky určené pro Prometheus nabídnout (vystavit) ze služby, resp. přesněji řečeno z libovolného procesu, vyvinutého v programovacím jazyce Go. Nejdříve je pochopitelně nutné získat samotný balíček, který nám umožní do služby přidat nový koncový bod REST API s metrikami. Instalaci tohoto balíčku zajistíme jednoduše příkazem:
$ go get -v github.com/prometheus/client_golang/prometheus github.com/golang/protobuf (download) github.com/prometheus/client_model (download) github.com/prometheus/common (download) github.com/matttproud/golang_protobuf_extensions (download) github.com/prometheus/procfs (download) github.com/beorn7/perks/quantile github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg github.com/golang/protobuf/proto github.com/prometheus/common/model github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util github.com/prometheus/procfs github.com/matttproud/golang_protobuf_extensions/pbutil github.com/prometheus/client_model/go github.com/prometheus/client_golang/prometheus/internal github.com/prometheus/common/expfmt github.com/prometheus/client_golang/prometheus
Aplikace, která chce metriky nabízet, tento balíček nejdříve importuje:
import "github.com/prometheus/client_golang/prometheus/promhttp"
Následně je nutné zaregistrovat handler HTTP serveru, který bude obsluhovat dotazy posílané na koncový bod /metrics:
http.Handle("/metrics", promhttp.Handler())
A nakonec aplikace musí spustit samotný HTTP server, popř. zareagovat na situaci, kdy se server z nějakého důvodu nespustí:
err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) os.Exit(2) }
Na zmíněném koncovém bodě nyní budou dostupné základní metriky automaticky poskytované klientem Promethea.
7. První varianta služby poskytující standardní metriky
Nyní již máme k dispozici všechny informace potřebné pro to, abychom vytvořili triviální aplikaci, která na koncovém bodě /metrics bude nabízet metriky zpracovatelné (mj. i) nástrojem Prometheus. Úplný zdrojový kód této aplikace vypadá následovně:
package main import ( "fmt" "net/http" "github.com/prometheus/client_golang/prometheus/promhttp" "os" ) func main() { fmt.Println("Initializing HTTP server") http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) os.Exit(2) } }
Aplikaci přeložíme a spustíme:
$ go run 01_basic_metrics.go
Aplikace by nyní pouze měla vypsat, že se inicializoval HTTP server. Ten naslouchá na portu 8080, takže z dalšího terminálu zkusíme načíst všechny metriky poskytované klientem Promethea. Použijeme přitom standardní nástroj curl:
$ curl localhost:8080/metrics
Na standardní výstup by se po spuštění tohoto příkazu mělo vypsat množství řádků s komentáři i jednotlivými metrikami. Tyto metriky se týkají především samotného běžícího procesu, práce automatického správce paměti atd. Povšimněte si, že se jedná o velmi jednoduchý a současně i relativně snadno parsovatelný formát:
# HELP go_gc_duration_seconds A summary of the GC invocation durations. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 0 go_gc_duration_seconds{quantile="0.25"} 0 go_gc_duration_seconds{quantile="0.5"} 0 go_gc_duration_seconds{quantile="0.75"} 0 go_gc_duration_seconds{quantile="1"} 0 go_gc_duration_seconds_sum 0 go_gc_duration_seconds_count 0 # HELP go_goroutines Number of goroutines that currently exist. # TYPE go_goroutines gauge go_goroutines 7 # HELP go_info Information about the Go environment. # TYPE go_info gauge go_info{version="go1.13"} 1 # HELP go_memstats_alloc_bytes Number of bytes allocated and still in use. # TYPE go_memstats_alloc_bytes gauge go_memstats_alloc_bytes 460480 # HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed. # TYPE go_memstats_alloc_bytes_total counter go_memstats_alloc_bytes_total 460480 # HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table. # TYPE go_memstats_buck_hash_sys_bytes gauge go_memstats_buck_hash_sys_bytes 2724 # HELP go_memstats_frees_total Total number of frees. # TYPE go_memstats_frees_total counter go_memstats_frees_total 95 # HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started. # TYPE go_memstats_gc_cpu_fraction gauge go_memstats_gc_cpu_fraction 0 # HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata. # TYPE go_memstats_gc_sys_bytes gauge go_memstats_gc_sys_bytes 2.240512e+06 # HELP go_memstats_heap_alloc_bytes Number of heap bytes allocated and still in use. # TYPE go_memstats_heap_alloc_bytes gauge go_memstats_heap_alloc_bytes 460480 # HELP go_memstats_heap_idle_bytes Number of heap bytes waiting to be used. # TYPE go_memstats_heap_idle_bytes gauge go_memstats_heap_idle_bytes 6.53312e+07 # HELP go_memstats_heap_inuse_bytes Number of heap bytes that are in use. # TYPE go_memstats_heap_inuse_bytes gauge go_memstats_heap_inuse_bytes 1.35168e+06 # HELP go_memstats_heap_objects Number of allocated objects. # TYPE go_memstats_heap_objects gauge go_memstats_heap_objects 1760 # HELP go_memstats_heap_released_bytes Number of heap bytes released to OS. # TYPE go_memstats_heap_released_bytes gauge go_memstats_heap_released_bytes 6.53312e+07 # HELP go_memstats_heap_sys_bytes Number of heap bytes obtained from system. # TYPE go_memstats_heap_sys_bytes gauge go_memstats_heap_sys_bytes 6.668288e+07 # HELP go_memstats_last_gc_time_seconds Number of seconds since 1970 of last garbage collection. # TYPE go_memstats_last_gc_time_seconds gauge go_memstats_last_gc_time_seconds 0 # HELP go_memstats_lookups_total Total number of pointer lookups. # TYPE go_memstats_lookups_total counter go_memstats_lookups_total 0 # HELP go_memstats_mallocs_total Total number of mallocs. # TYPE go_memstats_mallocs_total counter go_memstats_mallocs_total 1855 # HELP go_memstats_mcache_inuse_bytes Number of bytes in use by mcache structures. # TYPE go_memstats_mcache_inuse_bytes gauge go_memstats_mcache_inuse_bytes 13888 # HELP go_memstats_mcache_sys_bytes Number of bytes used for mcache structures obtained from system. # TYPE go_memstats_mcache_sys_bytes gauge go_memstats_mcache_sys_bytes 16384 # HELP go_memstats_mspan_inuse_bytes Number of bytes in use by mspan structures. # TYPE go_memstats_mspan_inuse_bytes gauge go_memstats_mspan_inuse_bytes 18632 # HELP go_memstats_mspan_sys_bytes Number of bytes used for mspan structures obtained from system. # TYPE go_memstats_mspan_sys_bytes gauge go_memstats_mspan_sys_bytes 32768 # HELP go_memstats_next_gc_bytes Number of heap bytes when next garbage collection will take place. # TYPE go_memstats_next_gc_bytes gauge go_memstats_next_gc_bytes 4.473924e+06 # HELP go_memstats_other_sys_bytes Number of bytes used for other system allocations. # TYPE go_memstats_other_sys_bytes gauge go_memstats_other_sys_bytes 789852 # HELP go_memstats_stack_inuse_bytes Number of bytes in use by the stack allocator. # TYPE go_memstats_stack_inuse_bytes gauge go_memstats_stack_inuse_bytes 425984 # HELP go_memstats_stack_sys_bytes Number of bytes obtained from system for stack allocator. # TYPE go_memstats_stack_sys_bytes gauge go_memstats_stack_sys_bytes 425984 # HELP go_memstats_sys_bytes Number of bytes obtained from system. # TYPE go_memstats_sys_bytes gauge go_memstats_sys_bytes 7.0191104e+07 # HELP go_threads Number of OS threads created. # TYPE go_threads gauge go_threads 8 # HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. # TYPE process_cpu_seconds_total counter process_cpu_seconds_total 0 # HELP process_max_fds Maximum number of open file descriptors. # TYPE process_max_fds gauge process_max_fds 1024 # HELP process_open_fds Number of open file descriptors. # TYPE process_open_fds gauge process_open_fds 10 # HELP process_resident_memory_bytes Resident memory size in bytes. # TYPE process_resident_memory_bytes gauge process_resident_memory_bytes 8.06912e+06 # HELP process_start_time_seconds Start time of the process since unix epoch in seconds. # TYPE process_start_time_seconds gauge process_start_time_seconds 1.57052836543e+09 # HELP process_virtual_memory_bytes Virtual memory size in bytes. # TYPE process_virtual_memory_bytes gauge process_virtual_memory_bytes 5.04848384e+08 # HELP process_virtual_memory_max_bytes Maximum amount of virtual memory available in bytes. # TYPE process_virtual_memory_max_bytes gauge process_virtual_memory_max_bytes -1 # HELP promhttp_metric_handler_requests_in_flight Current number of scrapes being served. # TYPE promhttp_metric_handler_requests_in_flight gauge promhttp_metric_handler_requests_in_flight 1 # HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code. # TYPE promhttp_metric_handler_requests_total counter promhttp_metric_handler_requests_total{code="200"} 0 promhttp_metric_handler_requests_total{code="500"} 0 promhttp_metric_handler_requests_total{code="503"} 0
8. Přidání čítače (counter) do množiny sledovaných metrik
Nyní se pokusíme možnosti předchozí aplikace vylepšit. Přidáme do ní jeden čítač, který se bude postupně každou sekundu zvyšovat. Samotný čítač se vytvoří konstruktorem prometheus.NewCounter, kterému je nutné předat atributy čítače. Mezi základní atributy patří jméno metriky tak, jak ji bude vidět systém Prometheus. Nastavit můžeme i popis či nápovědu k dané metrice. I tuto nápovědu bude možné dále zpracovat:
var counter = prometheus.NewCounter(prometheus.CounterOpts{ Name: "number_of_ticks", Help: "The total number of ticks since the app is started", })
Čítač, který je představován rozhraním Counter, podporuje pouze dvě metody:
- Inc() – zvýšení čítače o jedničku
- Add(float64) – zvýšení čítače o zadanou hodnotu (offset), změna musí být větší než nula
Nově upravená aplikace by mohla vypadat následovně:
package main import ( "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "net/http" "os" "time" ) var counter = prometheus.NewCounter(prometheus.CounterOpts{ Name: "number_of_ticks", Help: "The total number of ticks since the app is started", }) func tick() { for { counter.Inc() time.Sleep(time.Second) } } func recordMetrics() { fmt.Println("Starting recording metrics") go tick() } func main() { recordMetrics() fmt.Println("Initializing HTTP server") http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) os.Exit(2) } }
9. Registrace nového čítače
Pokud upravenou aplikaci spustíme a pokusíme se nástrojem curl získat hodnotu čítače, nebudeme příliš úspěšní. Je tomu tak z jednoho prostého důvodu – samotné vytvoření čítače ještě neznamená, že hodnota tohoto čítače bude zveřejněna společně s ostatními metrikami. Aby tomu tak bylo, je nutné čítač zaregistrovat, a to například funkcí MustRegister:
func init() { prometheus.MustRegister(counter) }
Povšimněte si, že jsme funkci MustRegister() zavolali z funkce init(), která je zavolána automaticky při inicializaci balíčku.
Nová podoba aplikace, jejíž zdrojový kód naleznete zde, již bude plně funkční:
package main import ( "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "net/http" "os" "time" ) var counter = prometheus.NewCounter(prometheus.CounterOpts{ Name: "number_of_ticks", Help: "The total number of ticks since the app is started", }) func tick() { for { counter.Inc() time.Sleep(time.Second) } } func recordMetrics() { fmt.Println("Starting recording metrics") go tick() } func init() { prometheus.MustRegister(counter) } func main() { recordMetrics() fmt.Println("Initializing HTTP server") http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) os.Exit(2) } }
Pokud nyní aplikaci spustíme a kombinací nástrojů curl a grep vyfiltrujeme řádky obsahující řetězec „number_of_ticks“, zjistíme, že čítač pracuje bez problémů a jeho hodnota je začleněna do ostatních metrik:
# HELP number_of_ticks The total number of ticks since the app is started # TYPE number_of_ticks counter number_of_ticks 54
10. Automatická registrace čítače
Aby se nezapomnělo na registraci nějaké metriky, existuje v balíčku github.com/prometheus/client_golang/prometheus/promauto několik funkcí, které nejenom že určitou metriku (čítač, měřítko, histogram atd.) vytvoří, ale současně ho i zaregistrují. Při použití tohoto balíčku je možné čítač vytvořit a zaregistrovat takto:
var counter = promauto.NewCounter(prometheus.CounterOpts{ Name: "number_of_ticks", Help: "The total number of ticks since the app is started", })
Celá aplikace se zjednoduší, neboť se již nemusíme starat o registraci čítače ve funkci init():
package main import ( "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "net/http" "os" "time" ) var counter = promauto.NewCounter(prometheus.CounterOpts{ Name: "number_of_ticks", Help: "The total number of ticks since the app is started", }) func tick() { for { counter.Inc() time.Sleep(time.Second) } } func recordMetrics() { fmt.Println("Starting recording metrics") go tick() } func main() { recordMetrics() fmt.Println("Initializing HTTP server") http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) os.Exit(2) } }
Výsledky při běhu budou stejné, jako tomu bylo u předchozí varianty aplikace:
# HELP number_of_ticks The total number of ticks since the app is started # TYPE number_of_ticks counter number_of_ticks 100
11. Metrika reprezentující libovolnou číselnou hodnotu
V předchozím textu jsme si řekli, že hodnotu čítače lze pouze zvyšovat, a to metodami Inc() a Add(). Velmi často ovšem musíme zveřejnit i informace o metrice, která může nabývat libovolné hodnoty (tedy může se i snižovat). Taková metrika je reprezentována objektem Gauge se šesti metodami určenými pro změnu hodnoty:
- Inc() – zvýšení o jedničku
- Dec() – snížení o jedničku
- Set(float64) – nastavení na zadanou hodnotu
- Add(float64) – změna o zadanou hodnotu
- Sub(float64) – změna o zadanou hodnotu
- SetToCurrentTime() – nastavení na Unix time (v sekundách)
V dalším ukázkovém příkladu je Gauge použit pro poskytnutí informací o počtu aktuálně běžících gorutin. Tuto informaci získáme snadno s využitím funkce NumGoroutine() z balíčku runtime:
package main import ( "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "net/http" "os" "runtime" "time" ) var goroutines = promauto.NewGauge(prometheus.GaugeOpts{ Name: "number_of_goroutines", Help: "Number of goroutines in the process", }) func tick() { for { goroutines.Set(float64(runtime.NumGoroutine())) time.Sleep(time.Second) } } func recordMetrics() { fmt.Println("Starting recording metrics") go tick() } func main() { recordMetrics() fmt.Println("Initializing HTTP server") http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) os.Exit(2) } }
Výsledek poskytnutý aplikací přes /metrics:
# HELP number_of_goroutines Number of goroutines in the process # TYPE number_of_goroutines gauge number_of_goroutines 2
12. Metrika založená na sledování sekvence číselných hodnot
Dalším užitečným typem metriky je metrika představovaná objektem nazvaným Summary. Tento objekt má jedinou metodu Observe(), které lze předat libovolnou číselnou hodnotu. Na základě postupně získávaných hodnot se vypočítají základní statistické informace – počet zaznamenaných hodnot, jejich průměr, kvantily (míra polohy rozdělení pravděpodobnosti měřené veličiny). Další demonstrační příklad bude dosti umělý, protože budeme zaznamenávat náhodná čísla generovaná funkcí rand.Float64() a nikoli reálnou měřenou hodnotu:
package main import ( "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "math/rand" "net/http" "os" "time" ) var durations = promauto.NewSummary(prometheus.SummaryOpts{ Name: "request_durations", Help: "Durations of requests.", Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, }) func tick() { for { v := rand.Float64() durations.Observe(v) time.Sleep(time.Second) } } func recordMetrics() { fmt.Println("Starting recording metrics") go tick() } func main() { recordMetrics() fmt.Println("Initializing HTTP server") http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) os.Exit(2) } }
Pokud po určité době (přibližně po čtvrt minutě) přečteme nástrojem curl metriky, můžeme získat následující údaje:
# HELP go_gc_duration_seconds A summary of the GC invocation durations. # HELP request_durations Durations of requests. # TYPE request_durations summary request_durations{quantile="0.5"} 0.4246374970712657 request_durations{quantile="0.9"} 0.8136399609900968 request_durations{quantile="0.99"} 0.9405090880450124 request_durations_sum 7.089663510425493 request_durations_count 16
13. Kumulativní histogram
Posledním důležitým typem metriky je kumulativní histogram s volitelným počtem a šířkou intervalů. Histogram je představován objektem typu Histogram, který taktéž implementuje metodu Observe, které lze předat libovolnou číselnou hodnotu. Tato hodnota je do histogramu začleněna a celý histogram (počet hodnot, které padly do daného intervalu a intervalů nižších) je nabízen jako metrika. Podívejme se nejdříve na kód příkladu, posléze si vysvětlíme, proč se jedná o kumulativní histogram:
package main import ( "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "math/rand" "net/http" "os" "time" ) var durations = promauto.NewHistogram(prometheus.HistogramOpts{ Name: "request_durations", Help: "Durations of requests.", Buckets: prometheus.LinearBuckets(0, 0.1, 10), }) func tick() { for { v := rand.Float64() fmt.Println(v) durations.Observe(v) time.Sleep(time.Second) } } func recordMetrics() { fmt.Println("Starting recording metrics") go tick() } func main() { recordMetrics() fmt.Println("Initializing HTTP server") http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) os.Exit(2) } }
Zajímavé budou výsledky. Povšimněte si, že se provedlo devatenáct zápisů do histogramu a že samotný histogram obsahuje jedenáct intervalů, přičemž každý interval obsahuje počet hodnot menších, než je mezní hodnota intervalu (tedy nejedná se o o interval „od-do“, ale jen „do“):
# HELP request_durations Durations of requests. # TYPE request_durations histogram request_durations_bucket{le="0"} 0 request_durations_bucket{le="0.1"} 2 request_durations_bucket{le="0.2"} 3 request_durations_bucket{le="0.30000000000000004"} 6 request_durations_bucket{le="0.4"} 9 request_durations_bucket{le="0.5"} 12 request_durations_bucket{le="0.6"} 13 request_durations_bucket{le="0.7"} 17 request_durations_bucket{le="0.7999999999999999"} 17 request_durations_bucket{le="0.8999999999999999"} 18 request_durations_bucket{le="+Inf"} 19 request_durations_sum 8.34488419486297 request_durations_count 19
Výsledek po dalších dvanácti měřeních:
# HELP request_durations Durations of requests. # TYPE request_durations histogram request_durations_bucket{le="0"} 0 request_durations_bucket{le="0.1"} 2 request_durations_bucket{le="0.2"} 3 request_durations_bucket{le="0.30000000000000004"} 11 request_durations_bucket{le="0.4"} 15 request_durations_bucket{le="0.5"} 18 request_durations_bucket{le="0.6"} 21 request_durations_bucket{le="0.7"} 26 request_durations_bucket{le="0.7999999999999999"} 27 request_durations_bucket{le="0.8999999999999999"} 30 request_durations_bucket{le="+Inf"} 31 request_durations_sum 14.195887244852525 request_durations_count 31
Možná se nyní ptáte, proč se používá kumulativní histogram. Jeho velkou výhodou je fakt, že je možné operativně (podle paměťových aj. nároků) zmenšit počet intervalů, aniž by došlo k degradaci ostatních, resp. předchozích hodnot.
14. Intervaly s rozdílnou šířkou
Typické histogramy mají shodnou šířku všech intervalů (0.1, 0.2, 0.3, 0.4, …), což ovšem nemusí být pro všechny měřené veličiny ideální nastavení. Z tohoto důvodu je možné vytvořit i histogram, v němž se šířka intervalů postupně zvyšuje či naopak snižuje o určité měřítko. Podívejme se na následující příklad:
var durations = promauto.NewHistogram(prometheus.HistogramOpts{ Name: "request_durations", Help: "Durations of requests.", Buckets: prometheus.ExponentialBuckets(0.01, 2, 10), })
Výsledkem budou intervaly 0.01, 0.02 (první vynásobený dvěma) atd.:
request_durations_bucket{le="0.01"} 0 request_durations_bucket{le="0.02"} 0 request_durations_bucket{le="0.04"} 0 request_durations_bucket{le="0.08"} 0 request_durations_bucket{le="0.16"} 0 request_durations_bucket{le="0.32"} 0 request_durations_bucket{le="0.64"} 1 request_durations_bucket{le="1.28"} 1 request_durations_bucket{le="2.56"} 1 request_durations_bucket{le="5.12"} 1 request_durations_bucket{le="+Inf"} 1 request_durations_sum 0.6046602879796196 request_durations_count 1
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
package main import ( "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "math/rand" "net/http" "os" "time" ) var durations = promauto.NewHistogram(prometheus.HistogramOpts{ Name: "request_durations", Help: "Durations of requests.", Buckets: prometheus.ExponentialBuckets(0.01, 2, 10), }) func tick() { for { v := rand.Float64() fmt.Println(v) durations.Observe(v) time.Sleep(time.Second) } } func recordMetrics() { fmt.Println("Starting recording metrics") go tick() } func main() { recordMetrics() fmt.Println("Initializing HTTP server") http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) os.Exit(2) } }
Další výsledky běhu tohoto příkladu se zveřejněnými metrikami:
# HELP go_gc_duration_seconds A summary of the GC invocation durations. # HELP request_durations Durations of requests. # TYPE request_durations histogram request_durations_bucket{le="0.01"} 0 request_durations_bucket{le="0.02"} 0 request_durations_bucket{le="0.04"} 0 request_durations_bucket{le="0.08"} 1 request_durations_bucket{le="0.16"} 2 request_durations_bucket{le="0.32"} 2 request_durations_bucket{le="0.64"} 5 request_durations_bucket{le="1.28"} 8 request_durations_bucket{le="2.56"} 8 request_durations_bucket{le="5.12"} 8 request_durations_bucket{le="+Inf"} 8 request_durations_sum 3.9810604603187447 request_durations_count 8
Výsledek po záznamu 133 náhodných hodnot:
# HELP go_gc_duration_seconds A summary of the GC invocation durations. # HELP request_durations Durations of requests. # TYPE request_durations histogram request_durations_bucket{le="0.01"} 3 request_durations_bucket{le="0.02"} 3 request_durations_bucket{le="0.04"} 7 request_durations_bucket{le="0.08"} 11 request_durations_bucket{le="0.16"} 19 request_durations_bucket{le="0.32"} 46 request_durations_bucket{le="0.64"} 88 request_durations_bucket{le="1.28"} 133 request_durations_bucket{le="2.56"} 133 request_durations_bucket{le="5.12"} 133 request_durations_bucket{le="+Inf"} 133 request_durations_sum 65.22517940569107 request_durations_count 133
15. Mapa čítačů a její použití pro sledování počtu přístupů na stránky
Často se setkáme se situací, kdy potřebujeme vytvořit několik čítačů, které sice měří stejnou veličinu, ale pro různé stavy aplikace. Například budeme chtít spočítat přístupy k jednotlivým stránkám, která aplikace vytváří a posílá klientům. Řešením je použít takzvaný vektor čítačů, přičemž každý čítač bude uveden svým jménem (řetězec):
var pageRequests = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "page_requests", Help: "The total number page/URL requests", }, []string{"url"})
Aplikace již bude složitější, neboť se bude jednat o HTTP server s několika koncovými body (včetně /metrics). A pro každý koncový bod se zaznamená přístup následující funkcí (povšimněte si především určení konkrétního čítače):
func countEndpoint(request *http.Request) { url := request.URL.String() fmt.Printf("Request URL: %s\n", url) pageRequests.With(prometheus.Labels{"url": url}).Inc() }
Funkci budeme volat ze všech handlerů, což v tom nejjednodušším (nikoli nejkratším) případě může vypadat následovně:
func mainEndpoint(writer http.ResponseWriter, request *http.Request) { countEndpoint(request) io.WriteString(writer, "Hello world!\n") } func fooEndpoint(writer http.ResponseWriter, request *http.Request) { countEndpoint(request) io.WriteString(writer, "FOO!\n") } func barEndpoint(writer http.ResponseWriter, request *http.Request) { countEndpoint(request) io.WriteString(writer, "BAR!\n") }
Podívejme se nyní na úplný zdrojový kód této aplikace:
package main import ( "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "io" "net/http" "os" ) var pageRequests = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "page_requests", Help: "The total number page/URL requests", }, []string{"url"}) func countEndpoint(request *http.Request) { url := request.URL.String() fmt.Printf("Request URL: %s\n", url) pageRequests.With(prometheus.Labels{"url": url}).Inc() } func mainEndpoint(writer http.ResponseWriter, request *http.Request) { countEndpoint(request) io.WriteString(writer, "Hello world!\n") } func fooEndpoint(writer http.ResponseWriter, request *http.Request) { countEndpoint(request) io.WriteString(writer, "FOO!\n") } func barEndpoint(writer http.ResponseWriter, request *http.Request) { countEndpoint(request) io.WriteString(writer, "BAR!\n") } func main() { fmt.Println("Initializing HTTP server") http.HandleFunc("/", mainEndpoint) http.HandleFunc("/foo", fooEndpoint) http.HandleFunc("/bar", barEndpoint) http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) os.Exit(2) } }
Aplikaci spustíme a po několika přístupech na jednotlivé stránky si zobrazíme metriky. Mezi nimi nalezneme i čítače stránek:
# HELP page_requests The total number page/URL requests # TYPE page_requests counter page_requests{url="/"} 10 page_requests{url="/bar"} 1 page_requests{url="/bardsgdfs"} 1 page_requests{url="/foo"} 2
16. Výpočet doby trvání vytvoření odpovědi HTTP serveru
V dnešním posledním demonstračním příkladu budeme měřit dobu trvání vytváření odpovědi HTTP serveru. Pro jednoduchost se bude měřit jen doba strávená v handleru, ovšem ve skutečnosti je pochopitelně celková doba větší, protože musíme znát i čas odeslání posledního bajtu atd. atd. Ovšem pro základní ukázku bude postačovat jednoduché měření ve stylu:
func countEndpoint(request *http.Request, start time.Time) { url := request.URL.String() fmt.Printf("Request URL: %s\n", url) duration := time.Since(start) fmt.Printf("Time to serve the page: %s\n", duration) // uprava citacu stranek pageRequests.With(prometheus.Labels{"url": url}).Inc() // uprava histogramu histogram.With(prometheus.Labels{"url": url}).Observe(float64(duration.Microseconds())) }
Tuto funkci budeme volat na konci každého handleru a navíc jí budeme muset přidat i čas, kdy se do handleru vstoupilo:
func mainEndpoint(writer http.ResponseWriter, request *http.Request) { start := time.Now() io.WriteString(writer, "Hello world!\n") countEndpoint(request, start) }
17. Komplikovanější příklad – HTTP server poskytující více metrik
V příkladu budeme používat jak čítače přístupu na jednotlivé stránky, tak i histogram s dobami, které program stráví při generování odpovědí:
var pageRequests = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "page_requests", Help: "The total number page/URL requests", }, []string{"url"}) var histogram = promauto.NewHistogramVec(prometheus.HistogramOpts{ Name: "response_time", Help: "Response time", Buckets: prometheus.LinearBuckets(0, 5, 20), }, []string{"url"})
Úplný zdrojový kód příkladu může vypadat následovně:
package main import ( "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "io" "net/http" "os" "time" ) var pageRequests = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "page_requests", Help: "The total number page/URL requests", }, []string{"url"}) var histogram = promauto.NewHistogramVec(prometheus.HistogramOpts{ Name: "response_time", Help: "Response time", Buckets: prometheus.LinearBuckets(0, 5, 20), }, []string{"url"}) func countEndpoint(request *http.Request, start time.Time) { url := request.URL.String() fmt.Printf("Request URL: %s\n", url) duration := time.Since(start) fmt.Printf("Time to serve the page: %s\n", duration) // uprava citacu stranek pageRequests.With(prometheus.Labels{"url": url}).Inc() // uprava histogramu histogram.With(prometheus.Labels{"url": url}).Observe(float64(duration.Microseconds())) } func mainEndpoint(writer http.ResponseWriter, request *http.Request) { start := time.Now() io.WriteString(writer, "Hello world!\n") countEndpoint(request, start) } func fooEndpoint(writer http.ResponseWriter, request *http.Request) { start := time.Now() countEndpoint(request, start) io.WriteString(writer, "FOO!\n") } func barEndpoint(writer http.ResponseWriter, request *http.Request) { start := time.Now() io.WriteString(writer, "BAR!\n") countEndpoint(request, start) } func main() { fmt.Println("Initializing HTTP server") http.HandleFunc("/", mainEndpoint) http.HandleFunc("/foo", fooEndpoint) http.HandleFunc("/bar", barEndpoint) http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) os.Exit(2) } }
Výsledky – počet přístupů na stránku:
# HELP page_requests The total number page/URL requests # TYPE page_requests counter page_requests{url="/"} 10 page_requests{url="/bar"} 1 page_requests{url="/foo"} 2
A doby vytváření stránek (resp. přesněji řečeno odpovědí). Histogram je vytvořen pro každou stránku zvlášť, což je přesně stav, který potřebujeme:
# HELP response_time Response time # TYPE response_time histogram response_time_bucket{url="/",le="0"} 0 response_time_bucket{url="/",le="5"} 0 response_time_bucket{url="/",le="10"} 0 response_time_bucket{url="/",le="15"} 6 response_time_bucket{url="/",le="20"} 6 response_time_bucket{url="/",le="25"} 6 response_time_bucket{url="/",le="30"} 7 response_time_bucket{url="/",le="35"} 8 response_time_bucket{url="/",le="40"} 8 response_time_bucket{url="/",le="45"} 9 response_time_bucket{url="/",le="50"} 10 response_time_bucket{url="/",le="55"} 10 response_time_bucket{url="/",le="60"} 10 response_time_bucket{url="/",le="65"} 10 response_time_bucket{url="/",le="70"} 10 response_time_bucket{url="/",le="75"} 10 response_time_bucket{url="/",le="80"} 10 response_time_bucket{url="/",le="85"} 10 response_time_bucket{url="/",le="90"} 10 response_time_bucket{url="/",le="95"} 10 response_time_bucket{url="/",le="+Inf"} 10 response_time_sum{url="/"} 228 response_time_count{url="/"} 10 response_time_bucket{url="/bar",le="0"} 0 response_time_bucket{url="/bar",le="5"} 0 response_time_bucket{url="/bar",le="10"} 0 response_time_bucket{url="/bar",le="15"} 0 response_time_bucket{url="/bar",le="20"} 0 response_time_bucket{url="/bar",le="25"} 0 response_time_bucket{url="/bar",le="30"} 1 response_time_bucket{url="/bar",le="35"} 1 response_time_bucket{url="/bar",le="40"} 1 response_time_bucket{url="/bar",le="45"} 1 response_time_bucket{url="/bar",le="50"} 1 response_time_bucket{url="/bar",le="55"} 1 response_time_bucket{url="/bar",le="60"} 1 response_time_bucket{url="/bar",le="65"} 1 response_time_bucket{url="/bar",le="70"} 1 response_time_bucket{url="/bar",le="75"} 1 response_time_bucket{url="/bar",le="80"} 1 response_time_bucket{url="/bar",le="85"} 1 response_time_bucket{url="/bar",le="90"} 1 response_time_bucket{url="/bar",le="95"} 1 response_time_bucket{url="/bar",le="+Inf"} 1 response_time_sum{url="/bar"} 29 response_time_count{url="/bar"} 1 response_time_bucket{url="/foo",le="0"} 0 response_time_bucket{url="/foo",le="5"} 0 response_time_bucket{url="/foo",le="10"} 1 response_time_bucket{url="/foo",le="15"} 2 response_time_bucket{url="/foo",le="20"} 2 response_time_bucket{url="/foo",le="25"} 2 response_time_bucket{url="/foo",le="30"} 2 response_time_bucket{url="/foo",le="35"} 2 response_time_bucket{url="/foo",le="40"} 2 response_time_bucket{url="/foo",le="45"} 2 response_time_bucket{url="/foo",le="50"} 2 response_time_bucket{url="/foo",le="55"} 2 response_time_bucket{url="/foo",le="60"} 2 response_time_bucket{url="/foo",le="65"} 2 response_time_bucket{url="/foo",le="70"} 2 response_time_bucket{url="/foo",le="75"} 2 response_time_bucket{url="/foo",le="80"} 2 response_time_bucket{url="/foo",le="85"} 2 response_time_bucket{url="/foo",le="90"} 2 response_time_bucket{url="/foo",le="95"} 2 response_time_bucket{url="/foo",le="+Inf"} 2 response_time_sum{url="/foo"} 23 response_time_count{url="/foo"} 2
18. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně čtyři megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
# | Příklad | Stručný popis | Cesta |
---|---|---|---|
1 | 01_basic_metrics.go | aplikace, která nabízí základní metriky definované přímo v klientovi Promethea | https://github.com/tisnik/go-root/blob/master/article37/01_basic_metrics.go |
2 | 02_not_registered_counter.go | čítač (counter), který však není explicitně zaregistrován | https://github.com/tisnik/go-root/blob/master/article37/02_not_registered_counter.go |
3 | 03_registered_counter.go | čítač (counter), který je adekvátně zaregistrován a bude poskytován s ostatními metrikami | https://github.com/tisnik/go-root/blob/master/article37/03_registered_counter.go |
4 | 04_automatic_registration.go | automatická registrace čítače při jeho vytvoření | https://github.com/tisnik/go-root/blob/master/article37/04_automatic_registration.go |
5 | 05_gauge.go | automaticky zaregistrovaná metrika typu gauge | https://github.com/tisnik/go-root/blob/master/article37/05_gauge.go |
6 | 06_summary.go | automaticky zaregistrovaná metrika typu summary a princip jejího použití | https://github.com/tisnik/go-root/blob/master/article37/06_summary.go |
7 | 07_histogram.go | metrika prezentovaná formou kumulativního histogramu | https://github.com/tisnik/go-root/blob/master/article37/07_histogram.go |
8 | 08_histogram_exp_buckets.go | změna intervalů (buckets) v histogramu | https://github.com/tisnik/go-root/blob/master/article37/08_histogram_exp_buckets.go |
9 | 09_counter_vec.go | jedna metrika obsahující větší množství pojmenovaných čítačů | https://github.com/tisnik/go-root/blob/master/article37/09_counter_vec.go |
10 | 10_histogram_vec.go | složitější příklad používající větší množství kumulativních histogramů | https://github.com/tisnik/go-root/blob/master/article37/10_histogram_vec.go |
19. Odkazy na Internetu
- Metric types
https://prometheus.io/docs/concepts/metric_types/ - Histograms with Prometheus: A Tale of Woe
http://linuxczar.net/blog/2017/06/15/prometheus-histogram-2/ - Why are Prometheus histograms cumulative?
https://www.robustperception.io/why-are-prometheus-histograms-cumulative - Histograms and summaries
https://prometheus.io/docs/practices/histograms/ - Instrumenting Golang server in 5 min
https://medium.com/@gsisimogang/instrumenting-golang-server-in-5-min-c1c32489add3 - Semantic Import Versioning in Go
https://www.aaronzhuo.com/semantic-import-versioning-in-go/ - Sémantické verzování
https://semver.org/ - Getting started with Go modules
https://medium.com/@fonseka.live/getting-started-with-go-modules-b3dac652066d - Create projects independent of $GOPATH using Go Modules
https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o - Anatomy of Modules in Go
https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16 - Modules
https://github.com/golang/go/wiki/Modules - Go Modules Tutorial
https://tutorialedge.net/golang/go-modules-tutorial/ - Module support
https://golang.org/cmd/go/#hdr-Module_support - Go Lang: Memory Management and Garbage Collection
https://vikash1976.wordpress.com/2017/03/26/go-lang-memory-management-and-garbage-collection/ - Golang Internals, Part 4: Object Files and Function Metadata
https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html - What is REPL?
https://pythonprogramminglanguage.com/repl/ - What is a REPL?
https://codewith.mu/en/tutorials/1.0/repl - Programming at the REPL: Introduction
https://clojure.org/guides/repl/introduction - What is REPL? (Quora)
https://www.quora.com/What-is-REPL - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - Read-eval-print loop (Wikipedia)
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - Vim as a Go (Golang) IDE using LSP and vim-go
https://octetz.com/posts/vim-as-go-ide - gopls
https://github.com/golang/go/wiki/gopls - IDE Integration Guide
https://github.com/stamblerre/gocode/blob/master/docs/IDE_integration.md - How to instrument Go code with custom expvar metrics
https://sysdig.com/blog/golang-expvar-custom-metrics/ - Golang expvar metricset (Metricbeat Reference)
https://www.elastic.co/guide/en/beats/metricbeat/7.x/metricbeat-metricset-golang-expvar.html - Package expvar
https://golang.org/pkg/expvar/#NewInt - Java Platform Debugger Architecture: Overview
https://docs.oracle.com/en/java/javase/11/docs/specs/jpda/jpda.html - The JVM Tool Interface (JVM TI): How VM Agents Work
https://www.oracle.com/technetwork/articles/javase/index-140680.html - JVM Tool Interface Version 11.0
https://docs.oracle.com/en/java/javase/11/docs/specs/jvmti.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - Go & cgo: integrating existing C code with Go
http://akrennmair.github.io/golang-cgo-slides/#1 - Using cgo to call C code from within Go code
https://wenzr.wordpress.com/2018/06/07/using-cgo-to-call-c-code-from-within-go-code/ - Package trace
https://golang.org/pkg/runtime/trace/ - Introducing HTTP Tracing
https://blog.golang.org/http-tracing - Command trace
https://golang.org/cmd/trace/ - A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
https://github.com/wesovilabs/koazee - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Go is on a Trajectory to Become the Next Enterprise Programming Language
https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Goroutine IDs
https://blog.sgmansfield.com/2015/12/goroutine-ids/ - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go