-
Notifications
You must be signed in to change notification settings - Fork 347
/
profile.go
203 lines (171 loc) · 5.27 KB
/
profile.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// Package profile provides a simple way to generate pprof compatible gnark circuit profile.
//
// Since the gnark frontend compiler is not thread safe and operates in a single go-routine,
// this package is also NOT thread safe and is meant to be called in the same go-routine.
package profile
import (
"bytes"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"sync/atomic"
"github.com/consensys/gnark/logger"
"github.com/consensys/gnark/profile/internal/report"
"github.com/google/pprof/profile"
)
var (
sessions []*Profile // active sessions
activeSessions uint32
)
// Profile represents an active constraint system profiling session.
type Profile struct {
// defaults to ./gnark.pprof
// if blank, profiile is not written to disk
filePath string
// actual pprof profile struct
// details on pprof format: https://github.com/google/pprof/blob/main/proto/README.md
pprof profile.Profile
functions map[string]*profile.Function
locations map[uint64]*profile.Location
onceSetName sync.Once
chDone chan struct{}
}
// Option defines configuration Options for Profile.
type Option func(*Profile)
// WithPath controls the profile destination file. If blank, profile is not written.
//
// Defaults to ./gnark.pprof.
func WithPath(path string) Option {
return func(p *Profile) {
p.filePath = path
}
}
// WithNoOutput indicates that the profile is not going to be written to disk.
//
// This is equivalent to WithPath("")
func WithNoOutput() Option {
return func(p *Profile) {
p.filePath = ""
}
}
// Start creates a new active profiling session. When Stop() is called, this session is removed from
// active profiling sessions and may be serialized to disk as a pprof compatible file (see ProfilePath option).
//
// All calls to profile.Start() and Stop() are meant to be executed in the same go routine (frontend.Compile).
//
// It is allowed to create multiple overlapping profiling sessions in one circuit.
func Start(options ...Option) *Profile {
// start the worker first time a profiling session starts.
onceInit.Do(func() {
go worker()
})
p := Profile{
functions: make(map[string]*profile.Function),
locations: make(map[uint64]*profile.Location),
filePath: filepath.Join(".", "gnark.pprof"),
chDone: make(chan struct{}),
}
p.pprof.SampleType = []*profile.ValueType{{
Type: "constraints",
Unit: "count",
}}
for _, option := range options {
option(&p)
}
log := logger.Logger()
if p.filePath == "" {
log.Warn().Msg("gnark profiling enabled [not writing to disk]")
} else {
log.Info().Str("path", p.filePath).Msg("gnark profiling enabled")
}
// add the session to active sessions
chCommands <- command{p: &p}
atomic.AddUint32(&activeSessions, 1)
return &p
}
// Stop removes the profile from active session and may write the pprof file to disk. See ProfilePath option.
func (p *Profile) Stop() {
log := logger.Logger()
if p.chDone == nil {
log.Fatal().Msg("gnark profile stopped multiple times")
}
// ask worker routine to remove ourselves from the active sessions
chCommands <- command{p: p, remove: true}
// wait for worker routine to remove us.
<-p.chDone
p.chDone = nil
// if filePath is set, serialize profile to disk in pprof format
if p.filePath != "" {
f, err := os.Create(p.filePath)
if err != nil {
log.Fatal().Err(err).Msg("could not create gnark profile")
}
if err := p.pprof.Write(f); err != nil {
log.Error().Err(err).Msg("writing profile")
}
f.Close()
log.Info().Str("path", p.filePath).Msg("gnark profiling disabled")
} else {
log.Warn().Msg("gnark profiling disabled [not writing to disk]")
}
}
// NbConstraints return number of collected samples (constraints) by the profile session
func (p *Profile) NbConstraints() int {
return len(p.pprof.Sample)
}
// Top return a similar output than pprof top command
func (p *Profile) Top() string {
r := report.NewDefault(&p.pprof, report.Options{
OutputFormat: report.Tree,
CompactLabels: true,
NodeFraction: 0.005,
EdgeFraction: 0.001,
SampleValue: func(v []int64) int64 { return v[0] },
SampleUnit: "count",
})
var buf bytes.Buffer
report.Generate(&buf, r)
return buf.String()
}
// RecordConstraint add a sample (with count == 1) to all the active profiling sessions.
func RecordConstraint() {
if n := atomic.LoadUint32(&activeSessions); n == 0 {
return // do nothing, no active session.
}
// collect the stack and send it async to the worker
pc := make([]uintptr, 20)
n := runtime.Callers(3, pc)
if n == 0 {
return
}
pc = pc[:n]
chCommands <- command{pc: pc}
}
func (p *Profile) getLocation(frame *runtime.Frame) *profile.Location {
l, ok := p.locations[uint64(frame.PC)]
if !ok {
// first let's see if we have the function.
f, ok := p.functions[frame.File+frame.Function]
if !ok {
fe := strings.Split(frame.Function, "/")
fName := fe[len(fe)-1]
f = &profile.Function{
ID: uint64(len(p.functions) + 1),
Name: fName,
SystemName: frame.Function,
Filename: frame.File,
}
p.functions[frame.File+frame.Function] = f
p.pprof.Function = append(p.pprof.Function, f)
}
l = &profile.Location{
ID: uint64(len(p.locations) + 1),
Line: []profile.Line{{Function: f, Line: int64(frame.Line)}},
}
p.locations[uint64(frame.PC)] = l
p.pprof.Location = append(p.pprof.Location, l)
}
return l
}