Hlavní navigace

Sledování vybraných metrik služeb naprogramovaných v jazyku Go

Pavel Tišnovský

Dnes si na několika příkladech ukážeme, jak je možné zařídit, aby dlouhodobě běžící aplikace (webové servery, služby či démoni) poskytovaly dalším nástrojům různé metriky i ostatní užitečné údaje.

Doba čtení: 39 minut

Sdílet

11. Současné zveřejnění metrik i profilovacích informací

12. Nový demonstrační příklad: vykreslování Mandelbrotovy množiny se zaznamenáváním důležitých statistických informací

13. Sledování činnosti HTTP serveru

14. Zpracování metrik programově

15. Složitější skript: uložení výsledků do tabulky ve formátu CSV

16. Kumulativní časy

17. Další datové typy podporované modulem expvar

18. Dodatek: různé metody práce s čítačem zvyšovaným v gorutinách

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Sledování vybraných metrik služeb naprogramovaných v jazyku Go

V předchozí části seriálu o programovacím jazyku Go jsme si mj. řekli, že virtuální stroje, v nichž běží takzvané managed aplikace, dokážou dalším nástrojům (profilerům, debuggerům, monitorovacím aplikacím atd.) poskytovat důležité metriky a dokonce umožňují, aby aplikace sama zveřejňovala ty údaje, které jsou nutné například pro její korektní monitoring (počet aktuálně přihlášených uživatelů, stav připojení do databází apod.). Jako velmi dobrý příklad z praxe jsme si uvedli virtuální stroj programovacího jazyka Java (JVM), který pro poskytování podobných informací nabízí hned několik rozhraní spadajících pod obecný název JPDA neboli Java Platform Debugger Architecture; patří sem především rozhraní JVM TI (Java Virtual Machine Tools Interface), JDWP (Java Debug Wire Protocol) a JDI (Java Debug Interface). Kromě toho může sama aplikace zveřejňovat prakticky libovolné údaje přes rozhraní JMX, které lze využít například ze standardního nástroje JConsole.

*

Obrázek 1: Ovládání front message brokera Apache Active MQ (AMQ) ze standardního nástroje JConsole. Povšimněte si, že je možné mj. i přímo ovládat samotné fronty a jejich obsah.

Poznámka: možnosti rozhraní JMX jsou ve skutečnosti mnohem rozsáhlejší, protože lze velmi snadno zajistit, aby se atributy zveřejněných objektů mohly měnit a aby bylo dokonce možné volat jejich metody, a to přímo z již zmíněné JConsole nebo podobného nástroje. Tento přístup se používá při ovládání některých služeb, například Apache Active MQ (AMQ) (viz předchozí screenshot), s níž jsme se seznámiliseriálu o message brokerech.
*

Obrázek 2: Vpravo nahoře jsou vypsány všechny dostupné fronty běžícího serveru Apache Active MQ, přesněji řečeno datové typy, kterými jsou reprezentovány.

2. Nativní aplikace vytvořená v Go versus aplikace běžící ve virtuálních strojích

Programovací jazyk Go se v mnoha ohledech odlišuje od jazyků umožňujících běh aplikací ve specializovaných virtuálních strojích. Je tomu tak především z toho důvodu, že výsledkem překladu (s následným linkováním) je v případě jazyka Go binární spustitelná nativní aplikace obsahující i všechny potřebné knihovny, a to včetně modulu pro automatickou správu paměti apod. (naopak aplikace naprogramované v Javě jsou přeloženy do bajtkódu, který je JITovaný ve virtuálním stroji). Proto asi nebude velkým překvapením zjištění, že možnosti Go jsou odlišné, ovšem pokud je skutečně nutné, aby byla dlouhodobě běžící aplikace nějakým způsobem sledována, je to možné zařídit, a to dokonce relativně jednoduchým způsobem. Již minule jsme si řekli, jakým způsobem je možné zajistit, aby aplikace (například webové server nebo jiná síťová služba) poskytovala všechny profilovací informace. Připomeňme si, že dostačuje, aby taková aplikace provedla import balíčku net/http/pprof a popř. nastartovala webový server (pokud ovšem taková služba sama není webovým serverem, potom už se pochopitelně další HTTP server spouštět nemusí):

import (
        _ "net/http/pprof"
)

Profilovací informace takto nastavené aplikace budou dostupné z každého webového prohlížeče po zadání cesty /debug/pprof, což jsme si již ukázali minule na několika demonstračních příkladech. Příklad výsledku:

Obrázek 3: Úvodní stránka s profilovacími informacemi běžícího HTTP serveru.

Příklad informací o paměťovém subsystému běžící aplikace:

# runtime.MemStats
# Alloc = 1568912
# TotalAlloc = 7938176
# Sys = 72022264
# Lookups = 0
# Mallocs = 136375
# Frees = 134637
# HeapAlloc = 1568912
# HeapSys = 66617344
# HeapIdle = 64028672
# HeapInuse = 2588672
# HeapReleased = 0
# HeapObjects = 1738
# Stack = 491520 / 491520
# MSpan = 24776 / 49152
# MCache = 6912 / 16384
# BuckHashSys = 1444585
# GCSys = 2371584
# OtherSys = 1031695
# NextGC = 4194304
# LastGC = 1560699375977620410
...
...
...

3. Zopakování z minula: jednoduchý HTTP server s dynamicky generovaným obsahem

Dnes si ukážeme druhou důležitou část celého řešení – zveřejnění těch údajů, které budou nastavovány samotnou aplikací. Nejprve si (znovu) ukažme velmi jednoduchý webový server, který po svém spuštění uživatelům poskytuje jedinou dynamicky generovanou stránku a v ní zobrazí taktéž dynamicky generovaný rastrový obrázek s náhodně obarvenými pixely. Následující příklad vznikl úpravou (vylepšením) příkladu, s nímž jsme se již seznámili v předchozím článku:

package main
 
import (
        "image"
        "image/color"
        "image/png"
        "io"
        "math/rand"
        "net/http"
)
 
func indexPageHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/html")
        response := `
<body>
    <h1>Enterprise image renderer</h1>
    <img src="/image" width=256 height=256 />
</body>`
        io.WriteString(writer, response)
}
 
func calculateColor() color.RGBA {
        return color.RGBA{uint8(rand.Intn(255)),
                uint8(rand.Intn(255)),
                uint8(rand.Intn(255)), 255}
}
 
func imageHandler(writer http.ResponseWriter, request *http.Request) {
        const ImageWidth = 256
        const ImageHeight = 256
        outputimage := image.NewRGBA(image.Rectangle{image.Point{0, 0},
                image.Point{ImageWidth, ImageHeight}})
 
        for y := 0; y < ImageHeight; y++ {
                for x := 0; x < ImageWidth; x++ {
                        c := calculateColor()
                        outputimage.Set(x, y, c)
                }
        }
        png.Encode(writer, outputimage)
}
 
func main() {
        http.HandleFunc("/", indexPageHandler)
        http.HandleFunc("/image", imageHandler)
        http.ListenAndServe(":8080", nil)
}

Obrázek 4: Stránka s obrázkem generovaná HTTP serverem implementovaným v předchozím demonstračním příkladu.

4. Zveřejnění obsahu čítačů přes explicitně naprogramované koncové body API

Jak by bylo možné postupovat v případě, že budeme chtít získat informaci o tom, kolikrát byl vlastně obrázek vygenerován? Nic nám samozřejmě nebrání si do aplikace přidat čítač (proměnnou typu int):

var counter int

Čítač se bude při každém překreslení obrázku zvyšovat o jedničku:

func imageHandler(writer http.ResponseWriter, request *http.Request) {
        ...
        ...
        ...
        counter++
}

Obsah tohoto čítače můžeme zveřejnit například přes specializovaný koncový bod (endpoint) rozhraní HTTP serveru. V tom nejjednodušším případě může být koncový bod naprogramován zcela primitivním způsobem:

func counterHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/plain")
        fmt.Fprintf(writer, "Counter: %d\n", counter)
}

Navíc je samozřejmě nutné nový handler zaregistrovat při inicializaci HTTP serveru:

http.HandleFunc("/counter", counterHandler)

Nová podoba HTTP serveru s implementovaným čítačem může vypadat takto:

package main
 
import (
        "fmt"
        "image"
        "image/color"
        "image/png"
        "io"
        "math/rand"
        "net/http"
)
 
var counter int
 
func indexPageHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/html")
        response := `
<body>
    <h1>Enterprise image renderer</h1>
    <img src="/image" width=256 height=256 />
</body>`
        io.WriteString(writer, response)
}
 
func calculateColor() color.RGBA {
        return color.RGBA{uint8(rand.Intn(255)),
                uint8(rand.Intn(255)),
                uint8(rand.Intn(255)), 255}
}
 
func imageHandler(writer http.ResponseWriter, request *http.Request) {
        const ImageWidth = 256
        const ImageHeight = 256
        outputimage := image.NewRGBA(image.Rectangle{image.Point{0, 0},
                image.Point{ImageWidth, ImageHeight}})
 
        for y := 0; y < ImageHeight; y++ {
                for x := 0; x < ImageWidth; x++ {
                        c := calculateColor()
                        outputimage.Set(x, y, c)
                }
        }
        png.Encode(writer, outputimage)
        counter++
}
 
func counterHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/plain")
        fmt.Fprintf(writer, "Counter: %d\n", counter)
}
 
func main() {
        http.HandleFunc("/", indexPageHandler)
        http.HandleFunc("/image", imageHandler)
        http.HandleFunc("/counter", counterHandler)
        http.ListenAndServe(":8080", nil)
}

Po spuštění serveru nám nic nebrání se dotázat na obsah čítače. Můžeme využít například standardní nástroj curl:

$ curl localhost:8080/counter
 
Counter: 0

A po dvojím překreslení stránky v prohlížeči by se měl stejným způsobem změnit i obsah čítače:

$ curl localhost:8080/counter
 
Counter: 2

5. Mutex pro synchronizaci změny obsahu čítače

Musíme však mít na paměti, že funkce s implementací odpovědí na jednotlivé požadavky jsou spouštěny v samostatných gorutinách, takže je nutné změnu obsahu čítače provádět takovým způsobem, aby byly změny mezi gorutinami synchronizovány (což ovšem obecně dělat moc často nechceme, protože se snižuje výkon aplikace) a dokonce aby změny byly dalšími gorutinami vůbec viditelné, protože u běžných proměnných (i globálních) není viditelnost změn v ostatních gorutinách nijak garantována (viz též The Go Memory Model ). Jedno z možných řešení spočívá v použití klasických mutexů:

var counter int
var mutex = &sync.Mutex{}

Čítač se bude zvyšovat v sekci, která bude vždy vykonána maximálně jednou gorutinou:

mutex.Lock()
counter++
mutex.Unlock()

Celý demonstrační příklad bude po příslušných úpravách vypadat takto:

package main
 
import (
        "fmt"
        "image"
        "image/color"
        "image/png"
        "io"
        "math/rand"
        "net/http"
        "sync"
)
 
var counter int
var mutex = &sync.Mutex{}
 
func indexPageHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/html")
        response := `
<body>
    <h1>Enterprise image renderer</h1>
    <img src="/image" width=256 height=256 />
</body>`
        io.WriteString(writer, response)
}
 
func calculateColor() color.RGBA {
        return color.RGBA{uint8(rand.Intn(255)),
                uint8(rand.Intn(255)),
                uint8(rand.Intn(255)), 255}
}
 
func imageHandler(writer http.ResponseWriter, request *http.Request) {
        const ImageWidth = 256
        const ImageHeight = 256
        outputimage := image.NewRGBA(image.Rectangle{image.Point{0, 0},
                image.Point{ImageWidth, ImageHeight}})
 
        for y := 0; y < ImageHeight; y++ {
                for x := 0; x < ImageWidth; x++ {
                        c := calculateColor()
                        outputimage.Set(x, y, c)
                }
        }
        png.Encode(writer, outputimage)
        mutex.Lock()
        counter++
        mutex.Unlock()
}
 
func counterHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/plain")
        fmt.Fprintf(writer, "Counter: %d\n", counter)
}
 
func main() {
        http.HandleFunc("/", indexPageHandler)
        http.HandleFunc("/image", imageHandler)
        http.HandleFunc("/counter", counterHandler)
        http.ListenAndServe(":8080", nil)
}
Poznámka: porovnání různých řešení synchronizace a atomicity čítače si ukážeme v osmnácté kapitole.

