From e4f030e0719e363fb3b944e4eefab0739e1414c9 Mon Sep 17 00:00:00 2001 From: Aaron Alpar Date: Fri, 17 Nov 2023 12:32:20 -0800 Subject: [PATCH 1/8] debug libraries --- debug/debug.go | 327 ++++++++++++++++++++++++++++++++++++++++++++ debug/debug_test.go | 126 +++++++++++++++++ 2 files changed, 453 insertions(+) create mode 100644 debug/debug.go create mode 100644 debug/debug_test.go diff --git a/debug/debug.go b/debug/debug.go new file mode 100644 index 0000000000..88ac5f537f --- /dev/null +++ b/debug/debug.go @@ -0,0 +1,327 @@ +package debug + +import ( + "bufio" + "bytes" + "context" + "encoding/pem" + "errors" + "io" + "os" + "runtime" + "runtime/pprof" + "strconv" + "strings" + "sync" + + errors2 "gopkg.in/errgo.v2/fmt/errors" + + "github.com/kopia/kopia/repo/logging" +) + +var log = logging.Module("kopia/debug") + +// ProfileName the name of the profile (see: runtime/pprof/Lookup) +type ProfileName string + +const ( + // DefaultDebugProfileRate default sample/data fraction for profile sample collection rates (1/x, where x is the + // data fraction sample rate). + DefaultDebugProfileRate = 100 + // DefaultDebugProfileDumpBufferSizeB default size of the pprof output buffer. + DefaultDebugProfileDumpBufferSizeB = 1 << 17 +) + +const ( + // EnvVarKopiaDebugPprof environment variable that contains the pprof dump configuration. + EnvVarKopiaDebugPprof = "KOPIA_DEBUG_PPROF" +) + +// flags used to configure profiling in EnvVarKopiaDebugPprof +const ( + // KopiaDebugFlagForceGc force garbage collection before dumping heap data. + KopiaDebugFlagForceGc = "forcegc" + // KopiaDebugFlagDebug value of the profiles `debug` parameter. + KopiaDebugFlagDebug = "debug" + // KopiaDebugFlagRate rate setting for the named profile (if available). always an integer. + KopiaDebugFlagRate = "rate" +) + +const ( + ProfileNameBlock ProfileName = "block" + ProfileNameMutex = "mutex" + ProfileNameCpu = "cpu" +) + +// ProfileConfig configuration flags for a profile. +type ProfileConfig struct { + flags []string + buf *bytes.Buffer +} + +// ProfileConfigs configuration flags for all requested profiles. +type ProfileConfigs struct { + mu sync.Mutex + pcm map[ProfileName]*ProfileConfig +} + +var ( + pprofConfigs = &ProfileConfigs{} +) + +type pprofSetRate struct { + fn func(int) + def int +} + +var pprofProfileRates = map[ProfileName]pprofSetRate{ + ProfileNameBlock: { + fn: func(x int) { runtime.SetBlockProfileRate(x) }, + def: DefaultDebugProfileRate, + }, + ProfileNameMutex: { + fn: func(x int) { runtime.SetMutexProfileFraction(x) }, + def: DefaultDebugProfileRate, + }, +} + +// GetValue get the value of the named flag, `s`. false will be returned +// if the flag value does not exist +func (p ProfileConfig) GetValue(s string) (string, bool) { + for _, f := range p.flags { + kvs := strings.SplitN(f, "=", 2) + if kvs[0] != s { + continue + } + if len(kvs) == 1 { + return "", true + } + return kvs[1], true + } + return "", false +} + +func parseProfileConfigs(bufSizeB int, ppconfigs string) map[ProfileName]*ProfileConfig { + pbs := map[ProfileName]*ProfileConfig{} + allProfileOptions := strings.Split(ppconfigs, ":") + for _, profileOptionWithFlags := range allProfileOptions { + // of those, see if any have profile-specific settings + profileFlagNameValuePairs := strings.SplitN(profileOptionWithFlags, "=", 2) + flagValue := "" + if len(profileFlagNameValuePairs) > 1 { + flagValue = profileFlagNameValuePairs[1] + } + flagKey := ProfileName(strings.ToLower(profileFlagNameValuePairs[0])) + pbs[flagKey] = newProfileConfig(bufSizeB, flagValue) + } + return pbs +} + +// newProfileConfig create a new profiling configuration. +func newProfileConfig(bufSizeB int, ppconfig string) *ProfileConfig { + q := &ProfileConfig{ + buf: bytes.NewBuffer(make([]byte, 0, bufSizeB)), + } + flgs := strings.Split(ppconfig, ",") + if len(flgs) > 0 && flgs[0] != "" { // len(flgs) > 1 && flgs[0] == "" should never happen + q.flags = flgs + } + return q +} + +func setupProfileFractions(ctx context.Context, profileBuffers map[ProfileName]*ProfileConfig) { + for k, pprofset := range pprofProfileRates { + v, ok := profileBuffers[k] + if !ok { + // profile not configured - leave it alone + continue + } + if v == nil { + // profile configured, but no rate - set to default + pprofset.fn(pprofset.def) + continue + } + s, _ := v.GetValue(KopiaDebugFlagRate) + if s == "" { + // flag without an argument - set to default + pprofset.fn(pprofset.def) + continue + } + n1, err := strconv.Atoi(s) + if err != nil { + log(ctx).With("cause", err).Warnf("invalid PPROF rate, %q, for %s: %v", s, k) + continue + } + log(ctx).Debugf("setting PPROF rate, %d, for %s", n1, k) + pprofset.fn(n1) + } +} + +// ClearProfileFractions set the profile fractions to their zero values. +func ClearProfileFractions(profileBuffers map[ProfileName]*ProfileConfig) { + for k, pprofset := range pprofProfileRates { + v := profileBuffers[k] + if v == nil { // fold missing values and empty values + continue + } + _, ok := v.GetValue(KopiaDebugFlagRate) + if !ok { // only care if a value might have been set before + continue + } + pprofset.fn(0) + } +} + +// StartProfileBuffers start profile buffers for enabled profiles/trace. Buffers +// are returned in an slice of buffers: CPU, Heap and trace respectively. class is used to distinguish profiles +// external to kopia. +func StartProfileBuffers(ctx context.Context) { + ppconfigs := os.Getenv(EnvVarKopiaDebugPprof) + // if empty, then don't bother configuring but emit a log message - use might be expecting them to be configured + if ppconfigs == "" { + log(ctx).Debug("no profile buffers enabled") + return + } + + bufSizeB := DefaultDebugProfileDumpBufferSizeB + + // look for matching services. "*" signals all services for profiling + log(ctx).Debug("configuring profile buffers") + + // aquire global lock when performing operations with global side-effects + pprofConfigs.mu.Lock() + defer pprofConfigs.mu.Unlock() + + pprofConfigs.pcm = parseProfileConfigs(bufSizeB, ppconfigs) + + // profiling rates need to be set before starting profiling + setupProfileFractions(ctx, pprofConfigs.pcm) + + // cpu has special initialization + v, ok := pprofConfigs.pcm[ProfileNameCpu] + if ok { + err := pprof.StartCPUProfile(v.buf) + if err != nil { + log(ctx).With("cause", err).Warn("cannot start cpu PPROF") + delete(pprofConfigs.pcm, ProfileNameCpu) + } + } +} + +// DumpPem dump a PEM version of the byte slice, bs, into writer, wrt. +func DumpPem(bs []byte, types string, wrt *os.File) error { + blk := &pem.Block{ + Type: types, + Bytes: bs, + } + // wrt is likely a line oriented writer, so writing individual lines + // will make best use of output buffer and help prevent overflows or + // stalls in the output path. + pr, pw := io.Pipe() + // encode PEM in the background and output in a line oriented + // fashion - this prevents the need for a large buffer to hold + // the encoded PEM. + go func() { + defer pw.Close() + err := pem.Encode(pw, blk) + if err != nil { + return + } + }() + rdr := bufio.NewReader(pr) + var err0, err1 error + for err0 == nil && err1 == nil { + var ln []byte + ln, err0 = rdr.ReadBytes('\n') + _, err1 = wrt.Write(ln) + } + // got a write error + if err1 != nil { + return err1 + } + // did not get a read error. file ends in newline + if err0 == nil { + return nil + } + // if file does not end in newline, then output one + if errors.Is(err0, io.EOF) { + _, err1 = wrt.WriteString("\n") + // TODO: lint says errors need to be wrapped ... figure out how its dealth with in rest of kopia + return errors2.Wrap(err0) + } + return err0 +} + +func parseDebugNumber(v *ProfileConfig) (int, error) { + debugs, ok := v.GetValue(KopiaDebugFlagDebug) + if !ok { + return 0, nil + } + debug, err := strconv.Atoi(debugs) + if err != nil { + return 0, err + } + return debug, nil +} + +// StopProfileBuffers stop and dump the contents of the buffers to the log as PEMs. Buffers +// supplied here are from StartProfileBuffers +func StopProfileBuffers(ctx context.Context) { + pprofConfigs.mu.Lock() + defer pprofConfigs.mu.Unlock() + + if pprofConfigs == nil { + log(ctx).Debug("profile buffers not configured") + return + } + + log(ctx).Debug("saving PEM buffers for output") + // cpu and heap profiles requires special handling + for k, v := range pprofConfigs.pcm { + log(ctx).Debugf("stopping PPROF profile %q", k) + if v == nil { + continue + } + if k == ProfileNameCpu { + pprof.StopCPUProfile() + continue + } + _, ok := v.GetValue(KopiaDebugFlagForceGc) + if ok { + log(ctx).Debug("performing GC before PPROF dump ...") + runtime.GC() + } + debug, err := parseDebugNumber(v) + if err != nil { + log(ctx).With("cause", err).Warn("invalid PPROF configuration debug number") + continue + } + pent := pprof.Lookup(string(k)) + if pent == nil { + log(ctx).Warnf("no system PPROF entry for %q", k) + delete(pprofConfigs.pcm, k) + continue + } + err = pent.WriteTo(v.buf, debug) + if err != nil { + log(ctx).With("cause", err).Warn("error writing PPROF buffer") + continue + } + } + // dump the profiles out into their respective PEMs + for k, v := range pprofConfigs.pcm { + if v == nil { + continue + } + unm := strings.ToUpper(string(k)) + log(ctx).Infof("dumping PEM for %q", unm) + err := DumpPem(v.buf.Bytes(), unm, os.Stderr) + if err != nil { + log(ctx).With("cause", err).Error("cannot write PEM") + } + } + + // clear the profile rates and fractions to effectively stop profiling + ClearProfileFractions(pprofConfigs.pcm) + pprofConfigs.pcm = map[ProfileName]*ProfileConfig{} +} diff --git a/debug/debug_test.go b/debug/debug_test.go new file mode 100644 index 0000000000..e5ee7dd80b --- /dev/null +++ b/debug/debug_test.go @@ -0,0 +1,126 @@ +package debug + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDebug_parseProfileConfigs(t *testing.T) { + tcs := []struct { + in string + key ProfileName + expect []string + }{ + { + in: "foo=bar", + key: "foo", + expect: []string{ + "bar", + }, + }, + { + in: "first=one=1", + key: "first", + expect: []string{ + "one=1", + }, + }, + { + in: "foo=bar:first=one=1", + key: "first", + expect: []string{ + "one=1", + }, + }, + { + in: "foo=bar:first=one=1,two=2", + key: "first", + expect: []string{ + "one=1", + "two=2", + }, + }, + { + in: "foo=bar:first=one=1,two=2:second:third", + key: "first", + expect: []string{ + "one=1", + "two=2", + }, + }, + { + in: "foo=bar:first=one=1,two=2:second:third", + key: "foo", + expect: []string{ + "bar", + }, + }, + { + in: "foo=bar:first=one=1,two=2:second:third", + key: "second", + expect: nil, + }, + { + in: "foo=bar:first=one=1,two=2:second:third", + key: "third", + expect: nil, + }, + } + for i, tc := range tcs { + t.Run(fmt.Sprintf("%d %s", i, tc.in), func(t *testing.T) { + pbs := parseProfileConfigs(1<<10, tc.in) + pb, ok := pbs[tc.key] // no negative testing for missing keys (see newProfileConfigs) + require.True(t, ok) + require.NotNil(t, pb) // always not nil + require.Equal(t, 1<<10, pb.buf.Cap()) // bufsize is always 1024 + require.Equal(t, 0, pb.buf.Len()) + require.Equal(t, tc.expect, pb.flags) + }) + } +} + +func TestDebug_newProfileConfigs(t *testing.T) { + tcs := []struct { + in string + key string + expect string + ok bool + }{ + { + in: "foo=bar", + key: "foo", + ok: true, + expect: "bar", + }, + { + in: "foo=", + key: "foo", + ok: true, + expect: "", + }, + { + in: "", + key: "foo", + ok: false, + expect: "", + }, + { + in: "foo=bar", + key: "bar", + ok: false, + expect: "", + }, + } + for i, tc := range tcs { + t.Run(fmt.Sprintf("%d %s", i, tc.in), func(t *testing.T) { + pb := newProfileConfig(1<<10, tc.in) + require.NotNil(t, pb) // always not nil + require.Equal(t, pb.buf.Cap(), 1<<10) // bufsize is always 1024 + v, ok := pb.GetValue(tc.key) + require.Equal(t, tc.ok, ok) + require.Equal(t, tc.expect, v) + }) + } +} From 4057f4ea82b4994b36a3009feb6000d22cb03739 Mon Sep 17 00:00:00 2001 From: Aaron Alpar Date: Sat, 18 Nov 2023 08:39:25 -0800 Subject: [PATCH 2/8] commnets --- debug/debug.go | 119 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 33 deletions(-) diff --git a/debug/debug.go b/debug/debug.go index 88ac5f537f..c736ce1b7b 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -1,3 +1,4 @@ +// Package debug for debug helper functions. package debug import ( @@ -6,6 +7,7 @@ import ( "context" "encoding/pem" "errors" + "fmt" "io" "os" "runtime" @@ -14,16 +16,18 @@ import ( "strings" "sync" - errors2 "gopkg.in/errgo.v2/fmt/errors" - "github.com/kopia/kopia/repo/logging" ) var log = logging.Module("kopia/debug") -// ProfileName the name of the profile (see: runtime/pprof/Lookup) +// ProfileName the name of the profile (see: runtime/pprof/Lookup). type ProfileName string +const ( + pair = 2 +) + const ( // DefaultDebugProfileRate default sample/data fraction for profile sample collection rates (1/x, where x is the // data fraction sample rate). @@ -37,7 +41,7 @@ const ( EnvVarKopiaDebugPprof = "KOPIA_DEBUG_PPROF" ) -// flags used to configure profiling in EnvVarKopiaDebugPprof +// flags used to configure profiling in EnvVarKopiaDebugPprof. const ( // KopiaDebugFlagForceGc force garbage collection before dumping heap data. KopiaDebugFlagForceGc = "forcegc" @@ -48,9 +52,12 @@ const ( ) const ( + // ProfileNameBlock block profile key. ProfileNameBlock ProfileName = "block" - ProfileNameMutex = "mutex" - ProfileNameCpu = "cpu" + // ProfileNameMutex mutex profile key. + ProfileNameMutex = "mutex" + // ProfileNameCPU cpu profile key. + ProfileNameCPU = "cpu" ) // ProfileConfig configuration flags for a profile. @@ -65,15 +72,15 @@ type ProfileConfigs struct { pcm map[ProfileName]*ProfileConfig } -var ( - pprofConfigs = &ProfileConfigs{} -) +//nolint:gochecknoglobals +var pprofConfigs = &ProfileConfigs{} type pprofSetRate struct { fn func(int) def int } +//nolint:gochecknoglobals var pprofProfileRates = map[ProfileName]pprofSetRate{ ProfileNameBlock: { fn: func(x int) { runtime.SetBlockProfileRate(x) }, @@ -86,34 +93,41 @@ var pprofProfileRates = map[ProfileName]pprofSetRate{ } // GetValue get the value of the named flag, `s`. false will be returned -// if the flag value does not exist +// if the flag value does not exist. func (p ProfileConfig) GetValue(s string) (string, bool) { for _, f := range p.flags { - kvs := strings.SplitN(f, "=", 2) + kvs := strings.SplitN(f, "=", pair) if kvs[0] != s { continue } + if len(kvs) == 1 { return "", true } + return kvs[1], true } + return "", false } func parseProfileConfigs(bufSizeB int, ppconfigs string) map[ProfileName]*ProfileConfig { pbs := map[ProfileName]*ProfileConfig{} allProfileOptions := strings.Split(ppconfigs, ":") + for _, profileOptionWithFlags := range allProfileOptions { // of those, see if any have profile-specific settings - profileFlagNameValuePairs := strings.SplitN(profileOptionWithFlags, "=", 2) + profileFlagNameValuePairs := strings.SplitN(profileOptionWithFlags, "=", pair) flagValue := "" + if len(profileFlagNameValuePairs) > 1 { flagValue = profileFlagNameValuePairs[1] } + flagKey := ProfileName(strings.ToLower(profileFlagNameValuePairs[0])) pbs[flagKey] = newProfileConfig(bufSizeB, flagValue) } + return pbs } @@ -122,10 +136,12 @@ func newProfileConfig(bufSizeB int, ppconfig string) *ProfileConfig { q := &ProfileConfig{ buf: bytes.NewBuffer(make([]byte, 0, bufSizeB)), } + flgs := strings.Split(ppconfig, ",") if len(flgs) > 0 && flgs[0] != "" { // len(flgs) > 1 && flgs[0] == "" should never happen q.flags = flgs } + return q } @@ -136,22 +152,26 @@ func setupProfileFractions(ctx context.Context, profileBuffers map[ProfileName]* // profile not configured - leave it alone continue } + if v == nil { // profile configured, but no rate - set to default pprofset.fn(pprofset.def) continue } + s, _ := v.GetValue(KopiaDebugFlagRate) if s == "" { // flag without an argument - set to default pprofset.fn(pprofset.def) continue } + n1, err := strconv.Atoi(s) if err != nil { log(ctx).With("cause", err).Warnf("invalid PPROF rate, %q, for %s: %v", s, k) continue } + log(ctx).Debugf("setting PPROF rate, %d, for %s", n1, k) pprofset.fn(n1) } @@ -164,10 +184,12 @@ func ClearProfileFractions(profileBuffers map[ProfileName]*ProfileConfig) { if v == nil { // fold missing values and empty values continue } + _, ok := v.GetValue(KopiaDebugFlagRate) if !ok { // only care if a value might have been set before continue } + pprofset.fn(0) } } @@ -188,7 +210,7 @@ func StartProfileBuffers(ctx context.Context) { // look for matching services. "*" signals all services for profiling log(ctx).Debug("configuring profile buffers") - // aquire global lock when performing operations with global side-effects + // acquire global lock when performing operations with global side-effects pprofConfigs.mu.Lock() defer pprofConfigs.mu.Unlock() @@ -198,18 +220,21 @@ func StartProfileBuffers(ctx context.Context) { setupProfileFractions(ctx, pprofConfigs.pcm) // cpu has special initialization - v, ok := pprofConfigs.pcm[ProfileNameCpu] + v, ok := pprofConfigs.pcm[ProfileNameCPU] if ok { err := pprof.StartCPUProfile(v.buf) if err != nil { log(ctx).With("cause", err).Warn("cannot start cpu PPROF") - delete(pprofConfigs.pcm, ProfileNameCpu) + delete(pprofConfigs.pcm, ProfileNameCPU) } } } // DumpPem dump a PEM version of the byte slice, bs, into writer, wrt. func DumpPem(bs []byte, types string, wrt *os.File) error { + // err0 for background process + var err0 error + blk := &pem.Block{ Type: types, Bytes: bs, @@ -222,34 +247,50 @@ func DumpPem(bs []byte, types string, wrt *os.File) error { // fashion - this prevents the need for a large buffer to hold // the encoded PEM. go func() { + // writer close on exit of background process + //nolint:errcheck defer pw.Close() - err := pem.Encode(pw, blk) - if err != nil { + // do the encoding + err0 = pem.Encode(pw, blk) + if err0 != nil { return } }() + + // connect rdr to pipe reader rdr := bufio.NewReader(pr) - var err0, err1 error - for err0 == nil && err1 == nil { + + // err1 for reading + // err2 for writing + var err1, err2 error + for err1 == nil && err2 == nil { var ln []byte - ln, err0 = rdr.ReadBytes('\n') - _, err1 = wrt.Write(ln) + ln, err1 = rdr.ReadBytes('\n') + // err1 can return ln and non-nil err1, so always call write + _, err2 = wrt.Write(ln) } - // got a write error - if err1 != nil { - return err1 + + // got a write error. this has precedent + if err2 != nil { + return fmt.Errorf("could not write PEM: %w", err2) } + // did not get a read error. file ends in newline - if err0 == nil { + if err1 == nil { return nil } + // if file does not end in newline, then output one - if errors.Is(err0, io.EOF) { - _, err1 = wrt.WriteString("\n") - // TODO: lint says errors need to be wrapped ... figure out how its dealth with in rest of kopia - return errors2.Wrap(err0) + if errors.Is(err1, io.EOF) { + _, err2 = wrt.WriteString("\n") + if err2 != nil { + return fmt.Errorf("could not write PEM: %w", err2) + } + + return io.EOF } - return err0 + + return fmt.Errorf("error reading bytes: %w", err1) } func parseDebugNumber(v *ProfileConfig) (int, error) { @@ -257,15 +298,17 @@ func parseDebugNumber(v *ProfileConfig) (int, error) { if !ok { return 0, nil } + debug, err := strconv.Atoi(debugs) if err != nil { - return 0, err + return 0, fmt.Errorf("could not parse number %q: %w", debugs, err) } + return debug, nil } // StopProfileBuffers stop and dump the contents of the buffers to the log as PEMs. Buffers -// supplied here are from StartProfileBuffers +// supplied here are from StartProfileBuffers. func StopProfileBuffers(ctx context.Context) { pprofConfigs.mu.Lock() defer pprofConfigs.mu.Unlock() @@ -279,32 +322,40 @@ func StopProfileBuffers(ctx context.Context) { // cpu and heap profiles requires special handling for k, v := range pprofConfigs.pcm { log(ctx).Debugf("stopping PPROF profile %q", k) + if v == nil { continue } - if k == ProfileNameCpu { + + if k == ProfileNameCPU { pprof.StopCPUProfile() continue } + _, ok := v.GetValue(KopiaDebugFlagForceGc) if ok { log(ctx).Debug("performing GC before PPROF dump ...") runtime.GC() } + debug, err := parseDebugNumber(v) if err != nil { log(ctx).With("cause", err).Warn("invalid PPROF configuration debug number") continue } + pent := pprof.Lookup(string(k)) if pent == nil { log(ctx).Warnf("no system PPROF entry for %q", k) delete(pprofConfigs.pcm, k) + continue } + err = pent.WriteTo(v.buf, debug) if err != nil { log(ctx).With("cause", err).Warn("error writing PPROF buffer") + continue } } @@ -313,8 +364,10 @@ func StopProfileBuffers(ctx context.Context) { if v == nil { continue } + unm := strings.ToUpper(string(k)) log(ctx).Infof("dumping PEM for %q", unm) + err := DumpPem(v.buf.Bytes(), unm, os.Stderr) if err != nil { log(ctx).With("cause", err).Error("cannot write PEM") From 12be3590dd002fbfcf3985e7555fe160eca1c37a Mon Sep 17 00:00:00 2001 From: Aaron Alpar <55999015+aaron-kasten@users.noreply.github.com> Date: Wed, 22 Nov 2023 17:27:40 -0800 Subject: [PATCH 3/8] Update debug/debug.go Co-authored-by: Shikhar Mall --- debug/debug.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debug/debug.go b/debug/debug.go index c736ce1b7b..b616cca449 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -69,6 +69,8 @@ type ProfileConfig struct { // ProfileConfigs configuration flags for all requested profiles. type ProfileConfigs struct { mu sync.Mutex + + // +checklocks:mu pcm map[ProfileName]*ProfileConfig } From caef02e95b4ddc0f23a1e35a4b6756728d981409 Mon Sep 17 00:00:00 2001 From: Aaron Alpar <55999015+aaron-kasten@users.noreply.github.com> Date: Wed, 22 Nov 2023 17:27:54 -0800 Subject: [PATCH 4/8] Update debug/debug.go Co-authored-by: Shikhar Mall --- debug/debug.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/debug/debug.go b/debug/debug.go index b616cca449..413f35bb59 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -94,8 +94,9 @@ var pprofProfileRates = map[ProfileName]pprofSetRate{ }, } -// GetValue get the value of the named flag, `s`. false will be returned -// if the flag value does not exist. +// GetValue get the value of the named flag, `s`. False will be returned +// if the flag does not exist. True will be returned if flag exists without +// a value. func (p ProfileConfig) GetValue(s string) (string, bool) { for _, f := range p.flags { kvs := strings.SplitN(f, "=", pair) From 07c811faa74eb033533a640e33799486a4410222 Mon Sep 17 00:00:00 2001 From: Aaron Alpar <55999015+aaron-kasten@users.noreply.github.com> Date: Wed, 22 Nov 2023 17:28:07 -0800 Subject: [PATCH 5/8] Update debug/debug.go Co-authored-by: Shikhar Mall --- debug/debug.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug/debug.go b/debug/debug.go index 413f35bb59..04a8d6cb21 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -119,7 +119,7 @@ func parseProfileConfigs(bufSizeB int, ppconfigs string) map[ProfileName]*Profil allProfileOptions := strings.Split(ppconfigs, ":") for _, profileOptionWithFlags := range allProfileOptions { - // of those, see if any have profile-specific settings + // of those, see if any have profile specific settings profileFlagNameValuePairs := strings.SplitN(profileOptionWithFlags, "=", pair) flagValue := "" From 3447c4a8b4c4a1b6107e88bea700d2cf68f27d52 Mon Sep 17 00:00:00 2001 From: Aaron Alpar Date: Wed, 22 Nov 2023 17:38:28 -0800 Subject: [PATCH 6/8] fixups --- debug/debug.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/debug/debug.go b/debug/debug.go index c736ce1b7b..434861f5fe 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -76,19 +76,19 @@ type ProfileConfigs struct { var pprofConfigs = &ProfileConfigs{} type pprofSetRate struct { - fn func(int) - def int + setter func(int) + defaultValue int } //nolint:gochecknoglobals var pprofProfileRates = map[ProfileName]pprofSetRate{ ProfileNameBlock: { - fn: func(x int) { runtime.SetBlockProfileRate(x) }, - def: DefaultDebugProfileRate, + setter: func(x int) { runtime.SetBlockProfileRate(x) }, + defaultValue: DefaultDebugProfileRate, }, ProfileNameMutex: { - fn: func(x int) { runtime.SetMutexProfileFraction(x) }, - def: DefaultDebugProfileRate, + setter: func(x int) { runtime.SetMutexProfileFraction(x) }, + defaultValue: DefaultDebugProfileRate, }, } @@ -155,14 +155,14 @@ func setupProfileFractions(ctx context.Context, profileBuffers map[ProfileName]* if v == nil { // profile configured, but no rate - set to default - pprofset.fn(pprofset.def) + pprofset.setter(pprofset.defaultValue) continue } s, _ := v.GetValue(KopiaDebugFlagRate) if s == "" { // flag without an argument - set to default - pprofset.fn(pprofset.def) + pprofset.setter(pprofset.defaultValue) continue } @@ -173,12 +173,12 @@ func setupProfileFractions(ctx context.Context, profileBuffers map[ProfileName]* } log(ctx).Debugf("setting PPROF rate, %d, for %s", n1, k) - pprofset.fn(n1) + pprofset.setter(n1) } } -// ClearProfileFractions set the profile fractions to their zero values. -func ClearProfileFractions(profileBuffers map[ProfileName]*ProfileConfig) { +// clearProfileFractions set the profile fractions to their zero values. +func clearProfileFractions(profileBuffers map[ProfileName]*ProfileConfig) { for k, pprofset := range pprofProfileRates { v := profileBuffers[k] if v == nil { // fold missing values and empty values @@ -190,7 +190,7 @@ func ClearProfileFractions(profileBuffers map[ProfileName]*ProfileConfig) { continue } - pprofset.fn(0) + pprofset.setter(0) } } @@ -375,6 +375,6 @@ func StopProfileBuffers(ctx context.Context) { } // clear the profile rates and fractions to effectively stop profiling - ClearProfileFractions(pprofConfigs.pcm) + clearProfileFractions(pprofConfigs.pcm) pprofConfigs.pcm = map[ProfileName]*ProfileConfig{} } From de512f8a389e68ad3c1f2b52d1c4207488d45999 Mon Sep 17 00:00:00 2001 From: Aaron Alpar Date: Mon, 27 Nov 2023 14:55:15 -0800 Subject: [PATCH 7/8] fixup --- debug/debug.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug/debug.go b/debug/debug.go index 91963f10fc..22539f8040 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -68,7 +68,7 @@ type ProfileConfig struct { // ProfileConfigs configuration flags for all requested profiles. type ProfileConfigs struct { - mu sync.Mutex + mu sync.Mutex // +checklocks:mu pcm map[ProfileName]*ProfileConfig From d6bc21fd84aa40190bdd16a6bfc81cae98008ed2 Mon Sep 17 00:00:00 2001 From: Aaron Alpar Date: Mon, 27 Nov 2023 16:49:22 -0800 Subject: [PATCH 8/8] happy lint --- debug/debug.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/debug/debug.go b/debug/debug.go index 22539f8040..6c19c348c0 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -15,6 +15,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/kopia/kopia/repo/logging" ) @@ -26,6 +27,8 @@ type ProfileName string const ( pair = 2 + // PPROFDumpTimeout when dumping PPROF data, set an upper bound on the time it can take to log. + PPROFDumpTimeout = 15 * time.Second ) const (