├── VERSION ├── .gitignore ├── go.mod ├── Dockerfile ├── go.sum ├── fib └── fib.go ├── global └── main.go ├── manifests └── deployment.yaml ├── README.md ├── Makefile ├── LICENSE └── main.go /VERSION: -------------------------------------------------------------------------------- 1 | v0.5.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pprof-example-app-go* 2 | *.pprof 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/polarsignals/pprof-example-app-go 2 | 3 | go 1.15 4 | 5 | require github.com/pkg/profile v1.6.0 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/distroless/base 2 | 3 | ADD pprof-example-app-go /bin/pprof-example-app-go 4 | 5 | ENTRYPOINT ["/bin/pprof-example-app-go"] 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM= 2 | github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= 3 | -------------------------------------------------------------------------------- /fib/fib.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | import "math/big" 4 | 5 | func Fibonacci(n uint) *big.Int { 6 | if n <= 1 { 7 | return big.NewInt(int64(n)) 8 | } 9 | 10 | var n2, n1 = big.NewInt(0), big.NewInt(1) 11 | 12 | for i := uint(1); i < n; i++ { 13 | n2.Add(n2, n1) 14 | n1, n2 = n2, n1 15 | } 16 | 17 | return n1 18 | } 19 | -------------------------------------------------------------------------------- /global/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | _ "net/http/pprof" 7 | "time" 8 | 9 | "github.com/polarsignals/pprof-example-app-go/fib" 10 | ) 11 | 12 | func main() { 13 | // Calculates Fibonacci numbers starting with 1 000 000th. 14 | // Produces some CPU activity. 15 | go calculateFib() 16 | 17 | // Allocate 1mb of memory every second, and don't free it. 18 | // Don't do this at home. 19 | go allocMem() 20 | 21 | log.Println(http.ListenAndServe(":8080", nil)) 22 | } 23 | 24 | func calculateFib() { 25 | i := uint(1000000) 26 | for { 27 | log.Println("fibonacci number", i, fib.Fibonacci(i)) 28 | i++ 29 | } 30 | } 31 | 32 | func allocMem() { 33 | buf := []byte{} 34 | mb := 1024 * 1024 35 | 36 | for { 37 | buf = append(buf, make([]byte, mb)...) 38 | time.Sleep(time.Second) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /manifests/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: pprof-example-app-go 6 | name: pprof-example-app-go 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app.kubernetes.io/name: pprof-example-app-go 12 | template: 13 | metadata: 14 | labels: 15 | app.kubernetes.io/name: pprof-example-app-go 16 | spec: 17 | containers: 18 | - name: pprof-example-app-go 19 | image: quay.io/polarsignals/pprof-example-app-go:v0.5.0 20 | ports: 21 | - name: http 22 | containerPort: 8080 23 | resources: 24 | limits: 25 | cpu: 200m 26 | memory: 256Mi 27 | --- 28 | apiVersion: v1 29 | kind: Service 30 | metadata: 31 | name: pprof-example-app-go 32 | labels: 33 | app.kubernetes.io/name: pprof-example-app-go 34 | spec: 35 | selector: 36 | app.kubernetes.io/name: pprof-example-app-go 37 | ports: 38 | - protocol: TCP 39 | port: 8080 40 | targetPort: 8080 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pprof Example App Go 2 | 3 | This example app serves as an example of how one can easily instrument HTTP handlers with [Pprof][pprof] profiles in [Go](https://golang.org/). It uses the [Go standard library][gostdlib]. 4 | 5 | It calculates fibonacci numbers starting at number 1 million to produce some CPU activity and allocates memory 1mb per second, it never frees this memory so be careful it may crash your system. 6 | 7 | > Run this example at your own risk Polar Signals is not responsible for damage this example may cause. 8 | 9 | A Docker image is available at: `quay.io/polarsignals/pprof-example-app-go:v0.1.0` 10 | 11 | ## Deploying in a Kubernetes cluster 12 | 13 | First, deploy an instance of this example application, which listens and exposes metrics on port 8080 using the following [Deployment manifest](manifests/deployment.yaml). 14 | 15 | Then deploy the collector of the Polar Signals Continuous Profiler to continuously collect profiles from the example app. 16 | 17 | [pprof]:https://github.com/google/pprof 18 | [gostdlib]:https://golang.org/pkg/net/http/pprof/ 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION:=$(shell cat VERSION | tr -d '\n') 2 | CONTAINER_IMAGE:=quay.io/polarsignals/pprof-example-app-go:$(VERSION) 3 | 4 | LDFLAGS="-X main.version=$(VERSION)" 5 | 6 | all: pprof-example-app-go pprof-example-app-go-stripped pprof-example-app-go-withput-dwarf pprof-example-app-go-inlining-disabled 7 | pprof-example-app-go: go.mod main.go fib/fib.go 8 | CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -ldflags=$(LDFLAGS) -o $@ main.go 9 | 10 | .PHONY: container 11 | container: pprof-example-app-go 12 | docker build --platform linux/amd64 -t $(CONTAINER_IMAGE) . 13 | 14 | pprof-example-app-go-stripped: go.mod main.go fib/fib.go 15 | CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -ldflags=$(LDFLAGS) -ldflags="-s -w" -trimpath -o $@ main.go 16 | 17 | pprof-example-app-go-without-dwarf: go.mod main.go fib/fib.go 18 | CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -ldflags=$(LDFLAGS) -ldflags="-w" -o $@ main.go 19 | 20 | pprof-example-app-go-inlining-disabled: go.mod main.go fib/fib.go 21 | CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -ldflags=$(LDFLAGS) -gcflags="all=-N -l" -o $@ main.go 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Polar Signals 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | "net/http/pprof" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/pkg/profile" 14 | 15 | "github.com/polarsignals/pprof-example-app-go/fib" 16 | ) 17 | 18 | var ( 19 | version string 20 | ) 21 | 22 | const ( 23 | modeBusyCPU = "busyCPU" 24 | modeAllocMem = "allocMem" 25 | ) 26 | 27 | func main() { 28 | var ( 29 | bind = "" 30 | mode = "both" // busyCpu, allocMem 31 | output = false 32 | ) 33 | flagset := flag.NewFlagSet(os.Args[0], flag.ExitOnError) 34 | flagset.StringVar(&bind, "bind", ":8080", "The socket to bind to.") 35 | flagset.StringVar(&mode, "mode", "both", "The mode to run. Options: busyCPU, allocMem, both.") 36 | flagset.BoolVar(&output, "output", false, "Whether to output a profile to current directory. Only works for individual modes.") 37 | err := flagset.Parse(os.Args[1:]) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | path, err := os.Getwd() 43 | if err != nil { 44 | log.Println(err) 45 | } 46 | 47 | log.Println("Starting HTTP server on", bind) 48 | mux := http.NewServeMux() 49 | mux.HandleFunc("/debug/pprof/", pprof.Index) 50 | mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 51 | mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 52 | mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 53 | mux.HandleFunc("/debug/pprof/trace", pprof.Trace) 54 | go func() { log.Fatal(http.ListenAndServe(bind, mux)) }() 55 | 56 | switch mode { 57 | case modeBusyCPU: 58 | if output { 59 | prof := profile.Start(profile.CPUProfile, profile.ProfilePath(path), profile.NoShutdownHook) 60 | defer prof.Stop() 61 | } 62 | go busyCPU() 63 | case modeAllocMem: 64 | if output { 65 | prof := profile.Start(profile.MemProfile, profile.ProfilePath(path), profile.NoShutdownHook) 66 | defer prof.Stop() 67 | } 68 | go allocMem() 69 | default: 70 | go busyCPU() 71 | go allocMem() 72 | } 73 | 74 | c := make(chan os.Signal, 1) 75 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 76 | defer signal.Stop(c) 77 | <-c 78 | 79 | if mode != "both" && output { 80 | log.Println("profile: caught interrupt, stopping profiles") 81 | } 82 | } 83 | 84 | // Calculates Fibonacci numbers starting with 1 000 000th. 85 | // Produces some CPU activity. 86 | func busyCPU() { 87 | i := uint(1000000) 88 | for { 89 | log.Println("fibonacci number", i, fib.Fibonacci(i)) 90 | i++ 91 | } 92 | } 93 | 94 | // Allocate 1mb of memory every second, and don't free it. 95 | // Don't do this at home. 96 | func allocMem() { 97 | buf := []byte{} 98 | mb := 1024 * 1024 99 | 100 | for { 101 | buf = append(buf, make([]byte, mb)...) 102 | log.Println("total allocated memory", len(buf)) 103 | time.Sleep(time.Second) 104 | } 105 | } 106 | --------------------------------------------------------------------------------