6. Použití funkcí zajišťujících atomickou změnou hodnoty pro realizaci čítačů

Existuje samozřejmě i další řešení, které se spoléhá na speciální funkce zaručující, že provedou atomickou změnu hodnoty. Tyto funkce určené pro některé základní datové typy nalezneme ve standardním balíčku sync/atomic:

# Datový typ Uložení hodnoty Změna hodnoty Operace CAS
1 int32 StoreInt32(addr *int32, val int32) AddInt32(addr *int32, delta int32) (new int32) CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
2 int64 StoreInt64(addr *int64, val int64) AddInt64(addr *int64, delta int64) (new int64) CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
3 uint32 StoreUint32(addr *uint32, val uint32) AddUint32(addr *uint32, delta uint32) (new uint32) CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
4 uint64 StoreUint64(addr *uint64, val uint64) AddUint64(addr *uint64, delta uint64) (new uint64) CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
5 uintptr StoreUintptr(addr *uintptr, val uintptr) AddUintptr(addr *uintptr, delta uintptr) (new uintptr) CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)

7. Úprava HTTP serveru s čítači s atomickou změnou hodnoty

Úprava našeho HTTP serveru takovým způsobem, aby používat čítače měněné atomickou operací (z pohledu ostatních gorutin), je snadná. Nejprve čítač vytvoříme; bude se jednat o běžnou globální proměnnou:

var counter uint32

Zvýšení čítače v gorutině vytvořené pro obsluhu události, se provede takto:

func imageHandler(writer http.ResponseWriter, request *http.Request) {
        ...
        ...
        ...
        atomic.AddUint32(&counter, 1)
}

Úplný zdrojový kód upraveného příkladu bude vypadat následovně:

package main
 
import (
        "fmt"
        "image"
        "image/color"
        "image/png"
        "io"
        "math/rand"
        "net/http"
        "sync/atomic"
)
 
var counter uint32
 
func indexPageHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/html")
        response := `
<body>
    <h1>Enterprise image renderer</h1>
    <img src="/image" width=256 height=256 />
</body>`
        io.WriteString(writer, response)
}
 
func calculateColor() color.RGBA {
        return color.RGBA{uint8(rand.Intn(255)),
                uint8(rand.Intn(255)),
                uint8(rand.Intn(255)), 255}
}
 
func imageHandler(writer http.ResponseWriter, request *http.Request) {
        const ImageWidth = 256
        const ImageHeight = 256
        outputimage := image.NewRGBA(image.Rectangle{image.Point{0, 0},
                image.Point{ImageWidth, ImageHeight}})
 
        for y := 0; y < ImageHeight; y++ {
                for x := 0; x < ImageWidth; x++ {
                        c := calculateColor()
                        outputimage.Set(x, y, c)
                }
        }
        png.Encode(writer, outputimage)
 
        atomic.AddUint32(&counter, 1)
}
 
func counterHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/plain")
        fmt.Fprintf(writer, "Counter: %d\n", counter)
}
 
func main() {
        http.HandleFunc("/", indexPageHandler)
        http.HandleFunc("/image", imageHandler)
        http.HandleFunc("/counter", counterHandler)
        http.ListenAndServe(":8080", nil)
}

8. Balíček expvar

Ovšem nejlepší (a především i standardní) řešení spočívá v použití balíčku nazvaného expvar. Tento balíček umožňuje definovat ty proměnné, jejichž obsah bude zveřejněn interním HTTP serverem aplikace, a to na standardní adrese /debug/vars. U těchto proměnných je zaručeno, že jejich modifikace bude synchronizována mezi gorutinami, takže nebudeme muset ručně pracovat s mutexy ani s atomickými datovými typy.

V balíčku expvar je implementována podpora pro několik datových typů exportovaných proměnných:

# Datový typ Typ exportované proměnné
1 Int int64
2 Float float64
3 String string
4 Map Map
5 Func funkce vracející řetězec
Poznámka: jak uvidíme dále, jedná se o uživatelské datové typy, u nichž je mj. definováno i několik metod pro modifikaci hodnot. Příkladem mohou být metody pro typ Int:
# Funkce/metoda Stručný popis
1 func NewInt(name string) *Int konstruktor datového typu
2 func (v *Int) Add(delta int64) zvýšení či snížení hodnoty o zadaný offset
3 func (v *Int) Set(value int64) nastavení nové hodnoty
4 func (v *Int) String() string převod na tisknutelný řetězec
5 func (v *Int) Value() int64 získání aktuální hodnoty proměnné

Obrázek 5: Hodnota čítače získaná přes standardní rozhraní runtime jazyka Go.

9. Úprava HTTP serveru tak, aby byly čítače dostupné přes standardní rozhraní

Ukažme si nyní, jakým způsobem je nutné předělat předchozí demonstrační příklady takovým způsobem, aby bylo počitadlo překreslení obrázku exportováno standardním způsobem. Ve skutečnosti to není vůbec nic těžkého. Nejprve je samozřejmě nutné importovat balíček expvar společně se všemi ostatními balíčky, které aplikace pro svůj běh vyžaduje:

import (
        "expvar"
)

Následně můžeme vytvořit globální exportovanou proměnnou typu Int a pojmenovat ji (pod daným jménem bude obsah proměnné zveřejněn):

var counter = expvar.NewInt("counter")
Poznámka: používání globálních proměnných je z pochopitelných důvodů považováno za špatný přístup, ovšem v tomto případě se jedná o nejjednodušší řešení, které umožňuje, že nemusíme obalovat handlery HTTP serveru do uzávěrů. Pokud přesto budete preferovat čistší přístup, uvedeme si příklad alternativního řešení v navazující kapitole.

Změnu hodnoty této proměnné můžeme provést přímo v handleru, který je zavolán ve chvíli, kdy je zapotřebí vykreslit obrázek (na základě uživatelského požadavku – requestu). Povšimněte si způsobu změny hodnoty počitadla pomocí metody:

func imageHandler(writer http.ResponseWriter, request *http.Request) {
        ...
        ...
        ...
        counter.Add(1)
}

Následuje výpis úplného zdrojového kódu upraveného demonstračního příkladu:

package main
 
import (
        "expvar"
        "fmt"
        "image"
        "image/color"
        "image/png"
        "io"
        "math/rand"
        "net/http"
)
 
var counter = expvar.NewInt("counter")
 
func indexPageHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/html")
        response := `
<body>
    <h1>Enterprise image renderer</h1>
    <img src="/image" width=256 height=256 />
</body>`
        io.WriteString(writer, response)
}
 
func calculateColor() color.RGBA {
        return color.RGBA{uint8(rand.Intn(255)),
                uint8(rand.Intn(255)),
                uint8(rand.Intn(255)), 255}
}
 
func imageHandler(writer http.ResponseWriter, request *http.Request) {
        const ImageWidth = 256
        const ImageHeight = 256
        outputimage := image.NewRGBA(image.Rectangle{image.Point{0, 0},
                image.Point{ImageWidth, ImageHeight}})
 
        for y := 0; y < ImageHeight; y++ {
                for x := 0; x < ImageWidth; x++ {
                        c := calculateColor()
                        outputimage.Set(x, y, c)
                }
        }
        png.Encode(writer, outputimage)
        counter.Add(1)
}
 
func counterHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/plain")
        fmt.Fprintf(writer, "Counter: %d\n", counter.Value())
}
 
func main() {
        http.HandleFunc("/", indexPageHandler)
        http.HandleFunc("/image", imageHandler)
        http.HandleFunc("/counter", counterHandler)
        http.ListenAndServe(":8080", nil)
}

Zveřejněné proměnné a jejich obsah bude dostupný na adrese /debug/vars, takže ho můžeme přečíst například standardní utilitou curl:

$ curl localhost:8080/debug/vars
 
{
"cmdline": ["/tmp/go-build196872790/b001/exe/05_image_server_expvar"],
"counter": 0,
"memstats":
{"Alloc":285976,"TotalAlloc":285976,"Sys":70189056,"Lookups":0,"Mallocs":859,"Frees":69,"HeapAlloc":285976,"HeapSys":66715648,"HeapIdle":65724416,"HeapInuse":991232,"HeapReleased":0,"HeapObjects":790,"StackInuse":393216,"StackSys":393216,"MSpanInuse":15808,"MSpanSys":16384,"MCacheInuse":6912,"MCacheSys":16384,"BuckHashSys":2689,"GCSys":2234368,"OtherSys":810367,"NextGC":4473924,"LastGC
...
...
...
Poznámka: povšimněte si, že kromě obsahu samotného čítače (atribut counter) se vrátily i informace o činnosti paměťového subsystému celé aplikace, resp. přesněji řečeno její běhové (runtime) části.

10. Úprava předchozího příkladu a odstranění globálních proměnných ze zdrojového kódu

Předchozí demonstrační příklad ještě upravíme takovým způsobem, aby se v něm nepoužívaly globální proměnné. To znamená, že do gorutin budeme muset předávat i samotný čítač, takže se handlery upraví takto:

func imageHandler(writer http.ResponseWriter, request *http.Request, counter *expvar.Int) {
        ...
        ...
        ...
        counter.Add(1)
}

Aby bylo možné do handlerů přidat další parametr, musíme vytvořit buď pomocnou funkci nebo uzávěr (closure). Ukažeme si způsob použití uzávěru, tj. (zde anonymní) funkce, na kterou je navázána lokální proměnná counter definovaná v nadřazené funkci main:

http.HandleFunc("/image",
        func(writer http.ResponseWriter, request *http.Request) { imageHandler(writer, request, counter) })

Úplný zdrojový kód příkladu vypadá následovně:

package main
 
import (
        "expvar"
        "fmt"
        "image"
        "image/color"
        "image/png"
        "io"
        "math/rand"
        "net/http"
)
 
func indexPageHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/html")
        response := `
<body>
    <h1>Enterprise image renderer</h1>
    <img src="/image" width=256 height=256 />
</body>`
        io.WriteString(writer, response)
}
 
func calculateColor() color.RGBA {
        return color.RGBA{uint8(rand.Intn(255)),
                uint8(rand.Intn(255)),
                uint8(rand.Intn(255)), 255}
}
 
func imageHandler(writer http.ResponseWriter, request *http.Request, counter *expvar.Int) {
        const ImageWidth = 256
        const ImageHeight = 256
        outputimage := image.NewRGBA(image.Rectangle{image.Point{0, 0},
                image.Point{ImageWidth, ImageHeight}})
 
        for y := 0; y < ImageHeight; y++ {
                for x := 0; x < ImageWidth; x++ {
                        c := calculateColor()
                        outputimage.Set(x, y, c)
                }
        }
        png.Encode(writer, outputimage)
        counter.Add(1)
}
 
func counterHandler(writer http.ResponseWriter, request *http.Request, counter *expvar.Int) {
        writer.Header().Set("Content-Type", "text/plain")
        fmt.Fprintf(writer, "Counter: %d\n", counter.Value())
}
 
func main() {
        counter := expvar.NewInt("counter")
 
        http.HandleFunc("/", indexPageHandler)
        http.HandleFunc("/image",
                func(writer http.ResponseWriter, request *http.Request) { imageHandler(writer, request, counter) })
        http.HandleFunc("/counter",
                func(writer http.ResponseWriter, request *http.Request) { counterHandler(writer, request, counter) })
        http.ListenAndServe(":8080", nil)
}

11. Současné zveřejnění metrik i profilovacích informací

Nic nám ovšem nebrání, aby aplikace zveřejňovala jak své metriky, tak i profilovací informace (popsané minule). Řešení je jednoduché – do předchozího příkladu pouze přidáme import balíčku net/http/pprof. Žádné další změny není nutné v příkladu dělat:

package main
 
import (
        "expvar"
        "fmt"
        "image"
        "image/color"
        "image/png"
        "io"
        "math/rand"
        "net/http"
        _ "net/http/pprof"
)
 
var counter = expvar.NewInt("counter")
 
func indexPageHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/html")
        response := `
<body>
    <h1>Enterprise image renderer</h1>
    <img src="/image" width=256 height=256 />
</body>`
        io.WriteString(writer, response)
}
 
func calculateColor() color.RGBA {
        return color.RGBA{uint8(rand.Intn(255)),
                uint8(rand.Intn(255)),
                uint8(rand.Intn(255)), 255}
}
 
