package main import ( "flag" "io" "log" "net/http" "os" "path" "strings" "github.com/google/pprof/driver" ) const portEnvVar = "PORT" const defaultPort = "8080" const maxUploadSize = 32 << 20 // 32 MiB const pprofFilePath = "/tmp/pprofweb-temp" const fileFormID = "file" const uploadPath = "/upload" const pprofWebPath = "/pprofweb/" type server struct { // serves pprof handlers after it is loaded pprofMux *http.ServeMux } func (s *server) startHTTP(args *driver.HTTPServerArgs) error { s.pprofMux = http.NewServeMux() for pattern, handler := range args.Handlers { var joinedPattern string if pattern == "/" { joinedPattern = pprofWebPath } else { joinedPattern = path.Join(pprofWebPath, pattern) } s.pprofMux.Handle(joinedPattern, handler) } return nil } func (s *server) servePprof(w http.ResponseWriter, r *http.Request) { if s.pprofMux == nil { http.Error(w, "must upload profile first", http.StatusInternalServerError) return } s.pprofMux.ServeHTTP(w, r) } func rootHandler(w http.ResponseWriter, r *http.Request) { log.Printf("rootHandler %s %s", r.Method, r.URL.String()) if r.Method != http.MethodGet { http.Error(w, "wrong method", http.StatusMethodNotAllowed) return } if r.URL.Path != "/" { http.Error(w, "not found", http.StatusNotFound) return } w.Write([]byte(rootTemplate)) } func (s *server) uploadHandlerErrHandler(w http.ResponseWriter, r *http.Request) { log.Printf("uploadHandler %s %s", r.Method, r.URL.String()) if r.Method != http.MethodPost { http.Error(w, "wrong method", http.StatusMethodNotAllowed) return } err := s.uploadHandler(w, r) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } func (s *server) uploadHandler(w http.ResponseWriter, r *http.Request) error { if err := r.ParseMultipartForm(maxUploadSize); err != nil { return err } uploadedFile, _, err := r.FormFile(fileFormID) if err != nil { return err } defer uploadedFile.Close() // write the file out to a temporary location f, err := os.OpenFile(pprofFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { return err } defer f.Close() if _, err := io.Copy(f, uploadedFile); err != nil { return err } if err := f.Close(); err != nil { return err } if err := uploadedFile.Close(); err != nil { return err } // start the pprof web handler: pass -http and -no_browser so it starts the // handler but does not try to launch a browser // our startHTTP will do the appropriate interception flags := &pprofFlags{ args: []string{"-http=localhost:0", "-no_browser", pprofFilePath}, } options := &driver.Options{ Flagset: flags, HTTPServer: s.startHTTP, } if err := driver.PProf(options); err != nil { return err } http.Redirect(w, r, pprofWebPath, http.StatusSeeOther) return nil } func main() { s := &server{} mux := http.NewServeMux() mux.HandleFunc("/", rootHandler) mux.HandleFunc(uploadPath, s.uploadHandlerErrHandler) mux.HandleFunc(pprofWebPath, s.servePprof) port := os.Getenv(portEnvVar) if port == "" { port = defaultPort log.Printf("warning: %s not specified; using default %s", portEnvVar, port) } addr := ":" + port log.Printf("listen addr %s (http://localhost:%s/)", addr, port) if err := http.ListenAndServe(addr, mux); err != nil { panic(err) } } const rootTemplate = ` PProf Web Interface

PProf Web Interface

Upload a file to explore it using the Pprof web interface.

NOTE: Will not work with multiple users

This is currently a hack: it runs in Google Cloud Run, which will restart instances whenever it wants. This means your state may get lost at any time, and it won't work if there are multiple people using it at the same time.

TODO: Write all the output from pprof out to static files in Google Cloud Storage and serve them from there.

Upload file:

` // Mostly copied from https://github.com/google/pprof/blob/master/internal/driver/flags.go type pprofFlags struct { args []string s flag.FlagSet usage []string } // Bool implements the plugin.FlagSet interface. func (p *pprofFlags) Bool(o string, d bool, c string) *bool { return p.s.Bool(o, d, c) } // Int implements the plugin.FlagSet interface. func (p *pprofFlags) Int(o string, d int, c string) *int { return p.s.Int(o, d, c) } // Float64 implements the plugin.FlagSet interface. func (p *pprofFlags) Float64(o string, d float64, c string) *float64 { return p.s.Float64(o, d, c) } // String implements the plugin.FlagSet interface. func (p *pprofFlags) String(o, d, c string) *string { return p.s.String(o, d, c) } // BoolVar implements the plugin.FlagSet interface. func (p *pprofFlags) BoolVar(b *bool, o string, d bool, c string) { p.s.BoolVar(b, o, d, c) } // IntVar implements the plugin.FlagSet interface. func (p *pprofFlags) IntVar(i *int, o string, d int, c string) { p.s.IntVar(i, o, d, c) } // Float64Var implements the plugin.FlagSet interface. // the value of the flag. func (p *pprofFlags) Float64Var(f *float64, o string, d float64, c string) { p.s.Float64Var(f, o, d, c) } // StringVar implements the plugin.FlagSet interface. func (p *pprofFlags) StringVar(s *string, o, d, c string) { p.s.StringVar(s, o, d, c) } // StringList implements the plugin.FlagSet interface. func (p *pprofFlags) StringList(o, d, c string) *[]*string { return &[]*string{p.s.String(o, d, c)} } // AddExtraUsage implements the plugin.FlagSet interface. func (p *pprofFlags) AddExtraUsage(eu string) { p.usage = append(p.usage, eu) } // ExtraUsage implements the plugin.FlagSet interface. func (p *pprofFlags) ExtraUsage() string { return strings.Join(p.usage, "\n") } // Parse implements the plugin.FlagSet interface. func (p *pprofFlags) Parse(usage func()) []string { p.s.Usage = usage p.s.Parse(p.args) args := p.s.Args() if len(args) == 0 { usage() } return args }