@ -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 ( "P prof service disabled..." )
utils . Logger ( ) . Info ( ) . Msg ( "p prof service disabled..." )
return nil
return nil
}
}
utils . Logger ( ) . Debug ( ) . Str ( "cfg" , cfg . String ( ) ) . Msg ( "P prof" )
utils . Logger ( ) . Debug ( ) . Str ( "cfg" , cfg . String ( ) ) . Msg ( "p prof" )
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 ( "S tarting pprof HTTP service" )
utils . Logger ( ) . Info ( ) . Str ( "address" , cfg . ListenAddr ) . Msg ( "s tarting 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 . StopCPU Profile( )
stopCpu 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 ) )
}
}
}
}
}
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 {
err = profile . ProfileRef . WriteTo ( f , profile . Debug )
return nil
if err != nil {
}
utils . Logger ( ) . Error ( ) . Err ( err ) . Msg ( fmt . Sprintf ( "Could not write pprof profile: %s" , profile . Name ) )
err = profile . ProfileRef . WriteTo ( f , profile . Debug )
return err
if err != nil {
}
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
return nil
}
}
// handleCpuProfile handles the provided CPU profile
// 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 handleCpuProfile ( profile Profile , dir string ) {
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
}
// stopCpuProfile stops the current CPU profile, if any
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 ( "P prof profile does not exist: %s" , profile . Name )
return nil , fmt . Errorf ( "p prof 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 ( "C ould not create file of the form %s%d%s" , prefix , currentTime , suffix )
return nil , fmt . Errorf ( "c ould not create file of the form %s%d%s" , prefix , currentTime , suffix )
}
}
return f , nil
return f , nil
}
}