package traceviewer
import (
"bufio"
"fmt"
"internal/profile"
"internal/trace"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
)
type ProfileFunc func (r *http .Request ) ([]ProfileRecord , error )
func SVGProfileHandlerFunc (f ProfileFunc ) http .HandlerFunc {
return func (w http .ResponseWriter , r *http .Request ) {
if r .FormValue ("raw" ) != "" {
w .Header ().Set ("Content-Type" , "application/octet-stream" )
failf := func (s string , args ...any ) {
w .Header ().Set ("Content-Type" , "text/plain; charset=utf-8" )
w .Header ().Set ("X-Go-Pprof" , "1" )
http .Error (w , fmt .Sprintf (s , args ...), http .StatusInternalServerError )
}
records , err := f (r )
if err != nil {
failf ("failed to get records: %v" , err )
return
}
if err := BuildProfile (records ).Write (w ); err != nil {
failf ("failed to write profile: %v" , err )
return
}
return
}
blockf , err := os .CreateTemp ("" , "block" )
if err != nil {
http .Error (w , fmt .Sprintf ("failed to create temp file: %v" , err ), http .StatusInternalServerError )
return
}
defer func () {
blockf .Close ()
os .Remove (blockf .Name ())
}()
records , err := f (r )
if err != nil {
http .Error (w , fmt .Sprintf ("failed to generate profile: %v" , err ), http .StatusInternalServerError )
}
blockb := bufio .NewWriter (blockf )
if err := BuildProfile (records ).Write (blockb ); err != nil {
http .Error (w , fmt .Sprintf ("failed to write profile: %v" , err ), http .StatusInternalServerError )
return
}
if err := blockb .Flush (); err != nil {
http .Error (w , fmt .Sprintf ("failed to flush temp file: %v" , err ), http .StatusInternalServerError )
return
}
if err := blockf .Close (); err != nil {
http .Error (w , fmt .Sprintf ("failed to close temp file: %v" , err ), http .StatusInternalServerError )
return
}
svgFilename := blockf .Name () + ".svg"
if output , err := exec .Command (goCmd (), "tool" , "pprof" , "-svg" , "-output" , svgFilename , blockf .Name ()).CombinedOutput (); err != nil {
http .Error (w , fmt .Sprintf ("failed to execute go tool pprof: %v\n%s" , err , output ), http .StatusInternalServerError )
return
}
defer os .Remove (svgFilename )
w .Header ().Set ("Content-Type" , "image/svg+xml" )
http .ServeFile (w , r , svgFilename )
}
}
type ProfileRecord struct {
Stack []*trace .Frame
Count uint64
Time time .Duration
}
func BuildProfile (prof []ProfileRecord ) *profile .Profile {
p := &profile .Profile {
PeriodType : &profile .ValueType {Type : "trace" , Unit : "count" },
Period : 1 ,
SampleType : []*profile .ValueType {
{Type : "contentions" , Unit : "count" },
{Type : "delay" , Unit : "nanoseconds" },
},
}
locs := make (map [uint64 ]*profile .Location )
funcs := make (map [string ]*profile .Function )
for _ , rec := range prof {
var sloc []*profile .Location
for _ , frame := range rec .Stack {
loc := locs [frame .PC ]
if loc == nil {
fn := funcs [frame .File +frame .Fn ]
if fn == nil {
fn = &profile .Function {
ID : uint64 (len (p .Function ) + 1 ),
Name : frame .Fn ,
SystemName : frame .Fn ,
Filename : frame .File ,
}
p .Function = append (p .Function , fn )
funcs [frame .File +frame .Fn ] = fn
}
loc = &profile .Location {
ID : uint64 (len (p .Location ) + 1 ),
Address : frame .PC ,
Line : []profile .Line {
{
Function : fn ,
Line : int64 (frame .Line ),
},
},
}
p .Location = append (p .Location , loc )
locs [frame .PC ] = loc
}
sloc = append (sloc , loc )
}
p .Sample = append (p .Sample , &profile .Sample {
Value : []int64 {int64 (rec .Count ), int64 (rec .Time )},
Location : sloc ,
})
}
return p
}
func goCmd() string {
var exeSuffix string
if runtime .GOOS == "windows" {
exeSuffix = ".exe"
}
path := filepath .Join (runtime .GOROOT (), "bin" , "go" +exeSuffix )
if _ , err := os .Stat (path ); err == nil {
return path
}
return "go"
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .