Hlavní navigace

Trasování a profilování aplikací naprogramovaných v Go (dokončení)

Pavel Tišnovský

V dalším článku o jazyku Go si nejprve ukážeme, jak získat aktuální metriky měřené profilerem u těch aplikací, které pracují v režimu serveru (síťové aplikace, démoni, webové servery). Dále se seznámíme s možnostmi profileru.

Doba čtení: 41 minut

Sdílet

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

20. Odkazy na Internetu

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.

Poznámka: samozřejmě je možné tento doplňující text přeskočit a přejít přímo k části věnované jazyku Go.

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.

Poznámka: osobně existenci a stabilitu těchto rozhraní považuji za jednu z těch technologií, která JVM (a podobně vyspělé VM) odlišuje od jednodušších řešení.

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:

  1. 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.
  2. 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).
  3. 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á.
  4. 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_i­mage_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
Poznámka: parametr debug je zde nutné uvést.

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_i­mage_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_i­mage_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_i­mage_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_ba­sic_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_con­text.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_con­text_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_re­gion_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/man­delbrot3
2 mandelbrot4 podrobnější profilovací informace v nástroji pro vykreslení Mandelbrotovy množiny https://github.com/tisnik/go-root/blob/master/article30/man­delbrot4
3 mandelbrot5 nejpodrobnější profilovací informace v nástroji pro vykreslení Mandelbrotovy množiny https://github.com/tisnik/go-root/blob/master/article30/man­delbrot5
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

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