go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/internal/pprof.go (about)

     1  // Copyright 2019 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package internal
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"html/template"
    21  	"runtime"
    22  	"sync"
    23  
    24  	"go.chromium.org/luci/common/logging"
    25  
    26  	"go.chromium.org/luci/server/portal"
    27  )
    28  
    29  const (
    30  	blockProfileRate = 100
    31  	mutexProfileRate = 10
    32  )
    33  
    34  var (
    35  	profilingEnabled bool
    36  	profilingMutex   sync.Mutex
    37  )
    38  
    39  func setProfilingEnabled(ctx context.Context, enabled bool) {
    40  	profilingMutex.Lock()
    41  	defer profilingMutex.Unlock()
    42  	if profilingEnabled != enabled {
    43  		profilingEnabled = enabled
    44  		if enabled {
    45  			runtime.SetBlockProfileRate(blockProfileRate)
    46  			runtime.SetMutexProfileFraction(mutexProfileRate)
    47  			logging.Warningf(ctx, "Contention profiling is now enabled")
    48  		} else {
    49  			runtime.SetBlockProfileRate(0)
    50  			runtime.SetMutexProfileFraction(0)
    51  			logging.Warningf(ctx, "Contention profiling is now disabled")
    52  		}
    53  	}
    54  }
    55  
    56  func isProfilingEnabled() bool {
    57  	profilingMutex.Lock()
    58  	defer profilingMutex.Unlock()
    59  	return profilingEnabled
    60  }
    61  
    62  type pprofPage struct {
    63  	portal.BasePage
    64  }
    65  
    66  func (pprofPage) Title(ctx context.Context) (string, error) {
    67  	return "Profiling options", nil
    68  }
    69  
    70  func (pprofPage) Overview(ctx context.Context) (template.HTML, error) {
    71  	curValue := "disabled"
    72  	if isProfilingEnabled() {
    73  		curValue = "enabled"
    74  	}
    75  	return template.HTML(fmt.Sprintf(`
    76  <p>This page allows to enable the collection of goroutine blocking and mutex
    77  contention events from this process, by calling:</p>
    78  <pre>
    79    runtime.SetBlockProfileRate(%d)
    80    runtime.SetMutexProfileFraction(%d)
    81  </pre>
    82  
    83  <p>The current value of this setting is <b>%s</b>.</p>
    84  
    85  <p>This slows down the process and should be used sparingly only when
    86  debugging performance issues. This setting affects only this single specific
    87  process and it is not preserved between process restarts.</p>
    88  
    89  <p>See <a href="https://golang.org/pkg/runtime/#SetBlockProfileRate">SetBlockProfileRate</a> and
    90  <a href="https://golang.org/pkg/runtime/#SetMutexProfileFraction">SetMutexProfileFraction</a>
    91  for more info.</p>
    92  
    93  <p>All available profiles are listed at <a href="/debug/pprof/">/debug/pprof</a>.</p>
    94  
    95  <p>To visualize some profile (e.g. <code>heap</code>), run e.g.:</p>
    96  <pre>
    97    go tool pprof -png \
    98        http://127.0.0.1:8900/debug/pprof/heap > out.png
    99  </pre>
   100  
   101  <p>To save a profile as a compressed protobuf message for later analysis:</p>
   102  <pre>
   103    go tool pprof -proto \
   104        http://127.0.0.1:8900/debug/pprof/heap > out.pb.gz
   105  </pre>
   106  `,
   107  		blockProfileRate, mutexProfileRate, curValue)), nil
   108  }
   109  
   110  func (pprofPage) Actions(ctx context.Context) ([]portal.Action, error) {
   111  	var profilingAction portal.Action
   112  	if !isProfilingEnabled() {
   113  		profilingAction = portal.Action{
   114  			ID:    "EnableProfiling",
   115  			Title: "Enable contention profiling",
   116  			Callback: func(ctx context.Context) (string, template.HTML, error) {
   117  				setProfilingEnabled(ctx, true)
   118  				return "Done", `<p>Contention profiling is now enabled.</p>`, nil
   119  			},
   120  		}
   121  	} else {
   122  		profilingAction = portal.Action{
   123  			ID:    "DisableProfiling",
   124  			Title: "Disable contention profiling",
   125  			Callback: func(ctx context.Context) (string, template.HTML, error) {
   126  				setProfilingEnabled(ctx, false)
   127  				return "Done", `<p>Contention profiling is now disabled.</p>`, nil
   128  			},
   129  		}
   130  	}
   131  	return []portal.Action{profilingAction}, nil
   132  }
   133  
   134  func init() {
   135  	portal.RegisterPage("pprof", pprofPage{})
   136  }