CPU profiling (#1371)

* Add cpuprofile flag
* Add notes to readme
This commit is contained in:
WithoutPants 2021-05-16 17:21:11 +10:00 committed by GitHub
parent bc9aa02835
commit 16fe21138f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 5 deletions

View File

@ -145,3 +145,15 @@ where the app can be cross-compiled. This process is kicked off by CI via the `
command to open a bash shell to the container to poke around:
`docker run --rm --mount type=bind,source="$(pwd)",target=/stash -w /stash -i -t stashappdev/compiler:latest /bin/bash`
## Profiling
Stash can be profiled using the `--cpuprofile <output profile filename>` command line flag.
The resulting file can then be used with pprof as follows:
`go tool pprof <path to binary> <path to profile filename>`
With `graphviz` installed and in the path, a call graph can be generated with:
`go tool pprof -svg <path to binary> <path to profile filename> > <output svg file>`

14
main.go
View File

@ -2,6 +2,11 @@
package main
import (
"os"
"os/signal"
"runtime/pprof"
"syscall"
"github.com/stashapp/stash/pkg/api"
"github.com/stashapp/stash/pkg/manager"
@ -12,9 +17,16 @@ import (
func main() {
manager.Initialize()
api.Start()
// stop any profiling at exit
defer pprof.StopCPUProfile()
blockForever()
}
func blockForever() {
select {}
// handle signals
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
<-signals
}

View File

@ -142,7 +142,8 @@ func (e MissingConfigError) Error() string {
}
type Instance struct {
isNewSystem bool
cpuProfilePath string
isNewSystem bool
}
var instance *Instance
@ -162,6 +163,13 @@ func (i *Instance) SetConfigFile(fn string) {
viper.SetConfigFile(fn)
}
// GetCPUProfilePath returns the path to the CPU profile file to output
// profiling info to. This is set only via a commandline flag. Returns an
// empty string if not set.
func (i *Instance) GetCPUProfilePath() string {
return i.cpuProfilePath
}
func (i *Instance) Set(key string, value interface{}) {
viper.Set(key, value)
}

View File

@ -17,18 +17,20 @@ var once sync.Once
type flagStruct struct {
configFilePath string
cpuProfilePath string
}
func Initialize() (*Instance, error) {
var err error
once.Do(func() {
instance = &Instance{}
flags := initFlags()
instance = &Instance{
cpuProfilePath: flags.cpuProfilePath,
}
if err = initConfig(flags); err != nil {
return
}
initEnvs()
if instance.isNewSystem {
@ -46,6 +48,7 @@ func Initialize() (*Instance, error) {
}
func initConfig(flags flagStruct) error {
// The config file is called config. Leave off the file extension.
viper.SetConfigName("config")
@ -98,6 +101,7 @@ func initFlags() flagStruct {
pflag.IP("host", net.IPv4(0, 0, 0, 0), "ip address for the host")
pflag.Int("port", 9999, "port to serve from")
pflag.StringVarP(&flags.configFilePath, "config", "c", "", "config file to use")
pflag.StringVar(&flags.cpuProfilePath, "cpuprofile", "", "write cpu profile to file")
pflag.Parse()
if err := viper.BindPFlags(pflag.CommandLine); err != nil {

View File

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime/pprof"
"sync"
"time"
@ -55,6 +56,7 @@ func Initialize() *singleton {
}
initLog()
initProfiling(cfg.GetCPUProfilePath())
instance = &singleton{
Config: cfg,
@ -92,6 +94,22 @@ func Initialize() *singleton {
return instance
}
func initProfiling(cpuProfilePath string) {
if cpuProfilePath == "" {
return
}
f, err := os.Create(cpuProfilePath)
if err != nil {
logger.Fatalf("unable to create cpu profile file: %s", err.Error())
}
logger.Infof("profiling to %s", cpuProfilePath)
// StopCPUProfile is defer called in main
pprof.StartCPUProfile(f)
}
func initFFMPEG() {
configDirectory := paths.GetStashHomeDirectory()
ffmpegPath, ffprobePath := ffmpeg.GetPaths(configDirectory)