├── samples ├── test.json ├── aloc.go ├── timezone_test.go ├── aloc_test.go ├── timezone.go ├── json_test.go └── json.go ├── result.pprof ├── profile001.png ├── web-interface-use.gif ├── interactive-terminal-use.gif ├── web-interactive-interface.gif ├── go.mod ├── .vscode └── launch.json ├── go.sum ├── main.go └── README.md /samples/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiling": 1 3 | } -------------------------------------------------------------------------------- /result.pprof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelhl/profiling-samples/HEAD/result.pprof -------------------------------------------------------------------------------- /profile001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelhl/profiling-samples/HEAD/profile001.png -------------------------------------------------------------------------------- /web-interface-use.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelhl/profiling-samples/HEAD/web-interface-use.gif -------------------------------------------------------------------------------- /interactive-terminal-use.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelhl/profiling-samples/HEAD/interactive-terminal-use.gif -------------------------------------------------------------------------------- /web-interactive-interface.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelhl/profiling-samples/HEAD/web-interactive-interface.gif -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rafaelhl/profiling-samples 2 | 3 | go 1.18 4 | 5 | require github.com/stretchr/testify v1.2.2 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /samples/aloc.go: -------------------------------------------------------------------------------- 1 | package samples 2 | 3 | import "fmt" 4 | 5 | func Aloc[O any, T []O](obj O, times int) (result T) { 6 | 7 | for i := 0; i <= times; i++ { 8 | result = append(result, obj) 9 | } 10 | 11 | for _, o := range result { 12 | fmt.Printf("%+v\n", o) 13 | } 14 | 15 | return 16 | } 17 | -------------------------------------------------------------------------------- /samples/timezone_test.go: -------------------------------------------------------------------------------- 1 | package samples_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rafaelhl/profiling-samples/samples" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestTimezone(t *testing.T) { 11 | tz := samples.Timezone(10) 12 | 13 | assert.NotNil(t, tz) 14 | } 15 | -------------------------------------------------------------------------------- /samples/aloc_test.go: -------------------------------------------------------------------------------- 1 | package samples_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rafaelhl/profiling-samples/samples" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAloc(t *testing.T) { 11 | result := samples.Aloc("test", 10) 12 | 13 | assert.Len(t, result, 11) 14 | } 15 | -------------------------------------------------------------------------------- /samples/timezone.go: -------------------------------------------------------------------------------- 1 | package samples 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func Timezone(times int) (tz *time.Location) { 8 | for i := 0; i <= times; i++ { 9 | var err error 10 | tz, err = time.LoadLocation("America/Sao_Paulo") 11 | if err != nil { 12 | panic(err) 13 | } 14 | } 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /samples/json_test.go: -------------------------------------------------------------------------------- 1 | package samples_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/rafaelhl/profiling-samples/samples" 9 | ) 10 | 11 | func TestUnmarshall(t *testing.T) { 12 | samples.Path = "" 13 | result := samples.Unmarshall[map[string]int](10) 14 | 15 | assert.Equal(t, map[string]int{ 16 | "profiling": 1, 17 | }, result) 18 | } 19 | -------------------------------------------------------------------------------- /samples/json.go: -------------------------------------------------------------------------------- 1 | package samples 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | var Path = "samples/" 11 | 12 | func Unmarshall[T any](times int) (result T) { 13 | file, err := os.ReadFile(fmt.Sprintf("%stest.json", Path)) 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | for i := 0; i <= times; i++ { 19 | err = json.NewDecoder(bytes.NewReader(file)).Decode(&result) 20 | if err != nil { 21 | panic(err) 22 | } 23 | } 24 | 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Package", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${fileDirname}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 6 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 7 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | _ "net/http/pprof" 8 | "strconv" 9 | 10 | "github.com/rafaelhl/profiling-samples/samples" 11 | ) 12 | 13 | func main() { 14 | http.HandleFunc("/json/unmarshall", handleJson) 15 | http.HandleFunc("/timezone", handleTimezone) 16 | http.HandleFunc("/aloc", handleAloc) 17 | 18 | log.Fatal(http.ListenAndServe(":7777", nil)) 19 | } 20 | 21 | func timesParam(r *http.Request, w http.ResponseWriter) (int, bool) { 22 | timesParam := r.URL.Query().Get("times") 23 | times, err := strconv.Atoi(timesParam) 24 | if err != nil { 25 | w.WriteHeader(http.StatusBadRequest) 26 | w.Write([]byte("unexpected times value")) 27 | return 0, false 28 | } 29 | return times, true 30 | } 31 | 32 | func handleJson(w http.ResponseWriter, r *http.Request) { 33 | times, ok := timesParam(r, w) 34 | if !ok { 35 | return 36 | } 37 | 38 | result := samples.Unmarshall[map[string]any](times) 39 | body, err := json.Marshal(result) 40 | if err != nil { 41 | w.WriteHeader(http.StatusInternalServerError) 42 | w.Write([]byte("unexpected error")) 43 | return 44 | } 45 | 46 | w.Write(body) 47 | } 48 | 49 | func handleTimezone(w http.ResponseWriter, r *http.Request) { 50 | times, ok := timesParam(r, w) 51 | if !ok { 52 | return 53 | } 54 | 55 | result := samples.Timezone(times) 56 | w.Write([]byte(result.String())) 57 | } 58 | 59 | func handleAloc(w http.ResponseWriter, r *http.Request) { 60 | times, ok := timesParam(r, w) 61 | if !ok { 62 | return 63 | } 64 | 65 | result := samples.Aloc("test", times) 66 | body, err := json.Marshal(result) 67 | if err != nil { 68 | w.WriteHeader(http.StatusBadRequest) 69 | w.Write([]byte("unexpected times value")) 70 | return 71 | } 72 | 73 | w.Write(body) 74 | } 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Profiling Samples 2 | 3 | Some samples implementations to illustrate how to use 4 | [Go Profiling](https://go.dev/doc/diagnostics#profiling) to improve a Go code. 5 | 6 | ## Samples 7 | 8 | This project has three implementations with issues performance common 9 | in a Go code. 10 | 11 | ### JSON Unmarshalling sample 12 | 13 | Simple implementation that will do a unmarshall multiple times as defined on param. 14 | 15 | ```http request 16 | GET /json/unmarshall?times={times} 17 | ``` 18 | 19 | ```go 20 | func Unmarshall[T any](times int) (result T) { 21 | file, err := os.ReadFile(fmt.Sprintf("%stest.json", Path)) 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | for i := 0; i <= times; i++ { 27 | err = json.NewDecoder(bytes.NewReader(file)).Decode(&result) 28 | if err != nil { 29 | panic(err) 30 | } 31 | } 32 | 33 | return 34 | } 35 | ``` 36 | [samples/json.go](samples/json.go) 37 | 38 | ### Load location with timezone sample 39 | 40 | Simple implementation that will load a location with tz multiple times as defined on param. 41 | 42 | ```http request 43 | GET /timezone?times={times} 44 | ``` 45 | 46 | 47 | ```go 48 | func Timezone(times int) (tz *time.Location) { 49 | for i := 0; i <= times; i++ { 50 | var err error 51 | tz, err = time.LoadLocation("America/Sao_Paulo") 52 | if err != nil { 53 | panic(err) 54 | } 55 | } 56 | 57 | return 58 | } 59 | ``` 60 | [samples/timezone.go](samples/timezone.go) 61 | 62 | ### Allocations inside a loop sample 63 | 64 | Simple implementation that will create an object and append on slice and iterate it 65 | multiple times as defined on param. 66 | 67 | ```http request 68 | GET /aloc?times={times} 69 | ``` 70 | 71 | ```go 72 | func Aloc[O any, T []O](obj O, times int) (result T) { 73 | 74 | for i := 0; i <= times; i++ { 75 | result = append(result, obj) 76 | } 77 | 78 | for _, o := range result { 79 | fmt.Printf("%+v\n", o) 80 | } 81 | 82 | return 83 | } 84 | ``` 85 | [samples/aloc.go](samples/aloc.go) 86 | 87 | ## Using pprof 88 | 89 | On this project we will use the [HTTP pkg](https://pkg.go.dev/net/http/pprof) to run the pprof. 90 | 91 | ### Starting the app 92 | 93 | First we will start the app. 94 | 95 | ```shell 96 | go run . 97 | ``` 98 | 99 | ### Running the pprof 100 | 101 | To generate the pprof data and render it on a web page 102 | we have to execute passing the expected endpoint available with follow args: 103 | 104 | ```shell 105 | go tool pprof -http=localhost: http://localhost:7777/debug/pprof/profile?seconds=60 106 | ``` 107 | 108 | ### Simulating HTTP requests 109 | 110 | Now for the pprof data has some metrics 111 | we have to simulate some parallel requests immediately after run the pprof. 112 | 113 | ```shell 114 | curl "http://localhost:7777/json/unmarshall?times=10000000" & 115 | curl "http://localhost:7777/timezone?times=100000" & 116 | curl "http://localhost:7777/aloc?times=1000000" & 117 | ``` 118 | 119 | ### Expected web page 120 | 121 | After 60 seconds we have to be redirected to a web page as below: 122 | 123 | ![Go tool pprof web interactive](web-interactive-interface.gif) 124 | 125 | [https://youtu.be/hmD9zBjuc74 "Go tool pprof web interactive"](https://youtu.be/hmD9zBjuc74) 126 | 127 | ## Pull requests with improvements of the samples 128 | 129 | - [Improve JSON route decode #1](https://github.com/rafaelhl/profiling-samples/pull/1) 130 | - [Improve timezone load #2](https://github.com/rafaelhl/profiling-samples/pull/2) 131 | - [Improve aloc processing route #3](https://github.com/rafaelhl/profiling-samples/pull/3) 132 | - [Validate an error avoiding returning any result with err #5](https://github.com/rafaelhl/profiling-samples/pull/5) 133 | 134 | 135 | --------------------------------------------------------------------------------