Obsah
1. Sledování vybraných metrik služeb naprogramovaných v jazyku Go
2. Nativní aplikace vytvořená v Go versus aplikace běžící ve virtuálních strojích
3. Zopakování z minula: jednoduchý HTTP server s dynamicky generovaným obsahem
4. Zveřejnění obsahu čítačů přes explicitně naprogramované koncové body API
5. Mutex pro synchronizaci změny obsahu čítače
6. Použití funkcí zajišťujících atomickou změnou hodnoty pro realizaci čítačů
7. Úprava HTTP serveru s čítači s atomickou změnou hodnoty
9. Úprava HTTP serveru tak, aby byly čítače dostupné přes standardní rozhraní
10. Úprava předchozího příkladu a odstranění globálních proměnných ze zdrojového kódu
11. Současné zveřejnění metrik i profilovací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
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
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.
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) }
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 |
# | 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")
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 ... ... ...
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:
- Počet překreslení Mandelbrotovy množiny
- Celkový čas, který CPU strávil výpočtem Mandelbrotových množin
- Počet překreslených (vybarvených) pixelů
- Č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
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_image_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_image_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_image_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_image_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_image_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_image_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_image_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_image_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_counter.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_counter_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_counter_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_metrics1.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_metrics2.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_metrics3.py |
20. Odkazy na Internetu
- 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