1
1
package lisp
2
2
3
- import (
4
- "errors"
5
- "fmt"
6
- "github.com/luthersystems/elps/parser/token"
7
- "os"
8
- "regexp"
9
- "runtime"
10
- "sync"
11
- "time"
12
- )
13
-
14
-
15
3
const ElpsVersion = "1.7"
16
- var builtinRegex = regexp .MustCompile ("\\ <(?:builtin|special)-[a-z]+ \\ `\\ `(.*)\\ '\\ '\\ >" )
17
4
18
5
// Interface for a profiler
19
6
type Profiler interface {
@@ -31,237 +18,3 @@ type Profiler interface {
31
18
End (function * LVal )
32
19
}
33
20
34
- // A profiler implementation that builds Callgrind files.
35
- // Heavily influenced by how XDebug works for PHP. Not because
36
- // I like PHP but because XDebug is very light
37
- // The resulting files can be opened in KCacheGrind or QCacheGrind.
38
- type profiler struct {
39
- sync.Mutex
40
- writer * os.File
41
- runtime * Runtime
42
- enabled bool
43
- startTime time.Time
44
- refs map [string ]int
45
- sourceCache map [int ]* token.Location
46
- refCounter int
47
- callRefs map [int32 ]* callRef
48
- }
49
-
50
- // Returns a new Callgrind processor
51
- func NewProfiler (runtime * Runtime ) Profiler {
52
- p := new (profiler )
53
- p .runtime = runtime
54
- runtime .Profiler = p
55
- return p
56
- }
57
-
58
- // Represents something that got called
59
- type callRef struct {
60
- start time.Time
61
- prev * callRef
62
- name string
63
- children []* callRef
64
- duration time.Duration
65
- file string
66
- line int
67
- }
68
-
69
- func (p * profiler ) IsEnabled () bool {
70
- return p .enabled
71
- }
72
-
73
- func (p * profiler ) Enable () error {
74
- p .Lock ()
75
- defer p .Unlock ()
76
- if p .enabled {
77
- return errors .New ("Profiler already enabled" )
78
- }
79
- if p .writer == nil {
80
- return errors .New ("No output set in profiler" )
81
- }
82
- fmt .Fprintf (p .writer , "version: 1\n creator: elps %s (Go %s)\n " , ElpsVersion , runtime .Version ());
83
- fmt .Fprintf (p .writer , "cmd: %s\n part: 1\n positions: line\n \n " , "TODO" );
84
- fmt .Fprintf (p .writer , "events: Time_(ns) Memory_(bytes)\n \n " );
85
- p .callRefs = make (map [int32 ]* callRef )
86
- p .startTime = time .Now ()
87
- p .refs = make (map [string ]int )
88
- p .refCounter = 0
89
- p .enabled = true
90
- return nil
91
- }
92
-
93
- func (p * profiler ) SetFile (filename string ) error {
94
- p .Lock ()
95
- defer p .Unlock ()
96
- if p .enabled {
97
- return errors .New ("Profiler already enabled" )
98
- }
99
- pointer , err := os .Create (filename )
100
- if err != nil {
101
- return err
102
- }
103
- p .writer = pointer
104
- return nil
105
- }
106
-
107
- func (p * profiler ) Complete () error {
108
- p .Lock ()
109
- defer p .Unlock ()
110
- duration := time .Now ().Sub (p .startTime )
111
- ms := & runtime.MemStats {}
112
- runtime .ReadMemStats (ms )
113
- fmt .Fprintf (p .writer , "summary %d %d\n \n " , duration .Nanoseconds (), ms .TotalAlloc )
114
- return p .writer .Close ()
115
- }
116
-
117
- func (p * profiler ) getRef (name string ) string {
118
- if ref , ok := p .refs [name ]; ok {
119
- return fmt .Sprintf ("(%d)" , ref )
120
- }
121
- p .refCounter ++
122
- p .refs [name ] = p .refCounter
123
- return fmt .Sprintf ("(%d) %s" , p .refCounter , name )
124
- }
125
-
126
- func (p * profiler ) Start (function * LVal ) {
127
- if ! p .enabled {
128
- return
129
- }
130
- switch function .Type {
131
- case LInt , LString , LFloat , LBytes , LError , LArray , LQuote , LNative , LQSymbol , LSortMap :
132
- // We don't need to profile these types. We could, but we're not that LISP :D
133
- return
134
- case LFun , LSymbol , LSExpr :
135
- // Mark the time and point of entry. It feels like we're building the call stack in Runtime
136
- // again, but we're not - it's called, not callers.
137
- _ , _ , name := p .getFunctionParameters (function )
138
- p .incrementCallRef (name , function .Source )
139
- default :
140
- panic (fmt .Sprintf ("Missing type %d" , function .Type ))
141
- }
142
- }
143
-
144
- // Generates a call ref so the same item can be located again
145
- func (p * profiler ) incrementCallRef (name string , loc * token.Location ) * callRef {
146
- p .Lock ()
147
- defer p .Unlock ()
148
- var thread * int32
149
- thread = & ([]int32 {1 }[0 ]);
150
- frameRef := new (callRef )
151
- frameRef .name = name
152
- frameRef .children = make ([]* callRef , 0 )
153
- if loc != nil {
154
- frameRef .file = loc .File
155
- frameRef .line = loc .Line
156
- }
157
- if current , ok := p .callRefs [* thread ]; ok && current != nil {
158
- frameRef .prev = current
159
- frameRef .prev .children = append (frameRef .prev .children , frameRef )
160
- }
161
- frameRef .start = time .Now ()
162
- p .callRefs [* thread ] = frameRef
163
- return frameRef
164
- }
165
-
166
- // Finds a call ref for the current scope
167
- func (p * profiler ) getCallRefAndDecrement () * callRef {
168
- var thread * int32
169
- //C.GetGoId(thread)
170
- thread = & ([]int32 {1 }[0 ]);
171
- if current , ok := p .callRefs [* thread ]; ok {
172
- p .callRefs [* thread ] = current .prev
173
- return current
174
- }
175
- panic (fmt .Sprintf ("Unset thread ref %d" , * thread ))
176
- }
177
-
178
- // Gets a canonical version of the function name suitable for viewing in KCacheGrind
179
- func (p * profiler ) getFunNameFromFID (in string ) string {
180
- // Most of the time we can just look this up in FunNames
181
- if name , ok := p .runtime .Package .FunNames [in ]; ok {
182
- return name
183
- }
184
- // but sometimes something doesn't match - so we'll try to regexp it out
185
- if ! builtinRegex .MatchString (in ) {
186
- return in
187
- }
188
- return builtinRegex .FindStringSubmatch (in )[1 ]
189
- }
190
-
191
- // Gets file parameters from the LVal
192
- func (p * profiler ) getFunctionParameters (function * LVal ) (string , int , string ) {
193
- var source string
194
- line := 0
195
- if function .Source == nil {
196
- if cell := function .Cells [0 ]; cell != nil && cell .Source != nil {
197
- source = cell .Source .File
198
- line = cell .Source .Line
199
- } else {
200
- source = "no-source"
201
- }
202
- } else {
203
- source = function .Source .File
204
- line = function .Source .Line
205
- }
206
- fName := fmt .Sprintf ("%s:%s" , function .FunData ().Package , p .getFunNameFromFID (function .FunData ().FID ))
207
- return source , line , fName
208
- }
209
-
210
- func (p * profiler ) End (function * LVal ) {
211
- if ! p .enabled {
212
- return
213
- }
214
- p .Lock ()
215
- defer p .Unlock ()
216
- switch function .Type {
217
- case LInt , LString , LFloat , LBytes , LError , LArray , LQuote , LNative , LQSymbol , LSortMap :
218
- // Again, we can ignore these as we never put them on the stack
219
- return
220
- case LFun , LSymbol , LSExpr :
221
- source , line , fName := p .getFunctionParameters (function )
222
- // Write what function we've been observing and where to find it
223
- fmt .Fprintf (p .writer , "fl=%s\n " , p .getRef (source ))
224
- fmt .Fprintf (p .writer , "fn=%s\n " , p .getRef (fName ))
225
- // TODO track memory
226
- memory := 0
227
- ref := p .getCallRefAndDecrement ()
228
- ref .duration = time .Since (ref .start )
229
- if ref .duration == 0 {
230
- ref .duration = 1
231
- }
232
- // Cache the location - we won't be able to get it again when we build the maps for
233
- // things that call this.
234
- if ref .line == 0 && function .Source != nil {
235
- ref .line = function .Source .Line
236
- ref .file = function .Source .File
237
- }
238
- // Output timing and line ref
239
- fmt .Fprintf (p .writer , "%d %d %d\n " , line , ref .duration , memory )
240
- // Output the things we called
241
- for _ , entry := range ref .children {
242
- fmt .Fprintf (p .writer , "cfl=%s\n " , p .getRef (entry .file ))
243
- fmt .Fprintf (p .writer , "cfn=%s\n " , p .getRef (entry .name ))
244
- fmt .Fprint (p .writer , "calls=1 0 0\n " )
245
- fmt .Fprintf (p .writer , "%d %d %d\n " , entry .line , entry .duration , memory )
246
- }
247
- // and end the entry
248
- fmt .Fprint (p .writer , "\n " )
249
- // if we're a top level caller we need to generate a file entry
250
- if len (p .runtime .Stack .Frames ) == 0 {
251
- // just show the file
252
- fmt .Fprintf (p .writer , "fl=%s\n " , p .getRef (source ))
253
- fmt .Fprintf (p .writer , "%d %d %d\n " , line , ref .duration , memory )
254
- // and call ourselves
255
- fmt .Fprintf (p .writer , "cfl=%s\n " , p .getRef (source ))
256
- fmt .Fprintf (p .writer , "cfn=%s\n " , p .getRef (fName ))
257
- fmt .Fprintf (p .writer , "%d %d %d\n " , line , ref .duration , memory )
258
- fmt .Fprint (p .writer , "calls=1 0 0\n " )
259
- // and end the entry
260
- fmt .Fprint (p .writer , "\n " )
261
- }
262
- default :
263
- panic (fmt .Sprintf ("Missing type %d" , function .Type ))
264
- }
265
- }
266
-
267
-
0 commit comments