zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/debug/pprof/pprof.go (about)

     1  //go:build profile
     2  // +build profile
     3  
     4  package pprof
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"html"
    10  	"io"
    11  	"net/http"
    12  	"net/http/pprof"
    13  	"net/url"
    14  	runPprof "runtime/pprof"
    15  	"sort"
    16  	"strings"
    17  
    18  	"github.com/gorilla/mux"
    19  
    20  	"zotregistry.io/zot/pkg/api/config"
    21  	registryConst "zotregistry.io/zot/pkg/api/constants"
    22  	zcommon "zotregistry.io/zot/pkg/common"
    23  	"zotregistry.io/zot/pkg/debug/constants"
    24  	"zotregistry.io/zot/pkg/log"
    25  )
    26  
    27  type profileEntry struct {
    28  	Name  string
    29  	Href  string
    30  	Desc  string
    31  	Count int
    32  }
    33  
    34  var profileDescriptions = map[string]string{ //nolint: gochecknoglobals
    35  	"allocs":       "A sampling of all past memory allocations",
    36  	"block":        "Stack traces that led to blocking on synchronization primitives",
    37  	"cmdline":      "The command line invocation of the current program",
    38  	"goroutine":    "Stack traces of all current goroutines. Use debug=2 as a query parameter to export in the same format as an unrecovered panic.",  //nolint: lll
    39  	"heap":         "A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.", //nolint: lll
    40  	"mutex":        "Stack traces of holders of contended mutexes",
    41  	"profile":      "CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.", //nolint: lll
    42  	"threadcreate": "Stack traces that led to the creation of new OS threads",
    43  	"trace":        "A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.", //nolint: lll
    44  }
    45  
    46  func SetupPprofRoutes(conf *config.Config, router *mux.Router, authFunc mux.MiddlewareFunc,
    47  	log log.Logger,
    48  ) {
    49  	// If authn/authz are enabled the endpoints for pprof should be available only to admins
    50  	pprofRouter := router.PathPrefix(constants.ProfilingEndpoint).Subrouter()
    51  	pprofRouter.Use(zcommon.AuthzOnlyAdminsMiddleware(conf))
    52  	pprofRouter.Methods(http.MethodGet).Handler(http.HandlerFunc(
    53  		func(w http.ResponseWriter, r *http.Request) {
    54  			if name, found := strings.CutPrefix(r.URL.Path,
    55  				registryConst.RoutePrefix+constants.ProfilingEndpoint); found {
    56  				if name != "" {
    57  					switch name {
    58  					case "profile": // not available through pprof.Handler
    59  						pprof.Profile(w, r)
    60  
    61  						return
    62  					case "trace": // not available through pprof.Handler
    63  						pprof.Trace(w, r)
    64  
    65  						return
    66  					default:
    67  						pprof.Handler(name).ServeHTTP(w, r)
    68  
    69  						return
    70  					}
    71  				}
    72  			}
    73  
    74  			var profiles []profileEntry
    75  			for _, p := range runPprof.Profiles() {
    76  				profiles = append(profiles, profileEntry{
    77  					Name:  p.Name(),
    78  					Href:  p.Name(),
    79  					Desc:  profileDescriptions[p.Name()],
    80  					Count: p.Count(),
    81  				})
    82  			}
    83  
    84  			// Adding other profiles exposed from within this package
    85  			for _, p := range []string{"cmdline", "profile", "trace"} {
    86  				profiles = append(profiles, profileEntry{
    87  					Name: p,
    88  					Href: p,
    89  					Desc: profileDescriptions[p],
    90  				})
    91  			}
    92  
    93  			sort.Slice(profiles, func(i, j int) bool {
    94  				return profiles[i].Name < profiles[j].Name
    95  			})
    96  
    97  			if err := indexTmplExecute(w, profiles); err != nil {
    98  				log.Print(err)
    99  			}
   100  		}))
   101  }
   102  
   103  func indexTmplExecute(writer io.Writer, profiles []profileEntry) error {
   104  	var buff bytes.Buffer
   105  
   106  	buff.WriteString(`<html>
   107  <head>
   108  <title>/v2/_zot/pprof/</title>
   109  <style>
   110  .profile-name{
   111  	display:inline-block;
   112  	width:6rem;
   113  }
   114  </style>
   115  </head>
   116  <body>
   117  /debug/pprof/
   118  <br>
   119  <p>Set debug=1 as a query parameter to export in legacy text format</p>
   120  <br>
   121  Types of profiles available:
   122  <table>
   123  <thead><td>Count</td><td>Profile</td></thead>
   124  `)
   125  
   126  	for _, profile := range profiles {
   127  		link := &url.URL{Path: profile.Href, RawQuery: "debug=1"}
   128  		fmt.Fprintf(&buff, "<tr><td>%d</td><td><a href='%s'>%s</a></td></tr>\n",
   129  			profile.Count, link, html.EscapeString(profile.Name))
   130  	}
   131  
   132  	buff.WriteString(`</table>
   133  <a href="goroutine?debug=2">full goroutine stack dump</a>
   134  <br>
   135  <p>
   136  Profile Descriptions:
   137  <ul>
   138  `)
   139  
   140  	for _, profile := range profiles {
   141  		fmt.Fprintf(&buff, "<li><div class=profile-name>%s: </div> %s</li>\n",
   142  			html.EscapeString(profile.Name), html.EscapeString(profile.Desc))
   143  	}
   144  
   145  	buff.WriteString(`</ul>
   146  </p>
   147  </body>
   148  </html>`)
   149  
   150  	_, err := writer.Write(buff.Bytes())
   151  
   152  	return err
   153  }