func imageHandler(writer http.ResponseWriter, request *http.Request) {
        const ImageWidth = 256
        const ImageHeight = 256
        outputimage := image.NewRGBA(image.Rectangle{image.Point{0, 0},
                image.Point{ImageWidth, ImageHeight}})
 
        for y := 0; y < ImageHeight; y++ {
                for x := 0; x < ImageWidth; x++ {
                        c := calculateColor()
                        outputimage.Set(x, y, c)
                }
        }
        png.Encode(writer, outputimage)
        counter.Add(1)
}
 
func counterHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/plain")
        fmt.Fprintf(writer, "Counter: %d\n", counter.Value())
}
 
func main() {
        http.HandleFunc("/", indexPageHandler)
        http.HandleFunc("/image", imageHandler)
        http.HandleFunc("/counter", counterHandler)
        http.ListenAndServe(":8080", nil)
}

Obrázek 6: Zveřejněné profilovací informace.

Obrázek 7: Současně zveřejněné metriky aplikace.

12. Nový demonstrační příklad: vykreslování Mandelbrotovy množiny se zaznamenáváním důležitých statistických informací

V dalším příkladu, který vychází ze zdrojového kódu zveřejněného minule, je zveřejněno hned několik údajů o běžící aplikaci:

  1. Počet překreslení Mandelbrotovy množiny
  2. Celkový čas, který CPU strávil výpočtem Mandelbrotových množin
  3. Počet překreslených (vybarvených) pixelů
  4. Čas výpočtu, ovšem ve formátu čitelném člověkem

Jedná se o tyto proměnné:

var renderingCounter = expvar.NewInt("renderingCounter")
var renderingDuration = expvar.NewInt("renderingDuration")
var pixelsColored = expvar.NewInt("pixelsColored")
var humanReadableDuration = expvar.NewString("humanReadableDuration")

Upravený zdrojový kód tohoto příkladu vypadá následovně:

package main
 
import (
        "expvar"
        "image"
        "image/png"
        "io"
        "net/http"
        _ "net/http/pprof"
        "time"
)
 
var renderingCounter = expvar.NewInt("renderingCounter")
var renderingDuration = expvar.NewInt("renderingDuration")
var pixelsColored = expvar.NewInt("pixelsColored")
var humanReadableDuration = expvar.NewString("humanReadableDuration")
 
func indexPageHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/html")
        response := `
<body>
    <h1>Enterprise image renderer</h1>
    <img src="/image" width=256 height=256 />
</body>`
        io.WriteString(writer, response)
}
 
func iterCount(cx float64, cy float64, maxiter uint) uint {
        var zx float64 = 0.0
        var zy float64 = 0.0
        var i uint = 0
        for i < maxiter {
                zx2 := zx * zx
                zy2 := zy * zy
                if zx2+zy2 > 4.0 {
                        break
                }
                zy = 2.0*zx*zy + cy
                zx = zx2 - zy2 + cx
                i++
        }
        return i
}
 
func calcMandelbrot(width uint, height uint, maxiter uint, palette [][3]byte, image []byte, cy float64, done chan bool) {
        var cx float64 = -2.0
        for x := uint(0); x < width; x++ {
                i := iterCount(cx, cy, maxiter)
                color := palette[i]
                image[3*x] = color[0]
                image[3*x+1] = color[1]
                image[3*x+2] = color[2]
                cx += 3.0 / float64(width)
        }
        done <- true
}
 
func writeImage(width uint, height uint, pixels []byte) *image.NRGBA {
        img := image.NewNRGBA(image.Rect(0, 0, int(width), int(height)))
        pixel := 0
 
        for y := 0; y < int(height); y++ {
                offset := img.PixOffset(0, y)
                for x := uint(0); x < width; x++ {
                        img.Pix[offset] = pixels[pixel]
                        img.Pix[offset+1] = pixels[pixel+1]
                        img.Pix[offset+2] = pixels[pixel+2]
                        img.Pix[offset+3] = 0xff
                        pixel += 3
                        offset += 4
                }
        }
        return img
}
 
func calculateFractal(width int, height int, maxiter int) []byte {
        done := make(chan bool, height)
 
        pixels := make([]byte, width*height*3)
        offset := 0
        delta := width * 3
 
        var cy float64 = -1.5
        for y := 0; y < height; y++ {
                go calcMandelbrot(uint(width), uint(height), uint(maxiter), mandmap[:], pixels[offset:offset+delta], cy, done)
                offset += delta
                cy += 3.0 / float64(height)
        }
        for i := 0; i < height; i++ {
                <-done
        }
 
        return pixels
}
 
func imageHandler(writer http.ResponseWriter, request *http.Request) {
        const ImageWidth = 256
        const ImageHeight = 256
 
        t1 := time.Now()
 
        pixels := calculateFractal(ImageWidth, ImageHeight, 255)
        outputimage := writeImage(ImageWidth, ImageHeight, pixels)
        png.Encode(writer, outputimage)
 
        t2 := time.Now()
        elapsed := t2.Sub(t1)
 
        renderingCounter.Add(1)
        renderingDuration.Set(int64(elapsed))
        pixelsColored.Set(ImageWidth * ImageHeight)
        humanReadableDuration.Set(elapsed.String())
}
 
func main() {
        http.HandleFunc("/", indexPageHandler)
        http.HandleFunc("/image", imageHandler)
        http.ListenAndServe(":8080", nil)
}
 
/* taken from Fractint */
 
var mandmap = [...][3]byte{
        {255, 255, 255}, {224, 224, 224}, {216, 216, 216}, {208, 208, 208},
        {200, 200, 200}, {192, 192, 192}, {184, 184, 184}, {176, 176, 176},
        {168, 168, 168}, {160, 160, 160}, {152, 152, 152}, {144, 144, 144},
        ...
        ...
        ...
        {240, 240, 140}, {244, 244, 152}, {244, 244, 168}, {244, 244, 180},
        {244, 244, 196}, {248, 248, 208}, {248, 248, 224}, {248, 248, 236},
        {252, 252, 252}, {248, 248, 248}, {240, 240, 240}, {232, 232, 232}}

Obrázek 8: Nový HTTP server v akci.

13. Sledování činnosti HTTP serveru

Pro sledování činnosti HTTP serveru, který počítá a zobrazuje rastrový obrázek Mandelbrotovy množiny můžeme v tom nejjednodušším případě opět použít nástroj curl.

Před prvním překreslením:

$ curl localhost:8080/debug/vars
 
