|
|
|
@ -32,7 +32,11 @@ import ( |
|
|
|
|
"os" |
|
|
|
|
"path/filepath" |
|
|
|
|
"reflect" |
|
|
|
|
"runtime" |
|
|
|
|
"runtime/pprof" |
|
|
|
|
"runtime/trace" |
|
|
|
|
"strings" |
|
|
|
|
"sync" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"github.com/beevik/ntp" |
|
|
|
@ -42,7 +46,6 @@ import ( |
|
|
|
|
|
|
|
|
|
humanize "github.com/dustin/go-humanize" |
|
|
|
|
"github.com/gorilla/mux" |
|
|
|
|
"github.com/pkg/profile" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// IsErrIgnored returns whether given error is ignored or not.
|
|
|
|
@ -187,94 +190,130 @@ func contains(slice interface{}, elem interface{}) bool { |
|
|
|
|
// provide any API to calculate the profiler file path in the
|
|
|
|
|
// disk since the name of this latter is randomly generated.
|
|
|
|
|
type profilerWrapper struct { |
|
|
|
|
stopFn func() |
|
|
|
|
pathFn func() string |
|
|
|
|
stopFn func() ([]byte, error) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (p profilerWrapper) Stop() { |
|
|
|
|
p.stopFn() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (p profilerWrapper) Path() string { |
|
|
|
|
return p.pathFn() |
|
|
|
|
func (p profilerWrapper) Stop() ([]byte, error) { |
|
|
|
|
return p.stopFn() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Returns current profile data, returns error if there is no active
|
|
|
|
|
// profiling in progress. Stops an active profile.
|
|
|
|
|
func getProfileData() ([]byte, error) { |
|
|
|
|
if globalProfiler == nil { |
|
|
|
|
func getProfileData() (map[string][]byte, error) { |
|
|
|
|
globalProfilerMu.Lock() |
|
|
|
|
defer globalProfilerMu.Unlock() |
|
|
|
|
|
|
|
|
|
if len(globalProfiler) == 0 { |
|
|
|
|
return nil, errors.New("profiler not enabled") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
profilerPath := globalProfiler.Path() |
|
|
|
|
|
|
|
|
|
// Stop the profiler
|
|
|
|
|
globalProfiler.Stop() |
|
|
|
|
|
|
|
|
|
profilerFile, err := os.Open(profilerPath) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
dst := make(map[string][]byte, len(globalProfiler)) |
|
|
|
|
for typ, prof := range globalProfiler { |
|
|
|
|
// Stop the profiler
|
|
|
|
|
var err error |
|
|
|
|
buf, err := prof.Stop() |
|
|
|
|
delete(globalProfiler, typ) |
|
|
|
|
if err == nil { |
|
|
|
|
dst[typ] = buf |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ioutil.ReadAll(profilerFile) |
|
|
|
|
return dst, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Starts a profiler returns nil if profiler is not enabled, caller needs to handle this.
|
|
|
|
|
func startProfiler(profilerType, dirPath string) (minioProfiler, error) { |
|
|
|
|
var err error |
|
|
|
|
if dirPath == "" { |
|
|
|
|
dirPath, err = ioutil.TempDir("", "profile") |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var profiler interface { |
|
|
|
|
Stop() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var profilerFileName string |
|
|
|
|
func startProfiler(profilerType string) (minioProfiler, error) { |
|
|
|
|
var prof profilerWrapper |
|
|
|
|
|
|
|
|
|
// Enable profiler and set the name of the file that pkg/pprof
|
|
|
|
|
// library creates to store profiling data.
|
|
|
|
|
switch profilerType { |
|
|
|
|
case "cpu": |
|
|
|
|
profiler = profile.Start(profile.CPUProfile, profile.NoShutdownHook, profile.ProfilePath(dirPath)) |
|
|
|
|
profilerFileName = "cpu.pprof" |
|
|
|
|
dirPath, err := ioutil.TempDir("", "profile") |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
fn := filepath.Join(dirPath, "cpu.out") |
|
|
|
|
f, err := os.Create(fn) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
err = pprof.StartCPUProfile(f) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
prof.stopFn = func() ([]byte, error) { |
|
|
|
|
pprof.StopCPUProfile() |
|
|
|
|
err := f.Close() |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
defer os.RemoveAll(dirPath) |
|
|
|
|
return ioutil.ReadFile(fn) |
|
|
|
|
} |
|
|
|
|
case "mem": |
|
|
|
|
profiler = profile.Start(profile.MemProfile, profile.NoShutdownHook, profile.ProfilePath(dirPath)) |
|
|
|
|
profilerFileName = "mem.pprof" |
|
|
|
|
old := runtime.MemProfileRate |
|
|
|
|
runtime.MemProfileRate = 1 |
|
|
|
|
prof.stopFn = func() ([]byte, error) { |
|
|
|
|
var buf bytes.Buffer |
|
|
|
|
runtime.MemProfileRate = old |
|
|
|
|
err := pprof.Lookup("heap").WriteTo(&buf, 0) |
|
|
|
|
return buf.Bytes(), err |
|
|
|
|
} |
|
|
|
|
case "block": |
|
|
|
|
profiler = profile.Start(profile.BlockProfile, profile.NoShutdownHook, profile.ProfilePath(dirPath)) |
|
|
|
|
profilerFileName = "block.pprof" |
|
|
|
|
runtime.SetBlockProfileRate(1) |
|
|
|
|
prof.stopFn = func() ([]byte, error) { |
|
|
|
|
var buf bytes.Buffer |
|
|
|
|
runtime.SetBlockProfileRate(0) |
|
|
|
|
err := pprof.Lookup("block").WriteTo(&buf, 0) |
|
|
|
|
return buf.Bytes(), err |
|
|
|
|
} |
|
|
|
|
case "mutex": |
|
|
|
|
profiler = profile.Start(profile.MutexProfile, profile.NoShutdownHook, profile.ProfilePath(dirPath)) |
|
|
|
|
profilerFileName = "mutex.pprof" |
|
|
|
|
runtime.SetMutexProfileFraction(1) |
|
|
|
|
prof.stopFn = func() ([]byte, error) { |
|
|
|
|
var buf bytes.Buffer |
|
|
|
|
runtime.SetMutexProfileFraction(0) |
|
|
|
|
err := pprof.Lookup("mutex").WriteTo(&buf, 0) |
|
|
|
|
return buf.Bytes(), err |
|
|
|
|
} |
|
|
|
|
case "trace": |
|
|
|
|
profiler = profile.Start(profile.TraceProfile, profile.NoShutdownHook, profile.ProfilePath(dirPath)) |
|
|
|
|
profilerFileName = "trace.out" |
|
|
|
|
dirPath, err := ioutil.TempDir("", "profile") |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
fn := filepath.Join(dirPath, "trace.out") |
|
|
|
|
f, err := os.Create(fn) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
err = trace.Start(f) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
prof.stopFn = func() ([]byte, error) { |
|
|
|
|
trace.Stop() |
|
|
|
|
err := f.Close() |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
defer os.RemoveAll(dirPath) |
|
|
|
|
return ioutil.ReadFile(fn) |
|
|
|
|
} |
|
|
|
|
default: |
|
|
|
|
return nil, errors.New("profiler type unknown") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return &profilerWrapper{ |
|
|
|
|
stopFn: profiler.Stop, |
|
|
|
|
pathFn: func() string { |
|
|
|
|
return filepath.Join(dirPath, profilerFileName) |
|
|
|
|
}, |
|
|
|
|
}, nil |
|
|
|
|
return prof, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// minioProfiler - minio profiler interface.
|
|
|
|
|
type minioProfiler interface { |
|
|
|
|
// Stop the profiler
|
|
|
|
|
Stop() |
|
|
|
|
// Return the path of the profiling file
|
|
|
|
|
Path() string |
|
|
|
|
Stop() ([]byte, error) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Global profiler to be used by service go-routine.
|
|
|
|
|
var globalProfiler minioProfiler |
|
|
|
|
var globalProfiler map[string]minioProfiler |
|
|
|
|
var globalProfilerMu sync.Mutex |
|
|
|
|
|
|
|
|
|
// dump the request into a string in JSON format.
|
|
|
|
|
func dumpRequest(r *http.Request) string { |
|
|
|
|