Skip to content

Commit

Permalink
optimize utilroutes
Browse files Browse the repository at this point in the history
  • Loading branch information
bughou committed Apr 6, 2021
1 parent d8975fb commit 08d0f3e
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 85 deletions.
File renamed without changes.
57 changes: 0 additions & 57 deletions utilroutes/instance_name.go

This file was deleted.

102 changes: 102 additions & 0 deletions utilroutes/pprof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package utilroutes

import (
"fmt"
"net/http"
"runtime"
"runtime/pprof"
"runtime/trace"
"strconv"
"time"
)

// cpuProfile responds with the pprof-formatted cpu profile.
// Profiling lasts for duration specified in seconds GET parameter, or for 30 seconds if not specified.
func cpuProfile(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
sec, err := strconv.ParseInt(r.FormValue("seconds"), 10, 64)
if sec <= 0 || err != nil {
sec = 30
}

if durationExceedsWriteTimeout(r, float64(sec)) {
serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
return
}

// Set Content Type assuming StartCPUProfile will work,
// because if it does it starts writing.
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", `attachment; filename="cpu.bin"`)
if err := pprof.StartCPUProfile(w); err != nil {
// StartCPUProfile failed, so no writes yet.
serveError(w, http.StatusInternalServerError,
fmt.Sprintf("Could not enable CPU profiling: %s", err))
return
}
sleep(r, time.Duration(sec)*time.Second)
pprof.StopCPUProfile()
}

func getProfile(name string, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
profile := pprof.Lookup(name)
if profile == nil {
w.WriteHeader(404)
fmt.Fprintf(w, "Unknown profile: %s\n", name)
return
}
if name == "heap" && r.FormValue("gc") != `` {
runtime.GC()
}
debugLevel, _ := strconv.Atoi(r.FormValue("debug"))
profile.WriteTo(w, debugLevel)
}

// trace responds with the execution trace in binary form.
// Tracing lasts for duration specified in seconds GET parameter, or for 1 second if not specified.
func runTrace(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64)
if sec <= 0 || err != nil {
sec = 1
}

if durationExceedsWriteTimeout(r, sec) {
serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
return
}

// Set Content Type assuming trace.Start will work,
// because if it does it starts writing.
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", `attachment; filename="trace.bin"`)
if err := trace.Start(w); err != nil {
// trace.Start failed, so no writes yet.
serveError(w, http.StatusInternalServerError,
fmt.Sprintf("Could not enable tracing: %s", err))
return
}
sleep(r, time.Duration(sec*float64(time.Second)))
trace.Stop()
}

func sleep(r *http.Request, d time.Duration) {
select {
case <-time.After(d):
case <-r.Context().Done():
}
}

func durationExceedsWriteTimeout(r *http.Request, seconds float64) bool {
srv, ok := r.Context().Value(http.ServerContextKey).(*http.Server)
return ok && srv.WriteTimeout != 0 && seconds >= srv.WriteTimeout.Seconds()
}

func serveError(w http.ResponseWriter, status int, txt string) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Go-Pprof", "1")
w.Header().Del("Content-Disposition")
w.WriteHeader(status)
fmt.Fprintln(w, txt)
}
File renamed without changes.
92 changes: 64 additions & 28 deletions utilroutes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,89 @@ package utilroutes

import (
"fmt"
httpPprof "net/http/pprof"
"runtime"
"runtime/pprof"
"strconv"
"log"
"net"
"os"
"strings"

"github.com/lovego/goa"
)

var instanceName = getInstanceName()

func Setup(router *goa.Router) {
router.Get(`/_alive`, func(ctx *goa.Context) {
ctx.Write([]byte(`ok`))
})
router.Use(recordRequests) // ps middleware

debug := router.Group(`/_debug`)
debug.Get(`/`, func(ctx *goa.Context) {
group := router.Group(`/_debug`)
group.Use(func(ctx *goa.Context) {
ctx.ResponseWriter.Header().Set("Instance-Name", instanceName)
ctx.Next()
})
group.Get(`/`, func(ctx *goa.Context) {
ctx.Write(debugIndex())
})

debug.Get(`/reqs`, func(ctx *goa.Context) {
group.Get(`/reqs`, func(ctx *goa.Context) {
ctx.Write(requests.ToJson())
})

// pprof
debug.Get(`/cpu`, func(ctx *goa.Context) {
// ctx.Write([]byte(instanceName + "\n"))
httpPprof.Profile(ctx.ResponseWriter, ctx.Request)
group.Get(`/cpu`, func(ctx *goa.Context) {
cpuProfile(ctx.ResponseWriter, ctx.Request)
})
group.Get(`/(\w+)`, func(ctx *goa.Context) {
getProfile(ctx.Param(0), ctx.ResponseWriter, ctx.Request)
})

debug.Get(`/(\w+)`, func(ctx *goa.Context) {
name := ctx.Param(0)
ctx.ResponseWriter.Header().Set("Content-Type", "text/plain; charset=utf-8")
ctx.ResponseWriter.Header().Set("Instance-Name", instanceName)
profile := pprof.Lookup(name)
if profile == nil {
ctx.WriteHeader(404)
fmt.Fprintf(ctx, "Unknown profile: %s\n", name)
return
group.Get(`/trace`, func(ctx *goa.Context) {
runTrace(ctx.ResponseWriter, ctx.Request)
})
}

func getInstanceName() string {
hostname, err := os.Hostname()
if err != nil {
log.Panic(err)
}
return fmt.Sprintf(
"%s (%s) (Listen At %s)", hostname, strings.Join(ipv4Addrs(), ", "), ListenAddr(),
)
}

func ipv4Addrs() (result []string) {
ifcs, err := net.Interfaces()
if err != nil {
panic(err)
}
for _, ifc := range ifcs {
if ifc.Flags&net.FlagLoopback == 0 {
result = append(result, ipv4AddrsOfInterface(ifc)...)
}
if name == "heap" && ctx.FormValue("gc") != `` {
runtime.GC()
}
return result
}
func ipv4AddrsOfInterface(ifc net.Interface) (result []string) {
addrs, err := ifc.Addrs()
if err != nil {
panic(err)
}
for _, addr := range addrs {
if str := addr.String(); strings.IndexByte(str, '.') > 0 { // ipv4
if i := strings.IndexByte(str, '/'); i >= 0 {
str = str[:i]
}
result = append(result, str)
}
debugLevel, _ := strconv.Atoi(ctx.FormValue("debug"))
profile.WriteTo(ctx, debugLevel)
})
}
return result
}

debug.Get(`/trace`, func(ctx *goa.Context) {
httpPprof.Trace(ctx.ResponseWriter, ctx.Request)
})
func ListenAddr() string {
port := os.Getenv(`GOPORT`)
if port == `` {
port = `3000`
}
return `:` + port
}

0 comments on commit 08d0f3e

Please sign in to comment.