Update pprof module extension

pull/3829/head
niklr 3 years ago committed by Leo Chen
parent 9bb0e177ad
commit 7af5cdd864
  1. 79
      api/service/pprof/service.go
  2. 2
      api/service/pprof/service_test.go
  3. 6
      cmd/harmony/config_migrations.go
  4. 4
      cmd/harmony/default.go
  5. 2
      cmd/harmony/flags.go
  6. 4
      cmd/harmony/flags_test.go
  7. 2
      go.mod

@ -49,6 +49,7 @@ type Service struct {
var ( var (
initOnce sync.Once initOnce sync.Once
svc = &Service{} svc = &Service{}
cpuFile *os.File
) )
// NewService creates the new pprof service // NewService creates the new pprof service
@ -61,21 +62,21 @@ func NewService(cfg Config) *Service {
func newService(cfg Config) *Service { func newService(cfg Config) *Service {
if !cfg.Enabled { if !cfg.Enabled {
utils.Logger().Info().Msg("Pprof service disabled...") utils.Logger().Info().Msg("pprof service disabled...")
return nil return nil
} }
utils.Logger().Debug().Str("cfg", cfg.String()).Msg("Pprof") utils.Logger().Debug().Str("cfg", cfg.String()).Msg("pprof")
svc.config = cfg svc.config = cfg
if profiles, err := cfg.unpackProfilesIntoMap(); err != nil { profiles, err := cfg.unpackProfilesIntoMap()
log.Fatal("Could not unpack pprof profiles into map") if err != nil {
} else { log.Fatal("could not unpack pprof profiles into map")
svc.profiles = profiles
} }
svc.profiles = profiles
go func() { go func() {
utils.Logger().Info().Str("address", cfg.ListenAddr).Msg("Starting pprof HTTP service") utils.Logger().Info().Str("address", cfg.ListenAddr).Msg("starting pprof HTTP service")
http.ListenAndServe(cfg.ListenAddr, nil) http.ListenAndServe(cfg.ListenAddr, nil)
}() }()
@ -93,8 +94,10 @@ func (s *Service) Start() error {
return err return err
} }
if cpuProfile, ok := s.profiles[CPU]; ok { if _, ok := s.profiles[CPU]; ok {
go handleCpuProfile(cpuProfile, dir) // The nature of the pprof CPU profile is fundamentally different to the other profiles, because it streams output to a file during profiling.
// Thus it has to be started outside of the defined interval.
go restartCpuProfile(dir)
} }
for _, profile := range s.profiles { for _, profile := range s.profiles {
@ -112,9 +115,12 @@ func (s *Service) Stop() error {
} }
for _, profile := range s.profiles { for _, profile := range s.profiles {
if profile.Name == CPU { if profile.Name == CPU {
pprof.StopCPUProfile() stopCpuProfile()
} else { } else {
saveProfile(profile, dir) err := saveProfile(profile, dir)
if err != nil {
utils.Logger().Error().Err(err).Msg(fmt.Sprintf("could not save pprof profile: %s", profile.Name))
}
} }
} }
return nil return nil
@ -135,9 +141,15 @@ func scheduleProfile(profile Profile, dir string) {
select { select {
case <-ticker.C: case <-ticker.C:
if profile.Name == CPU { if profile.Name == CPU {
handleCpuProfile(profile, dir) err := restartCpuProfile(dir)
if err != nil {
utils.Logger().Error().Err(err).Msg("could not start pprof CPU profile")
}
} else { } else {
saveProfile(profile, dir) err := saveProfile(profile, dir)
if err != nil {
utils.Logger().Error().Err(err).Msg(fmt.Sprintf("could not save pprof profile: %s", profile.Name))
}
} }
} }
} }
@ -149,30 +161,39 @@ func scheduleProfile(profile Profile, dir string) {
func saveProfile(profile Profile, dir string) error { func saveProfile(profile Profile, dir string) error {
f, err := newTempFile(dir, profile.Name, ".pb.gz") f, err := newTempFile(dir, profile.Name, ".pb.gz")
if err != nil { if err != nil {
utils.Logger().Error().Err(err).Msg(fmt.Sprintf("Could not save pprof profile: %s", profile.Name))
return err return err
} }
defer f.Close() defer f.Close()
if profile.ProfileRef != nil { if profile.ProfileRef == nil {
return nil
}
err = profile.ProfileRef.WriteTo(f, profile.Debug) err = profile.ProfileRef.WriteTo(f, profile.Debug)
if err != nil { if err != nil {
utils.Logger().Error().Err(err).Msg(fmt.Sprintf("Could not write pprof profile: %s", profile.Name))
return err return err
} }
utils.Logger().Info().Msg(fmt.Sprintf("Saved pprof profile in: %s", f.Name())) utils.Logger().Info().Msg(fmt.Sprintf("saved pprof profile in: %s", f.Name()))
return nil
} }
// restartCpuProfile stops the current CPU profile, if any and then starts a new CPU profile. While profiling in the background, the profile will be buffered and written to a file.
func restartCpuProfile(dir string) error {
stopCpuProfile()
f, err := newTempFile(dir, CPU, ".pb.gz")
if err != nil {
return err
}
pprof.StartCPUProfile(f)
cpuFile = f
utils.Logger().Info().Msg(fmt.Sprintf("saved pprof CPU profile in: %s", f.Name()))
return nil return nil
} }
// handleCpuProfile handles the provided CPU profile // stopCpuProfile stops the current CPU profile, if any
func handleCpuProfile(profile Profile, dir string) { func stopCpuProfile() {
pprof.StopCPUProfile() pprof.StopCPUProfile()
f, err := newTempFile(dir, profile.Name, ".pb.gz") if cpuFile != nil {
if err == nil { cpuFile.Close()
pprof.StartCPUProfile(f) cpuFile = nil
utils.Logger().Info().Msg(fmt.Sprintf("Saved pprof CPU profile in: %s", f.Name()))
} else {
utils.Logger().Error().Err(err).Msg("Could not start pprof CPU profile")
} }
} }
@ -185,8 +206,8 @@ func (config *Config) unpackProfilesIntoMap() (map[string]Profile, error) {
for index, name := range config.ProfileNames { for index, name := range config.ProfileNames {
profile := Profile{ profile := Profile{
Name: name, Name: name,
Interval: 0, Interval: 0, // 0 saves the profile when stopping the service
Debug: 0, Debug: 0, // 0 writes the gzip-compressed protocol buffer
} }
// Try set interval value // Try set interval value
if len(config.ProfileIntervals) == len(config.ProfileNames) { if len(config.ProfileIntervals) == len(config.ProfileNames) {
@ -203,7 +224,7 @@ func (config *Config) unpackProfilesIntoMap() (map[string]Profile, error) {
// Try set the profile reference // Try set the profile reference
if profile.Name != CPU { if profile.Name != CPU {
if p := pprof.Lookup(profile.Name); p == nil { if p := pprof.Lookup(profile.Name); p == nil {
return nil, fmt.Errorf("Pprof profile does not exist: %s", profile.Name) return nil, fmt.Errorf("pprof profile does not exist: %s", profile.Name)
} else { } else {
profile.ProfileRef = p profile.ProfileRef = p
} }
@ -219,7 +240,7 @@ func newTempFile(dir, name, suffix string) (*os.File, error) {
currentTime := time.Now().Unix() currentTime := time.Now().Unix()
f, err := os.OpenFile(filepath.Join(dir, fmt.Sprintf("%s%d%s", prefix, currentTime, suffix)), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) f, err := os.OpenFile(filepath.Join(dir, fmt.Sprintf("%s%d%s", prefix, currentTime, suffix)), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err != nil { if err != nil {
return nil, fmt.Errorf("Could not create file of the form %s%d%s", prefix, currentTime, suffix) return nil, fmt.Errorf("could not create file of the form %s%d%s", prefix, currentTime, suffix)
} }
return f, nil return f, nil
} }

@ -37,7 +37,7 @@ func TestUnpackProfilesIntoMap(t *testing.T) {
ProfileNames: []string{"test"}, ProfileNames: []string{"test"},
}, },
expMap: nil, expMap: nil,
expErr: errors.New("Pprof profile does not exist: test"), expErr: errors.New("pprof profile does not exist: test"),
}, },
{ {
input: &Config{ input: &Config{

@ -177,13 +177,13 @@ func init() {
confTree.Set("Pprof.Folder", defaultConfig.Pprof.Folder) confTree.Set("Pprof.Folder", defaultConfig.Pprof.Folder)
} }
if confTree.Get("Pprof.ProfileNames") == nil { if confTree.Get("Pprof.ProfileNames") == nil {
confTree.Set("Pprof.ProfileNames", make([]interface{}, 0)) confTree.Set("Pprof.ProfileNames", defaultConfig.Pprof.ProfileNames)
} }
if confTree.Get("Pprof.ProfileIntervals") == nil { if confTree.Get("Pprof.ProfileIntervals") == nil {
confTree.Set("Pprof.ProfileIntervals", make([]interface{}, 0)) confTree.Set("Pprof.ProfileIntervals", defaultConfig.Pprof.ProfileIntervals)
} }
if confTree.Get("Pprof.ProfileDebugValues") == nil { if confTree.Get("Pprof.ProfileDebugValues") == nil {
confTree.Set("Pprof.ProfileDebugValues", make([]interface{}, 0)) confTree.Set("Pprof.ProfileDebugValues", defaultConfig.Pprof.ProfileDebugValues)
} }
confTree.Set("Version", "2.2.0") confTree.Set("Version", "2.2.0")

@ -67,8 +67,8 @@ var defaultConfig = harmonyconfig.HarmonyConfig{
ListenAddr: "127.0.0.1:6060", ListenAddr: "127.0.0.1:6060",
Folder: "./profiles", Folder: "./profiles",
ProfileNames: []string{}, ProfileNames: []string{},
ProfileIntervals: []int{}, ProfileIntervals: []int{600},
ProfileDebugValues: []int{}, ProfileDebugValues: []int{0},
}, },
Log: harmonyconfig.LogConfig{ Log: harmonyconfig.LogConfig{
Folder: "./latest", Folder: "./latest",

@ -986,7 +986,7 @@ var (
} }
pprofProfileDebugFlag = cli.IntSliceFlag{ pprofProfileDebugFlag = cli.IntSliceFlag{
Name: "pprof.profile.debug", Name: "pprof.profile.debug",
Usage: "a list of pprof profile debug integer values (separated by ,) e.g. 0 writes the gzip-compressed protocol buffer and 1 writes the legacy text format", Usage: "a list of pprof profile debug integer values (separated by ,) e.g. 0 writes the gzip-compressed protocol buffer and 1 writes the legacy text format. Predefined profiles may assign meaning to other debug values: https://golang.org/pkg/runtime/pprof/",
DefValue: defaultConfig.Pprof.ProfileDebugValues, DefValue: defaultConfig.Pprof.ProfileDebugValues,
Hidden: true, Hidden: true,
} }

@ -102,8 +102,8 @@ func TestHarmonyFlags(t *testing.T) {
ListenAddr: "127.0.0.1:6060", ListenAddr: "127.0.0.1:6060",
Folder: "./profiles", Folder: "./profiles",
ProfileNames: []string{}, ProfileNames: []string{},
ProfileIntervals: []int{}, ProfileIntervals: []int{600},
ProfileDebugValues: []int{}, ProfileDebugValues: []int{0},
}, },
Log: harmonyconfig.LogConfig{ Log: harmonyconfig.LogConfig{
Folder: "./latest", Folder: "./latest",

@ -40,7 +40,7 @@ require (
github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multiaddr-dns v0.3.1
github.com/natefinch/lumberjack v2.0.0+incompatible github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/pborman/uuid v1.2.0 github.com/pborman/uuid v1.2.0
github.com/pelletier/go-toml v1.2.0 github.com/pelletier/go-toml v1.9.3
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.8.0 github.com/prometheus/client_golang v1.8.0
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0

Loading…
Cancel
Save