From 949cd9f2aeb1df60196d5224ad78e82eee94184c Mon Sep 17 00:00:00 2001 From: Matthew Jaffee Date: Mon, 9 Oct 2023 11:08:42 -0500 Subject: [PATCH] compute actual sample rate for profile export (#26) In cases where due to either an excessive number of goroutines, or excessive load on the system, we are not able to sample at the given rate, we can compute the actual rate we were able to sample and use that when exporting the profile so that timing information in the profile is more accurate and sensible. With this code in place, a function which is on the stack for the entirety of a 27 second profile, will always display 27s when viewed, whereas previously it could display less time. --- fgprof.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/fgprof.go b/fgprof.go index 18f7d6f..e4af2e4 100644 --- a/fgprof.go +++ b/fgprof.go @@ -6,6 +6,7 @@ package fgprof import ( "fmt" "io" + "math" "runtime" "sort" "strings" @@ -37,16 +38,19 @@ func Start(w io.Writer, format Format) func() error { const hz = 99 ticker := time.NewTicker(time.Second / hz) stopCh := make(chan struct{}) - prof := &profiler{} profile := newWallclockProfile() + var sampleCount int64 + go func() { defer ticker.Stop() for { select { case <-ticker.C: + sampleCount++ + stacks := prof.GoroutineProfile() profile.Add(stacks) case <-stopCh: @@ -59,7 +63,14 @@ func Start(w io.Writer, format Format) func() error { stopCh <- struct{}{} endTime := time.Now() profile.Ignore(prof.SelfFrames()...) - return profile.Export(w, format, hz, startTime, endTime) + + // Compute actual sample rate in case, due to performance issues, we + // were not actually able to sample at the given hz. Converting + // everything to float avoids integers being rounded in the wrong + // direction and improves the correctness of times in profiles. + duration := endTime.Sub(startTime) + actualHz := float64(sampleCount) / (float64(duration) / 1e9) + return profile.Export(w, format, int(math.Round(actualHz)), startTime, endTime) } }