Last active
June 12, 2020 23:18
-
-
Save vardius/c7861a3ee19cd04164165cfb5f2f95cc to your computer and use it in GitHub Desktop.
Profiling Go HTTP service with pprof and expvar
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"expvar" // Register the expvar handlers | |
"fmt" | |
"log" | |
"net/http" | |
_ "net/http/pprof" // Register the pprof handlers | |
"os" | |
"runtime" | |
"time" | |
"github.com/vardius/shutdown" | |
) | |
// m contains the global program counters for the application. | |
var m = struct { | |
gr *expvar.Int | |
req *expvar.Int | |
}{ | |
gr: expvar.NewInt("goroutines"), | |
req: expvar.NewInt("requests"), | |
} | |
// Metrics updates program counters. | |
func Metrics(next http.Handler) http.Handler { | |
fn := func(w http.ResponseWriter, r *http.Request) { | |
next.ServeHTTP(w, r) | |
// Increment the request counter. | |
m.req.Add(1) | |
// Update the count for the number of active goroutines every 100 requests. | |
if m.req.Value()%100 == 0 { | |
m.gr.Set(int64(runtime.NumGoroutine())) | |
} | |
} | |
return http.HandlerFunc(fn) | |
} | |
type DebugServer struct { | |
*http.Server | |
} | |
// NewDebugServer provides new debug http server | |
func NewDebugServer(address string) *DebugServer { | |
return &DebugServer{ | |
&http.Server{ | |
Addr: address, | |
Handler: http.DefaultServeMux, | |
}, | |
} | |
} | |
var isDebug = os.Getenv("DEBUG") != "" | |
func main() { | |
mux := http.NewServeMux() | |
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { | |
fmt.Fprintf(w, "Welcome to the home page!") | |
}) | |
httpServer := &http.Server{ | |
Addr: ":8080", | |
Handler: Metrics(mux), // wrap our server with metrics middleware | |
} | |
go func() { | |
log.Printf("Server HTTP listen on %s:%d", "localhost", 8080) | |
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { | |
log.Fatal(fmt.Sprintf("ListenAndServe: %s", err)) | |
} | |
}() | |
var debugServer *DebugServer | |
if isDebug { | |
debugServer = NewDebugServer(fmt.Sprintf("%s:%d", "localhost", 6060)) | |
go func() { | |
log.Printf("Server HTTP listen on %s:%d", "localhost", 6060) | |
if err := debugServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { | |
log.Fatal(fmt.Sprintf("ListenAndServe: %s", err)) | |
} | |
}() | |
} | |
stop := func() { | |
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | |
defer cancel() | |
if err := httpServer.Shutdown(ctx); err != nil { | |
log.Fatalf("Server shutdown failed %s", err) | |
} | |
if debugServer != nil { | |
if err := debugServer.Shutdown(ctx); err != nil { | |
log.Fatalf("Debug server shutdown failed %s", err) | |
} | |
} | |
} | |
shutdown.GracefulStop(stop) // will block until shutdown signal is received | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment