Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set up a pprof server with optional mutex profiling in cmdutil #216

Merged
merged 11 commits into from
Aug 6, 2024

Conversation

sherryyaosf
Copy link
Contributor

@sherryyaosf sherryyaosf commented Jul 26, 2024

Description

We want to configure the pprof listener that is capable of running mutex profiles on server set-up in cmdutil/debug. This PR created a new NewPprofServer method to the cmdutil/debug package, allowing for the setup of a pprof server with configurable profiling types and optional mutex profiling.

Changes

  • Add PProfServerConfig struct: holds configuration for the pprof server.
  • Add PProfServer struct: wraps a pprof server
  • Implement NewPProfServer function: creates and configures the pprof server.
  • Implement Run and Stop methods: the NewPProfServer function includes Run and Stop methods for managing the pprof server lifecycle.
  • Write unit tests for NewPProfServer

Metadata
W-16252388

config.Addr = "127.0.0.1:9998" // Default port
}

return NewContextServer(func(ctx context.Context) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should return a *Server type and then we can build a context server if needed. That follows the debug.Server pattern:
https://github.com/heroku/x/blob/master/cmdutil/debug/debug.go#L48-L52

@@ -93,3 +97,80 @@ func MultiServer(servers ...Server) Server {
StopFunc: s.Stop,
}
}

// NewPprofServer sets up a pprof server with optional mutex profiling.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have this commented out code? Can we just remove it?

@@ -4,6 +4,10 @@ package cmdutil

import (
"context"
"net/http"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put the pprof server in the debug package. It is used for debugging

mux := http.NewServeMux()

profileHandlers := map[string]http.HandlerFunc{
"index": pprof.Index,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the handlers should be configurable. We don't need all of these profile types most of the time.

pprofSrv.Close()
}()

return pprofSrv.ListenAndServe()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have some logging when we start the server, including what port we are binding to: https://github.com/heroku/x/blob/master/cmdutil/debug/debug.go#L48-L52

@sherryyaosf sherryyaosf requested a review from slizco August 2, 2024 07:32
Copy link
Contributor

@slizco slizco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also like to see a unit test of this

logger logrus.FieldLogger
addr string
done chan struct{}
pprofServer *http.Server
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see this as a separate server. So something like debu.PProfServer.

}

l.WithFields(logrus.Fields{
"at": "binding",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binding log should happen when Run() is called. The server does not actually start until Run()

for profile, handler := range config.ProfileHandlers {
if handler != nil {
if profile == "mutex" {
runtime.SetMutexProfileFraction(2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be configurable on the profile config.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean something like this:

type PProfServerConfig struct {
  MutextProfileFraction int
}

If set to zero and the mutex profile is enabled, we can use a reasonable default of 2.

"service": "pprof",
"profile": profile,
}).Info("Added pprof profile handler")
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this else. If someone has passed in a nil-value handler, that is poor configuration we should not support. A nil-pointer exception and subsequent panic are acceptable

"at": "adding",
"service": "pprof",
"profile": profile,
}).Info("Added pprof profile handler")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think we need to log this. If we want, we can do a simple strings.Join() on the profiles slice and then log profiles="heap,cpu,blah..." when we log the port binding in Run().

@sherryyaosf sherryyaosf requested a review from slizco August 2, 2024 19:47
}

// ProfileConfig holds the configuration for the pprof server.
type ProfileConfig struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is used to configure a PProfServer, let's call it PProfServerConfig.

for profile, handler := range config.ProfileHandlers {
if handler != nil {
if profile == "mutex" {
runtime.SetMutexProfileFraction(2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean something like this:

type PProfServerConfig struct {
  MutextProfileFraction int
}

If set to zero and the mutex profile is enabled, we can use a reasonable default of 2.

if s.pprofServer != nil {
go func() {
if err := s.pprofServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
s.logger.WithError(err).Error("pprof server error")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll want the error to be returned by Run()

if profile == "mutex" {
runtime.SetMutexProfileFraction(2)
}
mux.HandleFunc("/debug/pprof/"+profile, handler)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to raw construction of the handlers like this. The pprof.Handler function can construct the handlers for us: https://pkg.go.dev/net/http/pprof#Handler

@sherryyaosf sherryyaosf requested a review from slizco August 5, 2024 19:57
}
runtime.SetMutexProfileFraction(config.MutexProfileFraction)
}
mux.Handle("/debug/pprof/"+profile, pprof.Handler(profile))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I am thinking that we should just use pprof.Index and generate a handler like so:

http.HandlerFunc(pprof.Index)

Then you can avoid using mux and having to pass in the list of profiles.

for _, profile := range config.ProfileNames {
if profile == "mutex" {
if config.MutexProfileFraction == 0 {
config.MutexProfileFraction = 2 // Use default value of 2 if not set
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not modify the config. That is unexpected behavior. Instead, we can use a local variable:

mpf := defaultMutextProfileFraction // value is 2
if config.MutexProfileFraction != 0 {
  mpf = config.MutextProfileFraction
}
runtime.SetMutextProfileFraction(mpf)

@sherryyaosf sherryyaosf requested a review from slizco August 6, 2024 18:51
Copy link
Contributor

@slizco slizco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor things but this looks good!

"addr": s.addr,
}).Info()

if s.pprofServer != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is nil, I think we can return an error.

}
runtime.SetMutexProfileFraction(tt.expectedMutexFraction) // Reset to the expected value

urls := []string{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think verifying the paths is probably overkill since that is functionality within pprof package.

@sherryyaosf sherryyaosf merged commit 1e0ca7c into master Aug 6, 2024
3 checks passed
@sherryyaosf sherryyaosf deleted the sherryyao-branch branch August 6, 2024 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants