Obsah
1. Trasování a profilování aplikací naprogramovaných v Go (dokončení)
2. Jednoduchý HTTP server s dynamicky generovaným obsahem
3. Přidání podpory pro poskytování trasovacích informací
5. Ukázka poskytovaných informací
6. Složitější HTTP server s výpočty probíhajícími v gorutinách
7. Trasování a balíček runtime/trace
8. Trasování při spuštění testů
10. Jednoduchý demonstrační příklad s přímou podporou trasování
11. Kontext (context) a úlohy (task)
12. Zobrazení úlohy na stránce s výsledky běhu traceru
13. Rozdělení celé úlohy na regiony
14. Zobrazení regionů na stránce s výsledky běhu traceru
15. Hierarchie regionů a zápis logovacích zpráv do souboru s trasovacími informacemi
16. Zobrazení regionů a logovacích informací na stránce s výsledky běhu traceru
17. Úprava projektu pro vykreslení Mandelbrotovy množiny pro přímou podporu trasování
18. Úplné trasovací informace společně s logováním do souboru generovaného tracerem
19. Repositář s demonstračními příklady
1. Trasování a profilování aplikací naprogramovaných v Go (dokončení)
V předchozí části seriálu o programovacím jazyce Go jsme si ukázali dva způsoby využití standardního profileru, který je součástí standardních nástrojů dodávaných společně s překladačem Go. Ještě si však musíme vysvětlit jednu důležitou oblast, v níž se profiler používá. Jedná se o zajištění (v případě potřeby i kontinuálního) profilingu síťových aplikací, webových aplikací a služeb (démonů), protože jazyk Go je v této oblasti poměrně intenzivně používán. Tomuto tématu se budeme věnovat v první části dnešního článku. V části druhé se naproti tomu budeme zabývat takzvaným tracingem, který je opět podporován v základní sadě nástrojů jazyka Go.
V mnoha vyspělých virtuálních strojích navržených pro spouštění aplikací (typicky přeložených do bajtkódu daného virtuálního stroje) většinou nalezneme i podporu pro sledování běžící aplikace, zjišťování metrik, trasování a někdy i profilování, a to i ve chvíli, kdy aplikace právě běží (a podobně sledované aplikace mohou bez restartu běžet i několik měsíců). Jedná se o velmi užitečnou vlastnost, protože u mnoha aplikací dochází k problematickému chování nikoli v době jejich testování, ale až po nasazení do předprodukčního nebo dokonce až produkčního prostředí. Virtuálním strojem, který tuto podporu nabízí, je i JVM (Java Virtual Machine), kterému jsme se poměrně podrobně věnovali v seriálu Seriál Programovací jazyk Java a JVM. Nyní tedy na chvíli odbočíme a řekneme si, jaké možnosti nám nabízí JVM.
Virtuální stroj Javy administrátorům a devops týmům nabízí několik rozhraní určených pro sledování aplikací, přičemž se tato rozhraní od sebe odlišují zejména tím, do jaké míry jsou nízkoúrovňová (a tedy i obecnější) či naopak vysokoúrovňová.
Na nejnižší úrovni leží rozhraní JVM TI, neboli celým názvem Java Virtual Machine Tools Interface, které lze využít pro sledování činnosti virtuálního stroje Javy i pro jeho částečné řízení. Toto rozhraní, které bylo poprvé implementováno v J2SE 5.0 může být využito k provádění mnoha různých operací. Například je možné sledovat práci správce paměti (GC – garbage collector), přistupovat k objektům uloženým na heapu, zjišťovat volané metody, detekovat a sledovat překlad bajtkódu JVM do nativního strojového kódu dané platformy, nastavovat a posléze i využívat breakpointy, detekovat výjimky v javovských aplikacích (a to i ty výjimky, které jsou zachycené v bloku catch) atd. JVM TI je využíváno debuggery a profilery, které potřebují přistupovat k běžící JVM, ovšem vlastnosti tohoto rozhraní lze využít i v dalších typech nástrojů, například pro zjišťování pokrytí kódu testy, detekci přístupu k určitým souborům apod.
Rozhraní JVM TI může být využíváno i takzvanými agenty, což jsou nativní knihovny, které jsou v tom nejjednodušším případě načteny při startu JVM a běží ve stejném procesu, jako samotná JVM. Agenti mohou přes rozhraní JVM TI oboustranně komunikovat s virtuálním strojem Javy. Komunikace směrem agent→JVM probíhá jednoduše voláním funkcí nabízených rozhraním, zpětná komunikace JVM→agent je zabezpečena pomocí takzvaných callback funkcí, které musí být nejdříve zaregistrovány pro různé typy událostí, které mohou ve virtuálním stroji nastat (mezi událost může například patřit spuštění správce paměti, vznik výjimky atd.). Vzhledem k tomu, že agenti musí být překládáni do nativní knihovny, používá se pro jejich implementaci většinou programovací jazyk C či C++ (popř. jakýkoli jiný jazyk podporující céčkovskou konvenci volání funkcí), ovšem samotný agent je většinou poměrně kompaktní knihovna nabízející své vlastní rozhraní externím procesům – takto lze například realizovat debugger nebo jednoduchý monitorovací nástroj.
Nad rozhraním JVM TI je vytvořen komunikační kanál tvořený protokolem JDWP (Java Debug Wire Protocol) a nad tímto protokolem bylo vytvořeno plnohodnotné javovské rozhraní nazvané JDI (Java Debug Interface). JVM TI, JDWP (společně s poměrně jednoduchým rozhraním k tomuto protokolu JDWPI) a JDI tvoří ucelenou třívrstvou architekturu nazvanou Java Platform Debugger Architecture neboli zkráceně JPDA. Jak již název této architektury naznačuje, lze ji použít například pro implementaci různých ladicích nástrojů (debuggerů), ale i nástrojů monitorovacích, nástrojů umožňujících „hotswap“ tříd či jejich metod do běžícího virtuálního stroje Javy apod.
Z tohoto důvodu jsou funkce nabízené jednou ze tří zmíněných technologií velmi často využívány i integrovanými vývojovými prostředími (IDE). Na provádění nízkoúrovňových operací je přitom používáno rozhraní JVM TI a pro implementaci vysokoúrovňových funkcí pak JDI, popř. se s virtuálním strojem Javy, v němž byla spuštěna vyvíjená aplikace, komunikuje přímo s využitím protokolu JDWP (to je však většinou zbytečně komplikované). Vzájemný vztah mezi rozhraním JVM TI, protokolem JDWP a rozhraním JDI je zobrazen na následujícím schématu:
Obrázek 1: Vzájemný vztah mezi (céčkovým) rozhraním JVM TI, protokolem JDWP a javovským rozhraním JDI.
A nakonec nesmíme zapomenout ani na JMX umožňující, aby běžící aplikace dávala k dispozici svoji konfiguraci, metriky atd. Ty je možné číst a popřípadě i modifikovat nástroji, které s rozhraním dokážou pracovat. Mezi tyto nástroje patří i standardní JConsole.
Obrázek 2: Sledování a řízení Apache MQ přes standardní rozhraní JMX z nástroje JConsole.
2. Jednoduchý HTTP server s dynamicky generovaným obsahem
Proč se vlastně o virtuálním stroji jazyka Java a jeho možnostech vůbec zmiňuji v článku, který by měl být primárně věnován programovacímu jazyku Go? Jazyk Go má totiž s Javou částečný překryv oblasti použití (neboli obsazuje stejné niky), takže je namístě otázka, zda nám runtime jazyka Go nabízí podobnou funkcionalitu, jako celý virtuální stroj Javy; samozřejmě s ohledem na to, že runtime Go je mnohem menší (přibližně jeden až dva megabajty strojového kódu). Ve skutečnosti i v ekosystému Go nalezneme podobně koncipovanou technologii, kterou lze zprovoznit následujícím způsobem:
- Aplikace, kterou potřebujeme sledovat, spustí HTTP server. Buď se bude jednat o server používaný pouze pro sledování aplikace, nebo se může jednat o server, který kromě jiného nabízí ty služby, pro které je aplikace naprogramována.
- Naimportuje se balíček , který zajistí, že HTTP server bude dodávat mj. i ladicí informace, a to pochopitelně přes HTTP protokol (dokonce s generováním interaktivních HTML stránek).
- Po spuštění aplikace a inicializaci HTTP serveru je možné se k běžící aplikaci připojit a „živě“ sledovat její chování. Díky použití HTTP je implementace případného klienta vlastně velmi jednoduchá.
- Navíc je možné s využitím balíčku expvar dát k dispozici i další metriky běžící aplikace. Tomuto tématu se budeme podrobněji věnovat příště.
Podívejme se nyní na jednoduchou aplikaci, která již obsahuje HTTP server a jejíž činnost budeme chtít sledovat. Jedná se o značně primitivní webový server, který po otevření cesty / vrátí webovou stránku s odkazem na rastrový obrázek dostupným pod cestou /image. Tento obrázek je taktéž generován HTTP serverem – obsahuje pixely s náhodnou barvou (což je mimochodem fakt, který příliš „nepotěší“ algoritmus DEFLATE použitý při exportu rastrového obrázku do formátu PNG). Úplný zdrojový kód tohoto HTTP serveru vypadá následovně:
package main import ( "image" "image/color" "image/png" "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>` writer.Write([]byte(response)) } func calculateColor() color.RGBA { return color.RGBA{uint8(rand.Intn(255)), uint8(rand.Intn(255)), uint8(rand.Intn(255)), 255} } func imageHandler(w http.ResponseWriter, r *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(w, outputimage) } func main() { http.HandleFunc("/", indexPageHandler) http.HandleFunc("/image", imageHandler) http.ListenAndServe(":8080", nil) }
3. Přidání podpory pro poskytování trasovacích informací
Úprava našeho HTTP serveru takovým způsobem, aby poskytoval i metriky s informacemi o běhu aplikace, je vlastně velmi primitivní. Pouze je zapotřebí naimportovat balíček net/http/pprof; to je vše, protože zbytek je zařízen funkcí init() zavolanou po importu. Vzhledem k tomu, že tento balíček nebudeme explicitně používat (volat jeho funkce), je nutné před jeho jméno vložit podtržítko, protože v opačném případě by překladač Go zcela správně vypsal chybové hlášení, že se snažíme importovat balíček, který není použit:
import ( "image" "image/png" "net/http" _ "net/http/pprof" )
Obrázek 3: Stránka s obrázkem generovaná HTTP serverem implementovaným v předchozím příkladu.
4. Nová podoba HTTP serveru
Zdrojový kód nové podoby HTTP serveru, který nyní bude poskytovat i profilovací informace, naleznete na adrese https://github.com/tisnik/go-root/blob/master/article30/02_image_server_with_pprof.go. Oproti předchozímu zdrojovému kódu skutečně došlo jen k minimální změně – importu balíčku net/http/pprof:
package main import ( "image" "image/color" "image/png" "math/rand" "net/http" _ "net/http/pprof" ) 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>` writer.Write([]byte(response)) } func calculateColor() color.RGBA { return color.RGBA{uint8(rand.Intn(255)), uint8(rand.Intn(255)), uint8(rand.Intn(255)), 255} } func imageHandler(w http.ResponseWriter, r *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(w, outputimage) } func main() { http.HandleFunc("/", indexPageHandler) http.HandleFunc("/image", imageHandler) http.ListenAndServe(":8080", nil) }
5. Ukázka poskytovaných informací
Pokud nyní HTTP server spustíme, bude stále poskytovat původní služby pod adresami / a /image:
Obrázek 4: Původní funkce HTTP serveru zůstane zachována.
Ovšem kromě toho se automaticky vytvoří i další cesty, které jsou v době běhu aplikace přístupné. Úvodní stránka s profilovacími informacemi je dostupná na adrese /debug/pprof:
Obrázek 5: Úvodní stránka s profilovacími informacemi.
Tato stránka je vytvořena takovým způsobem, že pro její zobrazení bude dostačovat i prohlížeč typu lynx či links:
Obrázek 6: Úvodní stránka s profilovacími informacemi, nyní zobrazená v Lynxu.
Mnoho informací je dostupných v čisté textové podobě, například zásobníkové rámce jednotlivých gorutin. Ty si můžeme prohlédnout přímo ve webovém prohlížeči nebo si je stáhnout nástroji wget a curl:
$ curl http://localhost:8080/debug/pprof/goroutine?debug=2
S výsledkem (zde zkráceným):
goroutine 18 [running]: runtime/pprof.writeGoroutineStacks(0x838840, 0xc00013e380, 0x40d6bf, 0xc000081980) /opt/go/src/runtime/pprof/pprof.go:678 +0xa7 ... ... ... goroutine 1 [IO wait]: internal/poll.runtime_pollWait(0x7fe139ec7f00, 0x72, 0x0) /opt/go/src/runtime/netpoll.go:173 +0x66 internal/poll.(*pollDesc).wait(0xc000126098, 0x72, 0xc00005c000, 0x0, 0x0) /opt/go/src/internal/poll/fd_poll_runtime.go:85 +0x9a internal/poll.(*pollDesc).waitRead(0xc000126098, 0xffffffffffffff00, 0x0, 0x0) /opt/go/src/internal/poll/fd_poll_runtime.go:90 +0x3d internal/poll.(*FD).Accept(0xc000126080, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0) /opt/go/src/internal/poll/fd_unix.go:384 +0x1a0 net.(*netFD).accept(0xc000126080, 0x40d6bf, 0xc000436000, 0xa0) /opt/go/src/net/fd_unix.go:238 +0x42 net.(*TCPListener).accept(0xc00012a008, 0xc000095db8, 0x71af28ce, 0x29e846d03867962b) /opt/go/src/net/tcpsock_posix.go:139 +0x2e net.(*TCPListener).AcceptTCP(0xc00012a008, 0xc000095de0, 0x49e5f6, 0x5d0660f6) /opt/go/src/net/tcpsock.go:247 +0x47 net/http.tcpKeepAliveListener.Accept(0xc00012a008, 0xc000095e30, 0x18, 0xc000000300, 0x68fb65) /opt/go/src/net/http/server.go:3232 +0x2f net/http.(*Server).Serve(0xc0001281a0, 0x83bcc0, 0xc00012a008, 0x0, 0x0) /opt/go/src/net/http/server.go:2826 +0x22f net/http.(*Server).ListenAndServe(0xc0001281a0, 0xc0001281a0, 0x7f03f0) /opt/go/src/net/http/server.go:2764 +0xb6 net/http.ListenAndServe(0x7d42ff, 0x5, 0x0, 0x0, 0xc000095f88, 0xc000076058) /opt/go/src/net/http/server.go:3004 +0x74 main.main() /home/tester/go-root/article_30/02_image_server_with_pprof.go:46 +0x8c
Informace o paměťovém subsystému jazyka Go v textové (čitelné) podobě získáme opět nástrojem curl nebo přímo ve webovém prohlížeči:
$ curl http://localhost:8080/debug/pprof/alloc?debug=1
S výsledkem (zde taktéž zkráceným):
# 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 # PauseNs = [294592 92207 81090 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] # PauseEnd = [1560699131094663288 1560699252313448267 1560699375977620410 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] # NumGC = 3 # NumForcedGC = 0 # GCCPUFraction = 2.9504534000959272e-06 # DebugGC = false
V případě, že neuvedete parametr debug, bude vyžadovaná informace poskytnuta v binárním formátu určeném pro další zpracování nástrojem pprof nebo trace. Opět si ukažme způsob použití. Tentokrát namísto curl použijeme wget a uložíme výsledek do souboru nazvaného „goroutines.pprof“:
$ wget -O goroutines.pprof http://localhost:8080/debug/pprof/goroutine
Po provedení příkazu by měl být vytvořen binární soubor goroutines.pprof, jenž si můžeme prohlédnout takto:
$ go tool pprof -text goroutines.pprof
S výsledky:
File: 02_image_server_with_pprof Type: goroutine Time: Jun 16, 2019 at 5:39pm (CEST) Showing nodes accounting for 3, 100% of 3 total flat flat% sum% cum cum% 1 33.33% 33.33% 1 33.33% runtime.gopark 1 33.33% 66.67% 1 33.33% runtime/pprof.writeRuntimeProfile 1 33.33% 100% 1 33.33% syscall.Syscall 0 0% 100% 1 33.33% internal/poll.(*FD).Accept 0 0% 100% 1 33.33% internal/poll.(*FD).Read ... ... ...
Informace o alokacích paměti:
$ wget -O allocs.pprof http://localhost:8080/debug/pprof/allocs
Analýza informací uložených do binárního souboru allocs.pprof:
$ go tool pprof -text allocs.pprof File: 02_image_server_with_pprof Type: alloc_space Time: Jun 16, 2019 at 5:45pm (CEST) Showing nodes accounting for 17945.43kB, 100% of 17945.43kB total flat flat% sum% cum cum% 7220.69kB 40.24% 40.24% 10338.57kB 57.61% compress/flate.NewWriter 3552.82kB 19.80% 60.03% 3552.82kB 19.80% runtime/pprof.writeGoroutineStacks 3525.88kB 19.65% 79.68% 3525.88kB 19.65% runtime/pprof.StartCPUProfile 1951.87kB 10.88% 90.56% 3117.88kB 17.37% compress/flate.(*compressor).init 1166.01kB 6.50% 97.06% 1166.01kB 6.50% compress/flate.newDeflateFast (inline) 528.17kB 2.94% 100% 528.17kB 2.94% bufio.NewWriterSize (inline) 0 0% 100% 1805.17kB 10.06% compress/flate.NewWriterDict 0 0% 100% 8533.40kB 47.55% compress/gzip.(*Writer).Write 0 0% 100% 1805.17kB 10.06% compress/zlib.(*Writer).Write 0 0% 100% 1805.17kB 10.06% compress/zlib.(*Writer).writeHeader 0 0% 100% 2333.34kB 13.00% image/png.(*Encoder).Encode 0 0% 100% 2333.34kB 13.00% image/png.(*encoder).writeIDATs 0 0% 100% 1805.17kB 10.06% image/png.(*encoder).writeImage 0 0% 100% 2333.34kB 13.00% image/png.Encode 0 0% 100% 2333.34kB 13.00% main.imageHandler ... ... ...
6. Složitější HTTP server s výpočty probíhajícími v gorutinách
Nic nám pochopitelně nebrání vytvořit složitější HTTP server, který bude namísto obrázku s náhodnými barvami pixelů počítat Mandelbrotovu množinu, a to nám již známým způsobem – takovým rozdělením výpočtu, že každý obrazový řádek bude vypočten v samostatné gorutině. Pro jednoduchost je celý HTTP server implementován jediným souborem:
package main import ( "image" "image/png" "net/http" _ "net/http/pprof" ) 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>` writer.Write([]byte(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(w http.ResponseWriter, r *http.Request) { const ImageWidth = 256 const ImageHeight = 256 pixels := calculateFractal(ImageWidth, ImageHeight, 255) outputimage := writeImage(ImageWidth, ImageHeight, pixels) png.Encode(w, outputimage) } 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 7: Nový HTTP server v akci.
Pokud v programu uděláme chybu a gorutiny pro výpočet se nebudou ukončovat, zjistíme to relativně snadno:
$ curl http://localhost:8080/debug/pprof/goroutine?debug=1 goroutine profile: total 133 127 @ 0x42f4ab 0x42f553 0x4069dd 0x4067b5 0x71348e 0x45c8a1 # 0x71348d main.calcMandelbrot+0x3d /home/tester/go-root/article_30/t.go:38 2 @ 0x42f4ab 0x42f553 0x4069dd 0x4067b5 0x7135cb 0x45c8a1 # 0x7135ca main.calcMandelbrot+0x17a /home/tester/go-root/article_30/t.go:48
7. Trasování a balíček runtime/trace
Ve druhé části dnešního článku se seznámíme s některými vlastnostmi traceru, který je, jak již víme, součástí základní sady nástrojů dodávaných společně s programovacím jazykem Go. Podobně jako v případě profileru je i tracer možné spouštět společně s testy (go test …), ve skutečnosti ovšem můžeme trasování zapnout i explicitně a použít ho v aplikaci spouštěné přímo, tj. bez testů. Samotný tracer dokáže zaznamenat, a to až (teoreticky) s nanosekundovou přesností, vznik událostí typu vytvoření gorutiny, zavolání systémové funkce (syscall), práce správce paměti (garbage collector), změna velikosti haldy (heapu) atd., tj. informace, které jsou potenciálně důležité při sledování stavu a zdraví aplikace.
8. Trasování při spuštění testů
Vzhledem k tomu, že je tracer možné spustit společně s testy, použijeme stejný demonstrační příklad, s nímž jsme se seznámili minule. V tomto příkladu se intenzivně pracuje s řezem (slice) s využitím funkce append():
package slices2 import "fmt" func Slices() { var a [0]int s := a[:] for i := 1; i <= 1000000; i++ { s = append(s, i) } fmt.Printf("Length: %d\n", len(s)) }
Přímé spuštění funkce Slices zařídí hlavní balíček s funkcí main():
package main import "slices2/slices2" func main() { slices2.Slices() }
Spuštění stejné funkce, ovšem tentokrát z testů:
package slices2_test import ( "slices2/slices2" "testing" ) func TestSlices(t *testing.T) { slices2.Slices() }
Tracer se nyní spustí společně s testy takto:
$ go test -trace slices2.trace slices_test.go
9. Výsledky běhu traceru
Binární soubor, který je výsledkem běhu traceru, je možné zpracovat několika různými způsoby. Nejjednodušší je použít nástroj trace a nechat si zobrazit výsledek ve webovém prohlížeči:
$ go tool trace slices2.trace
Po zadání tohoto příkazu se spustí webový server (web je ostatně primárním GUI nástrojem celého ekosystému postaveného okolo jazyka Go) a v prohlížeči by se měla otevřít tato stránka:
Obrázek 8: Úvodní stránka s trasovacími informacemi.
Z této stránky jsou dostupné i další důležité informace:
Obrázek 9: Informace o gorutinách.
Obrázek 10: Plánovač.
10. Jednoduchý demonstrační příklad s přímou podporou trasování
Tracer lze ovšem spustit i přímo z aplikace. Použijeme přitom podobný postup, jaký jsme využili minule při tvorbě souborů s metrikami běžící aplikace: explicitně otevřeme soubor používaný tracerem a zahájíme sledování aplikace:
f, err := os.Create("trace1.trace") if err != nil { log.Fatalf("failed to create trace output file: %v", err) } defer func() { if err := f.Close(); err != nil { log.Fatalf("failed to close trace file: %v", err) } }() if err := trace.Start(f); err != nil { log.Fatalf("failed to start trace: %v", err) } defer trace.Stop()
Úplný příklad, který trasovací informace tímto způsobem získává, vypadá následovně:
package main import ( "log" "os" "runtime/trace" ) func perform_login() { log.Println("login") } func perform_logout() { log.Println("logout") } func transaction(typ string) { log.Printf("transaction '%s'\n", typ) } func main() { f, err := os.Create("trace1.trace") if err != nil { log.Fatalf("failed to create trace output file: %v", err) } defer func() { if err := f.Close(); err != nil { log.Fatalf("failed to close trace file: %v", err) } }() if err := trace.Start(f); err != nil { log.Fatalf("failed to start trace: %v", err) } defer trace.Stop() perform_login() transaction("A") transaction("B") perform_logout() }
11. Kontext (context) a úlohy (task)
Pro trasování složitějších aplikací se mnohdy setkáme s tím, že se vytváří takzvané úlohy (task), které nám umožňují část aplikace (většinou funkčně ucelenou úlohu) explicitně pojmenovat. Tato úloha se objeví na stránce User-defined tasks. Samotné vytvoření úlohy vyžaduje takzvaný kontext, přičemž výchozí kontext získáme takto:
ctx := context.Background()
Z kontextu pak již vytvoříme novou úlohu a ihned ji pojmenujeme:
ctx, task := trace.NewTask(ctx, "transactionTask")
Ukončení úlohy zajistí funkce:
task.End()
Podívejme se nyní na příklad, v němž se celá logická úloha (přihlášení, provedení transakcí, odhlášení) skutečně sdružuje do úlohy pojmenované transactionTask:
package main import ( "context" "log" "os" "runtime/trace" ) func perform_login() { log.Println("login") } func perform_logout() { log.Println("logout") } func transaction(typ string) { log.Printf("transaction '%s'\n", typ) } func main() { f, err := os.Create("trace2.trace") if err != nil { log.Fatalf("failed to create trace output file: %v", err) } defer func() { if err := f.Close(); err != nil { log.Fatalf("failed to close trace file: %v", err) } }() if err := trace.Start(f); err != nil { log.Fatalf("failed to start trace: %v", err) } defer trace.Stop() ctx := context.Background() ctx, task := trace.NewTask(ctx, "transactionTask") perform_login() transaction("A") transaction("B") perform_logout() task.End() }
12. Zobrazení úlohy na stránce s výsledky běhu traceru
Nově vytvořenou úlohu ihned uvidíme na stránce User-defined tasks:
Obrázek 11: Uživatelsky definovaná úloha.
Samozřejmě jsou k dispozici i podrobnosti:
Obrázek 12: Podrobnější informace o úloze.
13. Rozdělení celé úlohy na regiony
Jednotlivé úlohy je možné rozdělit na menší části, které se jmenují regiony. I tyto regiony budou viditelné na stránce zobrazující výsledek sledování provedeného tracerem. Příkladem může být region pro první část úlohy – samotné přihlášení:
region1 := trace.StartRegion(ctx, "login") perform_login() region1.End()
Podobným způsobem je možné vytvořit i další regiony, jak je to ostatně ukázáno v dalším demonstračním příkladu:
package main import ( "context" "log" "os" "runtime/trace" ) func perform_login() { log.Println("login") } func perform_logout() { log.Println("logout") } func transaction(typ string) { log.Printf("transaction '%s'\n", typ) } func main() { f, err := os.Create("trace3.trace") if err != nil { log.Fatalf("failed to create trace output file: %v", err) } defer func() { if err := f.Close(); err != nil { log.Fatalf("failed to close trace file: %v", err) } }() if err := trace.Start(f); err != nil { log.Fatalf("failed to start trace: %v", err) } defer trace.Stop() ctx := context.Background() ctx, task := trace.NewTask(ctx, "transactionTask") region1 := trace.StartRegion(ctx, "login") perform_login() region1.End() region2 := trace.StartRegion(ctx, "transactions") transaction("A") transaction("B") region2.End() region3 := trace.StartRegion(ctx, "logout") perform_logout() region3.End() task.End() }
14. Zobrazení regionů na stránce s výsledky běhu traceru
Na dalším obrázku je patrné, jakým způsobem se na stránce User-defined regions zobrazí všechny tři regiony vytvořené v úloze transactionTask:
Obrázek 13: Podrobnější informace o regionech. Povšimněte si, že časové údaje jsou uvedeny v mikrosekundách (i když samotná přesnost měření je nižší).
15. Hierarchie regionů a zápis logovacích zpráv do souboru s trasovacími informacemi
Nic nám ovšem nebrání, aby byly regiony uspořádány hierarchicky. To nám umožní ještě lépe sledovat chování aplikace, zejména v případě, že se jedná o složitější program. Je zde ovšem jeden problém – pokud budeme regiony vytvářet ve funkcích, je nutné těmto funkcím předávat i kontext, což je poměrně nepříjemné (je to velký zásah do aplikace). Navíc je v další variantě aplikace ukázáno, že do souboru s trasovacími informacemi je možné zapisovat i logovací informace funkcí trace.Log() a trace.Logf() (format):
package main import ( "context" "log" "os" "runtime/trace" ) func perform_login() { log.Println("login") } func perform_logout() { log.Println("logout") } func transaction(ctx context.Context, typ string) { region := trace.StartRegion(ctx, "transaction") trace.Logf(ctx, "transaction", "type %s", typ) log.Printf("transaction '%s'\n", typ) region.End() } func main() { f, err := os.Create("trace4.trace") if err != nil { log.Fatalf("failed to create trace output file: %v", err) } defer func() { if err := f.Close(); err != nil { log.Fatalf("failed to close trace file: %v", err) } }() if err := trace.Start(f); err != nil { log.Fatalf("failed to start trace: %v", err) } defer trace.Stop() ctx := context.Background() ctx, task := trace.NewTask(ctx, "transactionTask") region1 := trace.StartRegion(ctx, "login") perform_login() region1.End() region2 := trace.StartRegion(ctx, "transactions") transaction(ctx, "A") transaction(ctx, "B") region2.End() region3 := trace.StartRegion(ctx, "logout") perform_logout() region3.End() task.End() }
16. Zobrazení regionů a logovacích informací na stránce s výsledky běhu traceru
Podrobnější informace o regionech se nyní zobrazí i na webové stránce s výsledky práce traceru. Můžeme se o tom pochopitelně velmi snadno přesvědčit:
Obrázek 14: Podrobnější trasovací informace s časy, strávenými v jednotlivých regionech.
Obrázek 15: Další informace o chování programu v jednotlivých regionech. Povšimněte si, že do regionu transaction aplikace vstoupila dvakrát, což plně odpovídá zdrojovému kódu:
region2 := trace.StartRegion(ctx, "transactions") transaction(ctx, "A") transaction(ctx, "B") region2.End()
17. Úprava projektu pro vykreslení Mandelbrotovy množiny pro přímou podporu trasování
Podobně jako předchozí jednoduché příklady můžeme upravit i aplikaci pro vykreslení Mandelbrotovy množiny, s níž jsme se seznámili minule. V celkově již třetí variantě pouze explicitně otevřeme soubor s trasovacími informacemi a povolíme trasování:
package main import ( "fmt" "log" "mandelbrot3/renderer" "os" "runtime/trace" "strconv" ) func main() { f, err := os.Create("mandelbrot3.trace") if err != nil { log.Fatalf("failed to create trace output file: %v", err) } defer func() { if err := f.Close(); err != nil { log.Fatalf("failed to close trace file: %v", err) } }() if err := trace.Start(f); err != nil { log.Fatalf("failed to start trace: %v", err) } defer trace.Stop() if len(os.Args) < 4 { println("usage: ./mandelbrot width height maxiter") os.Exit(1) } width, err := strconv.Atoi(os.Args[1]) if err != nil { fmt.Printf("Improper width parameter: '%s'\n", os.Args[1]) os.Exit(1) } height, err := strconv.Atoi(os.Args[2]) if err != nil { fmt.Printf("Improper height parameter: '%s'\n", os.Args[2]) os.Exit(1) } maxiter, err := strconv.Atoi(os.Args[3]) if err != nil { fmt.Printf("Improper maxiter parameter: '%s'\n", os.Args[3]) os.Exit(1) } renderer.Start(width, height, maxiter) }
Čtvrtá varianta je komplikovanější, protože je v ní specifikováno několik regionů a tudíž se musí do samotného rendereru přenášet i kontext:
package main import ( "context" "fmt" "log" "mandelbrot4/renderer" "os" "runtime/trace" "strconv" ) func main() { f, err := os.Create("mandelbrot4.trace") if err != nil { log.Fatalf("failed to create trace output file: %v", err) } defer func() { if err := f.Close(); err != nil { log.Fatalf("failed to close trace file: %v", err) } }() if err := trace.Start(f); err != nil { log.Fatalf("failed to start trace: %v", err) } defer trace.Stop() if len(os.Args) < 4 { println("usage: ./mandelbrot width height maxiter") os.Exit(1) } width, err := strconv.Atoi(os.Args[1]) if err != nil { fmt.Printf("Improper width parameter: '%s'\n", os.Args[1]) os.Exit(1) } height, err := strconv.Atoi(os.Args[2]) if err != nil { fmt.Printf("Improper height parameter: '%s'\n", os.Args[2]) os.Exit(1) } maxiter, err := strconv.Atoi(os.Args[3]) if err != nil { fmt.Printf("Improper maxiter parameter: '%s'\n", os.Args[3]) os.Exit(1) } ctx := context.Background() ctx, task := trace.NewTask(ctx, "renderFractal") defer task.End() renderer.Start(ctx, width, height, maxiter) }
package renderer import ( "context" "image" "image/png" "log" "os" "runtime/trace" ) func writeImage(width uint, height uint, pixels []byte) { 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 } } outputFile, err := os.Create("mandelbrot.png") if err != nil { log.Fatal(err) } defer outputFile.Close() png.Encode(outputFile, img) } 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 Start(ctx context.Context, width int, height int, maxiter int) { trace.Logf(ctx, "settings", "width %i", width) trace.Logf(ctx, "settings", "height %i", height) trace.Logf(ctx, "settings", "maxiter %i", maxiter) done := make(chan bool, height) pixels := make([]byte, width*height*3) offset := 0 delta := width * 3 var cy float64 = -1.5 region := trace.StartRegion(ctx, "startGoroutines") for y := 0; y < height; y++ { trace.Logf(ctx, "y (scanline)", "%i", y) go calcMandelbrot(uint(width), uint(height), uint(maxiter), mandmap[:], pixels[offset:offset+delta], cy, done) offset += delta cy += 3.0 / float64(height) } region.End() region2 := trace.StartRegion(ctx, "waitForGoroutines") for i := 0; i < height; i++ { <-done } region2.End() region3 := trace.StartRegion(ctx, "writeImage") writeImage(uint(width), uint(height), pixels) region3.End() }
18. Úplné trasovací informace společně s logováním do souboru generovaného tracerem
A nakonec v páté variantě použijeme úplné trasování. V tomto případě jsou nejzajímavější informace o časech strávených v jednotlivých regionech, protože máme definován jak region volaný pro každou obrazovou řádku (zde konkrétně volaný 256×), tak i region tvořený funkcí pro získání počtu iterací pro každý pixel (zde konkrétně volaný 256×256=65536×):
Obrázek 16: Povšimněte si, kolikrát je každý region volaný. Můžeme zde zjistit i čas výpočtu pro každý obrazový řádek či dokonce pro každý pixel v rastrovém obrázku.
Příklad byl upraven následujícím způsobem:
package renderer import ( "context" "image" "image/png" "log" "os" "runtime/trace" ) func writeImage(width uint, height uint, pixels []byte) { 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 } } outputFile, err := os.Create("mandelbrot.png") if err != nil { log.Fatal(err) } defer outputFile.Close() png.Encode(outputFile, img) } 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(ctx context.Context, 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++ { region := trace.StartRegion(ctx, "iterCount") i := iterCount(cx, cy, maxiter) region.End() 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 Start(ctx context.Context, width int, height int, maxiter int) { trace.Logf(ctx, "settings", "width %i", width) trace.Logf(ctx, "settings", "height %i", height) trace.Logf(ctx, "settings", "maxiter %i", maxiter) done := make(chan bool, height) pixels := make([]byte, width*height*3) offset := 0 delta := width * 3 var cy float64 = -1.5 region := trace.StartRegion(ctx, "startGoroutines") for y := 0; y < height; y++ { trace.Logf(ctx, "y (scanline)", "%i", y) go calcMandelbrot(ctx, uint(width), uint(height), uint(maxiter), mandmap[:], pixels[offset:offset+delta], cy, done) offset += delta cy += 3.0 / float64(height) } region.End() region2 := trace.StartRegion(ctx, "waitForGoroutines") for i := 0; i < height; i++ { <-done } region2.End() region3 := trace.StartRegion(ctx, "writeImage") writeImage(uint(width), uint(height), pixels) region3.End() }
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 projektu | Cesta |
---|---|---|---|
1 | 01_image_server.go | jednoduchý HTTP server generující HTML stránku i rastrový obrázek | https://github.com/tisnik/go-root/blob/master/article30/01_image_server.go |
2 | 02_image_server_with_pprof.go | jednoduchý HTTP server, který kromě vlastní služby poskytuje i informace pro profiling | https://github.com/tisnik/go-root/blob/master/article30/02_image_server_with_pprof.go |
3 | 03_image_server_fractal.go | HTTP server generující stránku obsahující obrázek s Mandelbrotovou množinou | https://github.com/tisnik/go-root/blob/master/article30/03_image_server_fractal.go |
4 | 04_basic_tracing.go | ukázka základních možností trasování | https://github.com/tisnik/go-root/blob/master/article30/04_basic_tracing.go |
5 | 05_context.go | kontext a jeho význam při trasování | https://github.com/tisnik/go-root/blob/master/article30/05_context.go |
6 | 06_context_regions.go | regiony použité pro logické rozdělení runtime aplikace | https://github.com/tisnik/go-root/blob/master/article30/06_context_regions.go |
7 | 07_region_hierarchy.go | hierarchie regionů, vložení zprávy do souboru s trasovacími informacemi atd. | https://github.com/tisnik/go-root/blob/master/article30/07_region_hierarchy.go |
Dnes jsme si taktéž ukázali několik projektů (s adresářovou strukturou atd.). Tyto projekty jsou vypsány ve druhé tabulce:
# | Projekt | Popis projektu | Cesta |
---|---|---|---|
1 | mandelbrot3 | základní profilovací informace v nástroji pro vykreslení Mandelbrotovy množiny | https://github.com/tisnik/go-root/blob/master/article30/mandelbrot3 |
2 | mandelbrot4 | podrobnější profilovací informace v nástroji pro vykreslení Mandelbrotovy množiny | https://github.com/tisnik/go-root/blob/master/article30/mandelbrot4 |
3 | mandelbrot5 | nejpodrobnější profilovací informace v nástroji pro vykreslení Mandelbrotovy množiny | https://github.com/tisnik/go-root/blob/master/article30/mandelbrot5 |
4 | slices2 | profilovací informace v aplikaci pro práci s řezy (slices) | https://github.com/tisnik/go-root/blob/master/article30/slices2 |
20. Odkazy na Internetu
- 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