{
"cmdline": ["/tmp/go-build480851913/b001/exe/08_image_server_fractal"],
"humanReadableDuration": "",
"memstats": {"Alloc":308736,"TotalAlloc":308736,"Sys":69926912,"Lookups":0,"Mall
...
...
...
"pixelsColored": 0,
"renderingCounter": 0,
"renderingDuration": 0
}

Po dvou překresleních:

$ curl localhost:8080/debug/vars
 
{
...
...
...
"humanReadableDuration": "44.726908ms",
...
...
...
"pixelsColored": 65536,
"renderingCounter": 2,
"renderingDuration": 44726908

14. Zpracování metrik programově

V tomto okamžiku již víme, jakým způsobem můžeme metriky popisující aktuální stav běžící aplikace získat. Nyní nám vlastně zbývá jen „maličkost“ – umět získaná data zpracovat a následně nějakým způsobem interpretovat. K tomuto účelu je možné použít buď již hotové nástroje (Prometheus aj.), nebo data zpracovat vlastními silami, což je samozřejmě mnohem zajímavější. Podívejme se tedy na způsob přečtení metrik jednoduchým skriptem naprogramovaným v Pythonu. Pro samotný přenos dat z aplikace do skriptu použijeme populární knihovnu Requests. První verze skriptu pouze data přečte a vypíše je na standardní výstup, bez jejich dalšího zpracování:

import requests
 
response = requests.get("http://localhost:8080/debug/vars")
 
assert response.status_code == 200, "Chyba při čtení metrik: neočekávaný HTTP kód odpovědi"
 
payload = response.json()
 
print("Počet překreslení: ", payload["renderingCounter"])
print("Celkový čas výpočtu: ", payload["humanReadableDuration"])

Příklad výsledku činnosti tohoto skriptu:

Počet překreslení:  1
Celkový čas výpočtu:  46.758702ms
Počet obarvených pixelů:  65536

15. Složitější skript: uložení výsledků do tabulky ve formátu CSV

Pochopitelně nám ovšem nic nebrání v tom, aby skript se získanými daty prováděl další manipulace. Jeho druhá varianta přečtená data uloží do datového souboru mandelbrot.csv; přesněji řečeno po každém spuštění se do tohoto souboru přidá další řádek s výsledky měření. Pokud tedy skript budeme spouštět v pravidelných intervalech (cron apod.), bude výsledkem tabulka obsahující postupný vývoj všech měřených hodnot:

import requests
import os
import csv
 
filename = "mandelbrot.csv"
 
response = requests.get("http://localhost:8080/debug/vars")
 
assert response.status_code == 200, "Chyba při čtení metrik: neočekávaný HTTP kód odpovědi"
 
payload = response.json()
 
mode = None
write_header = False
 
if os.path.exists(filename):
    mode = "a"
else:
    mode = "w"
    write_header = True
 
with open(filename, mode) as csv_file:
    writer = csv.writer(csv_file, delimiter=',')
    if write_header:
        writer.writerow(("Počet překreslení", "Čas výpočtu", "Obarvených pixelů"))
    writer.writerow((payload["renderingCounter"],
                     payload["humanReadableDuration"],
                     payload["pixelsColored"]))

Příklad výstupu postupně generovaného předchozím skriptem:

Počet překreslení,Čas výpočtu,Obarvených pixelů
0,,0
1,43.446635ms,65536
2,40.86645ms,65536
6,41.091674ms,65536

Obrázek 9: Zobrazení tabulky v tabulkovém procesoru.

16. Kumulativní časy

Příklad samozřejmě můžeme upravit tak, aby se počítaly kumulativní časy, obarvené pixely atd.:

renderingCounter.Add(1)
renderingDuration.Add(int64(elapsed))
pixelsColored.Add(ImageWidth * ImageHeight)
humanReadableDuration.Set(elapsed.String())
import requests
import os
import csv
 
filename = "mandelbrot.csv"
 
response = requests.get("http://localhost:8080/debug/vars")
 
assert response.status_code == 200, "Chyba při čtení metrik: neočekávaný HTTP kód odpovědi"
 
payload = response.json()
 
mode = None
write_header = False
 
if os.path.exists(filename):
    mode = "a"
else:
    mode = "w"
    write_header = True
 
with open(filename, mode) as csv_file:
    writer = csv.writer(csv_file, delimiter=',')
    if write_header:
        writer.writerow(("Počet překreslení", "Čas výpočtu", "Obarvených pixelů", "Poslední výpočet"))
    writer.writerow((payload["renderingCounter"],
                     payload["renderingDuration"],
                     payload["pixelsColored"],
                     payload["humanReadableDuration"]))

Výsledek pěti měření (CSV formát):

Počet překreslení,Čas výpočtu,Obarvených pixelů,Poslední výpočet
0,0,0,
1,41073963,65536,41.073963ms
2,83259556,131072,42.185593ms
3,126872642,196608,43.613086ms
4,173997267,262144,47.124625ms

Obrázek 10: Zobrazení tabulky s výsledky v tabulkovém procesoru.

17. Další datové typy podporované modulem expvar

Při popisu možností balíčku expvar jsme se mj. zmínili i o podporovaných datových typech proměnných, které může aplikace „zviditelnit“. Použití datových typů Int, Float a String je pravděpodobně zřejmé, ovšem zajímavější je situace u zbývajících dvou typů: funkcí a map. Funkce, pokud je použita jako typ „zviditelněné proměnné“, je zavolána ve chvíli, kdy je zapotřebí získat metriky. Výsledkem této funkce jsou data, která mají být zveřejněna:

type Metrics struct {
        Counter  int
        Duration int
}

func MetricsFunc() interface{} {
        return Metrics{
                Counter:  1,
                Duration: -1,
        }
}

Zveřejnění metriky zajistí zavolání:

expvar.Publish("metrics", expvar.Func(MetricsFunc))

Obrázek 11: Nově zveřejněná metrika.

18. Dodatek: různé metody práce s čítačem zvyšovaným v gorutinách

V předchozích kapitolách jsme se mj. zabývali i problémem práce s obsahem proměnné, která je používána větším množstvím gorutin. Programovací jazyk Go neobsahuje žádnou obdobu klíčového slova volatile, což je ostatně dobře, protože význam tohoto slova je v různých programovacích jazycích dosti odlišný (v první skupině jsou jazyky C a C++, ve druhé pak Java a .NET). Pokud například budeme v Go měnit obsah globální proměnné, a to v mnoha gorutinách, nebudou výsledky odpovídat očekávání, protože operace čtení a zápisu nebudou synchronizovány. O tom se můžeme snadno přesvědčit po spuštění následujícího příkladu, v němž se obsah globálního čítače zvýší 1000×, ovšem výsledkem nebude hodnota 1000, ale hodnota nižší (protože dojde k překryvu operací čtení hodnoty proměnné a jejího zápisu – více gorutin bude zvyšovat stejnou hodnotu, popř. provede zápis výsledku, který již ovšem byl jinou gorutinou změněn):

package main
 
import (
        "fmt"
        "time"
)
 
func main() {
        var cnt int = 0
 
        for i := 0; i < 1000; i++ {
                go func() {
                        cnt++
                }()
        }
        time.Sleep(1000 * time.Millisecond)
        fmt.Printf("%d\n", cnt)
}

Výsledek běhu tohoto programu (bude se obecně lišit s každým spuštěním aplikace):

952
Poznámka: dokonce může dojít k tomu, že čítač bude obsahovat zcela nesmyslné hodnoty, protože není zaručeno, že na všech architekturách se vždy zapíšou současně všechny bity čítače.

Jedno z možných řešení tohoto problému spočívá v použití synchronizace gorutin s využitím mutexu (jedna z forem zámku). Před změnou obsahu čítače se gorutina pokusí o získání mutexu a pokud uspěje, provede změnu obsahu čítače (přečtení hodnoty, zvýšení o jedničku, zápis výsledku) a zámek opět uvolní. V případě, že bude zámek držen jinou gorutinou, musí se počkat (takže se původní paralelní běh kódu v tomto místě opět „serializuje“):

package main
 
import (
        "fmt"
        "sync"
        "time"
)
 
func main() {
        var cnt int32 = 0
        var mutex = &sync.Mutex{}
 
        for i := 0; i < 1000; i++ {
                go func() {
                        mutex.Lock()
                        cnt++
                        mutex.Unlock()
                }()
        }
        time.Sleep(1000 * time.Millisecond)
        fmt.Printf("%d\n", cnt)
}

Výsledek běhu tohoto programu:

1000

Použití mutexů ovšem není zcela bez problémů, protože při špatném naprogramování může docházet k deadlockům.

V případě jednoduchého čítače je výhodnější využít spíše již zmíněný balíček sync/atom s implementací atomických operací. Interně se samozřejmě opět musí řešit nějaký typ zámku, ovšem tato činnost je před programátorem skryta. A hlavně – nedojde k situaci, kdy by programátor zapomněl zámek uvolnit nebo uvolňoval zámky ve špatném pořadí; tudíž se eliminuje možnost vzniku deadlocku:

package main
 
import (
        "fmt"
        "sync/atomic"
        "time"
)
 
func main() {
        var cnt int32 = 0
 
        for i := 0; < 1000; i++ {
                go func() {
                        atomic.AddInt32(&cnt, 1)
                }()
        }
        time.Sleep(1000 * time.Millisecond)
        fmt.Printf("%d\n", cnt)
}

Výsledek běhu tohoto programu:

1000

19. 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ě dva megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Příklad Popis příkladu Cesta
1 01_image_server.go jednoduchý HTTP server, který generuje dynamickou HTML stránku a rastrový obrázek s náhodnými barvami pixelů https://github.com/tisnik/go-root/blob/master/article31/01_i­mage_server.go
2 02_image_server_counter.go úprava HTTP serveru takovým způsobem, že je možné zobrazit i stav čítačů https://github.com/tisnik/go-root/blob/master/article31/02_i­mage_server_counter.go
3 03_image_server_counter_mutex.go čítač je zvyšován v sekci synchronizované s ostatními gorutinami https://github.com/tisnik/go-root/blob/master/article31/03_i­mage_server_counter_mutex­.go
4 04_image_server_counter_atom.go čítač je zvyšován funkcí umožňující atomické změny proměnné https://github.com/tisnik/go-root/blob/master/article31/04_i­mage_server_counter_atom.go
5 05_image_server_expvar.go zveřejnění informací o běžící aplikaci https://github.com/tisnik/go-root/blob/master/article31/05_i­mage_server_expvar.go
6 06_image_server_expvar_closure.go použití uzávěrů a eliminace globálních proměnných https://github.com/tisnik/go-root/blob/master/article31/06_i­mage_server_expvar_closure­.go
7 07_image_server_expvar_pprof.go zveřejnění informací o běžící aplikaci a současně i profilovacích záznamů https://github.com/tisnik/go-root/blob/master/article31/07_i­mage_server_expvar_pprof.go
8 08_image_server_fractal.go generování Mandelbrotovy množiny se zveřejněním metrik https://github.com/tisnik/go-root/blob/master/article31/08_i­mage_server_fractal.go
9 09_counter.go čítač zvyšovaný v několika gorutinách https://github.com/tisnik/go-root/blob/master/article31/09_cou­nter.go
10 10_counter_lock.go použití mutexu při přístupu k obsahu čítače https://github.com/tisnik/go-root/blob/master/article31/10_cou­nter_lock.go
11 11_counter_atomic.go použití funkce pro atomické zvýšení hodnoty čítače https://github.com/tisnik/go-root/blob/master/article31/11_cou­nter_atomic.go
       
12 get_metrics1.py přečtení metrik jednoduchým skriptem psaným v Pythonu https://github.com/tisnik/go-root/blob/master/article31/get_me­trics1.py
13 get_metrics2.py přečtení metrik jednoduchým skriptem psaným v Pythonu s exportem do CSV https://github.com/tisnik/go-root/blob/master/article31/get_me­trics2.py
14 get_metrics3.py přečtení metrik jednoduchým skriptem psaným v Pythonu s exportem do CSV https://github.com/tisnik/go-root/blob/master/article31/get_me­trics3.py

20. Odkazy na Internetu

  1. How to instrument Go code with custom expvar metrics
    https://sysdig.com/blog/golang-expvar-custom-metrics/
  2. Golang expvar metricset (Metricbeat Reference)
    https://www.elastic.co/gu­ide/en/beats/metricbeat/7­.x/metricbeat-metricset-golang-expvar.html
  3. Package expvar
    https://golang.org/pkg/expvar/#NewInt
  4. Java Platform Debugger Architecture: Overview
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jpda/jpda­.html
  5. The JVM Tool Interface (JVM TI): How VM Agents Work
    https://www.oracle.com/technet­work/articles/javase/index-140680.html
  6. JVM Tool Interface Version 11.0
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jvmti­.html
  7. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  8. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  9. IBM JVMTI extensions
    http://publib.boulder.ibm­.com/infocenter/realtime/v2r0/in­dex.jsp?topic=%2Fcom.ibm.sof­trt.doc%2Fdiag%2Ftools%2Fjvmti_ex­tensions.html
  10. Go & cgo: integrating existing C code with Go
    http://akrennmair.github.io/golang-cgo-slides/#1
  11. Using cgo to call C code from within Go code
    https://wenzr.wordpress.com/2018/06/07/u­sing-cgo-to-call-c-code-from-within-go-code/
  12. Package trace
    https://golang.org/pkg/runtime/trace/
  13. Introducing HTTP Tracing
    https://blog.golang.org/http-tracing
  14. Command trace
    https://golang.org/cmd/trace/
  15. A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
    https://github.com/wesovilabs/koazee
  16. Funkce vyššího řádu v knihovně Underscore
    https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/
  17. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  18. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  19. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  20. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  21. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  22. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  23. 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/
  24. Debugging Go Code with GDB
    https://golang.org/doc/gdb
  25. Debugging Go (golang) programs with gdb
    https://thornydev.blogspot­.com/2014/01/debugging-go-golang-programs-with-gdb.html
  26. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  27. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  28. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  29. The LLDB Debugger
    http://lldb.llvm.org/
  30. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  31. 13 Linux Debuggers for C++ Reviewed
    http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817
  32. 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
  33. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  34. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  35. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  36. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  37. 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/
  38. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  39. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  40. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  41. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  42. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  43. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  44. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  45. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  46. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  47. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  48. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  49. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  50. go-cron
    https://github.com/rk/go-cron
  51. gocron
    https://github.com/jasonlvhit/gocron
  52. clockwork
    https://github.com/whiteShtef/cloc­kwork
  53. clockwerk
    https://github.com/onatm/clockwerk
  54. JobRunner
    https://github.com/bamzi/jobrunner
  55. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  56. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  57. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  58. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  59. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  60. go-prompt
    https://github.com/c-bata/go-prompt
  61. readline
    https://github.com/chzyer/readline
  62. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  63. go-readline
    https://github.com/fiorix/go-readline
  64. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  65. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  66. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  67. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  68. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  69. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  70. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  71. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  72. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  73. Editline Library (libedit)
    http://thrysoee.dk/editline/
  74. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  75. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  76. WinEditLine
    http://mingweditline.sourceforge.net/
  77. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  78. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  79. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  80. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  81. history(3) – Linux man page
    https://linux.die.net/man/3/history
  82. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  83. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  84. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  85. Balíček ogletest
    https://github.com/jacobsa/ogletest
  86. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  87. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  88. package testing
    https://golang.org/pkg/testing/
  89. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  90. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  91. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  92. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  93. GoConvey
    http://goconvey.co/
  94. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  95. 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
  96. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  97. package gg
    https://godoc.org/github.com/fo­gleman/gg
  98. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  99. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  100. The Go image package
    https://blog.golang.org/go-image-package
  101. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  102. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  103. YAML
    https://yaml.org/
  104. edn
    https://github.com/edn-format/edn
  105. Smile
    https://github.com/FasterXML/smile-format-specification
  106. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  107. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  108. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  109. Introducing JSON
    http://json.org/
  110. Package json
    https://golang.org/pkg/encoding/json/
  111. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  112. Go by Example: JSON
    https://gobyexample.com/json
  113. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  114. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  115. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  116. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  117. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  118. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  119. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  120. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  121. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  122. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  123. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  124. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  125. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  126. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  127. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  128. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  129. Algorithms to Go
    https://yourbasic.org/
  130. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  131. 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/
  132. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  133. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  134. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  135. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  136. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  137. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  138. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  139. The Go Programming Language (home page)
    https://golang.org/
  140. GoDoc
    https://godoc.org/
  141. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  142. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  143. The Go Programming Language Specification
    https://golang.org/ref/spec
  144. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  145. Package builtin
    https://golang.org/pkg/builtin/
  146. Package fmt
    https://golang.org/pkg/fmt/
  147. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  148. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  149. Learning Go
    https://www.miek.nl/go/
  150. Go Bootcamp
    http://www.golangbootcamp.com/
  151. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  152. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  153. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  154. The Go Blog
    https://blog.golang.org/
  155. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  156. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  157. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  158. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  159. 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
  160. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  161. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  162. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  163. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  164. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  165. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  166. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  167. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  168. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  169. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  170. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  171. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  172. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  173. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  174. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  175. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  176. 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/
  177. 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
  178. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  179. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  180. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  181. Go vs. Python
    https://www.peterbe.com/plog/govspy
  182. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  183. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  184. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  185. Go by Example: Slices
    https://gobyexample.com/slices
  186. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  187. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  188. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  189. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  190. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  191. nils In Go
    https://go101.org/article/nil.html
  192. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  193. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  194. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  195. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  196. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  197. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  198. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  199. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  200. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  201. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  202. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  203. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  204. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  205. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  206. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  207. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  208. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  209. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  210. 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
  211. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  212. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  213. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  214. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  215. Selectors
    https://golang.org/ref/spec#Selectors
  216. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  217. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  218. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  219. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  220. Part 21: Goroutines
    https://golangbot.com/goroutines/
  221. Part 22: Channels
    https://golangbot.com/channels/
  222. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  223. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  224. 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/
  225. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  226. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  227. Control Structures
    https://www.golang-book.com/books/intro/5
  228. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  229. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  230. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  231. Goroutine IDs
    https://blog.sgmansfield.com/2015/12/go­routine-ids/
  232. Different ways to pass channels as arguments in function in go (golang)
    https://stackoverflow.com/qu­estions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang
  233. justforfunc #22: using the Go execution tracer
    https://www.youtube.com/wat­ch?v=ySy3sR1LFCQ
  234. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  235. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  236. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  237. Effective Go
    https://golang.org/doc/ef­fective_go.html
  238. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  239. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation
  240. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  241. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  242. Nils in Go
    https://www.doxsey.net/blog/nils-in-go