-
Notifications
You must be signed in to change notification settings - Fork 175
/
profiler.go
137 lines (112 loc) · 3.06 KB
/
profiler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package debug
import (
"fmt"
"math/rand"
"os"
"path/filepath"
"runtime/pprof"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/onflow/flow-go/engine"
)
var profilerEnabled bool
// SetProfilerEnabled enable or disable generating profiler data
func SetProfilerEnabled(enabled bool) {
profilerEnabled = enabled
log.Info().Msgf("changed profilerEnabled to %v", enabled)
}
type AutoProfiler struct {
unit *engine.Unit
dir string // where we store profiles
log zerolog.Logger
interval time.Duration
duration time.Duration
}
func NewAutoProfiler(log zerolog.Logger, dir string, interval time.Duration, duration time.Duration, enabled bool) (*AutoProfiler, error) {
SetProfilerEnabled(enabled)
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
return nil, fmt.Errorf("could not create profile dir %v: %w", dir, err)
}
p := &AutoProfiler{
unit: engine.NewUnit(),
log: log.With().Str("component", "profiler").Logger(),
dir: dir,
interval: interval,
duration: duration,
}
return p, nil
}
func (p *AutoProfiler) Ready() <-chan struct{} {
delay := time.Duration(float64(p.interval) * rand.Float64())
p.unit.LaunchPeriodically(p.start, p.interval, delay)
if profilerEnabled {
p.log.Info().Msgf("AutoProfiler has started, the first profiler will be genereated at: %v", time.Now().Add(p.interval))
} else {
p.log.Info().Msg("AutoProfiler has started, profiler is disabled")
}
return p.unit.Ready()
}
func (p *AutoProfiler) Done() <-chan struct{} {
return p.unit.Done()
}
func (p *AutoProfiler) start() {
if !profilerEnabled {
return
}
p.log.Info().Msg("starting profile trace")
// write pprof trace files
p.pprof("heap")
p.pprof("goroutine")
p.pprof("block")
p.pprof("mutex")
p.cpu()
p.log.Info().Msg("finished profile trace")
}
func (p *AutoProfiler) pprof(profile string) {
path := filepath.Join(p.dir, fmt.Sprintf("%s-%s", profile, time.Now().Format(time.RFC3339)))
log := p.log.With().Str("file", path).Logger()
log.Debug().Msgf("capturing %s profile", profile)
f, err := os.Create(path)
if err != nil {
p.log.Error().Err(err).Msgf("failed to open %s file", profile)
return
}
defer func() {
err := f.Close()
if err != nil {
log.Error().Err(err).Msgf("failed to close %s file", profile)
}
}()
err = pprof.Lookup(profile).WriteTo(f, 0)
if err != nil {
p.log.Error().Err(err).Msgf("failed to write to %s file", profile)
}
}
func (p *AutoProfiler) cpu() {
path := filepath.Join(p.dir, fmt.Sprintf("cpu-%s", time.Now().Format(time.RFC3339)))
log := p.log.With().Str("file", path).Logger()
log.Debug().Msg("capturing cpu profile")
f, err := os.Create(path)
if err != nil {
p.log.Error().Err(err).Msg("failed to open cpu file")
return
}
defer func() {
err := f.Close()
if err != nil {
p.log.Error().Err(err).Msgf("failed to close CPU file")
}
}()
err = pprof.StartCPUProfile(f)
if err != nil {
p.log.Error().Err(err).Msg("failed to start CPU profile")
return
}
defer pprof.StopCPUProfile()
select {
case <-time.After(p.duration):
case <-p.unit.Quit():
}
}