statprof: call-data is a record type
[bpt/guile.git] / module / statprof.scm
CommitLineData
47f3ce52
AW
1;;;; (statprof) -- a statistical profiler for Guile
2;;;; -*-scheme-*-
3;;;;
998f8494 4;;;; Copyright (C) 2009, 2010, 2011, 2013, 2014 Free Software Foundation, Inc.
47f3ce52
AW
5;;;; Copyright (C) 2004, 2009 Andy Wingo <wingo at pobox dot com>
6;;;; Copyright (C) 2001 Rob Browning <rlb at defaultvalue dot org>
7;;;;
8;;;; This library is free software; you can redistribute it and/or
9;;;; modify it under the terms of the GNU Lesser General Public
10;;;; License as published by the Free Software Foundation; either
11;;;; version 3 of the License, or (at your option) any later version.
12;;;;
13;;;; This library is distributed in the hope that it will be useful,
14;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16;;;; Lesser General Public License for more details.
17;;;;
18;;;; You should have received a copy of the GNU Lesser General Public
19;;;; License along with this library; if not, write to the Free Software
20;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21;;;;
22\f
23
24;;; Commentary:
998f8494 25;;;
62fd93e2 26;;; @code{(statprof)} is a statistical profiler for Guile.
998f8494
AW
27;;;
28;;; A simple use of statprof would look like this:
29;;;
30;;; @example
31;;; (statprof-reset 0 50000 #t)
32;;; (statprof-start)
33;;; (do-something)
34;;; (statprof-stop)
35;;; (statprof-display)
36;;; @end example
37;;;
38;;; This would reset statprof, clearing all accumulated statistics, then
39;;; start profiling, run some code, stop profiling, and finally display a
40;;; gprof flat-style table of statistics which will look something like
41;;; this:
42;;;
43;;; @example
44;;; % cumulative self self total
45;;; time seconds seconds calls ms/call ms/call name
46;;; 35.29 0.23 0.23 2002 0.11 0.11 -
47;;; 23.53 0.15 0.15 2001 0.08 0.08 positive?
48;;; 23.53 0.15 0.15 2000 0.08 0.08 +
49;;; 11.76 0.23 0.08 2000 0.04 0.11 do-nothing
50;;; 5.88 0.64 0.04 2001 0.02 0.32 loop
51;;; 0.00 0.15 0.00 1 0.00 150.59 do-something
52;;; ...
53;;; @end example
54;;;
55;;; All of the numerical data with the exception of the calls column is
56;;; statistically approximate. In the following column descriptions, and
57;;; in all of statprof, "time" refers to execution time (both user and
58;;; system), not wall clock time.
59;;;
60;;; @table @asis
61;;; @item % time
62;;; The percent of the time spent inside the procedure itself
63;;; (not counting children).
64;;; @item cumulative seconds
65;;; The total number of seconds spent in the procedure, including
66;;; children.
67;;; @item self seconds
68;;; The total number of seconds spent in the procedure itself (not counting
69;;; children).
70;;; @item calls
71;;; The total number of times the procedure was called.
72;;; @item self ms/call
73;;; The average time taken by the procedure itself on each call, in ms.
74;;; @item total ms/call
75;;; The average time taken by each call to the procedure, including time
76;;; spent in child functions.
77;;; @item name
78;;; The name of the procedure.
79;;; @end table
80;;;
81;;; The profiler uses @code{eq?} and the procedure object itself to
82;;; identify the procedures, so it won't confuse different procedures with
83;;; the same name. They will show up as two different rows in the output.
84;;;
85;;; Right now the profiler is quite simplistic. I cannot provide
86;;; call-graphs or other higher level information. What you see in the
87;;; table is pretty much all there is. Patches are welcome :-)
88;;;
89;;; @section Implementation notes
90;;;
91;;; The profiler works by setting the unix profiling signal
92;;; @code{ITIMER_PROF} to go off after the interval you define in the call
93;;; to @code{statprof-reset}. When the signal fires, a sampling routine is
94;;; run which looks at the current procedure that's executing, and then
95;;; crawls up the stack, and for each procedure encountered, increments
96;;; that procedure's sample count. Note that if a procedure is encountered
97;;; multiple times on a given stack, it is only counted once. After the
98;;; sampling is complete, the profiler resets profiling timer to fire
99;;; again after the appropriate interval.
100;;;
101;;; Meanwhile, the profiler keeps track, via @code{get-internal-run-time},
102;;; how much CPU time (system and user -- which is also what
103;;; @code{ITIMER_PROF} tracks), has elapsed while code has been executing
104;;; within a statprof-start/stop block.
105;;;
106;;; The profiler also tries to avoid counting or timing its own code as
107;;; much as possible.
108;;;
47f3ce52
AW
109;;; Code:
110
47f3ce52
AW
111(define-module (statprof)
112 #:use-module (srfi srfi-1)
62fd93e2 113 #:use-module (srfi srfi-9)
47f3ce52 114 #:autoload (ice-9 format) (format)
e1138ba1
AW
115 #:use-module (system vm vm)
116 #:use-module (system vm frame)
117 #:use-module (system vm program)
47f3ce52
AW
118 #:export (statprof-active?
119 statprof-start
120 statprof-stop
121 statprof-reset
122
123 statprof-accumulated-time
124 statprof-sample-count
125 statprof-fold-call-data
126 statprof-proc-call-data
127 statprof-call-data-name
128 statprof-call-data-calls
129 statprof-call-data-cum-samples
130 statprof-call-data-self-samples
131 statprof-call-data->stats
132
133 statprof-stats-proc-name
134 statprof-stats-%-time-in-proc
135 statprof-stats-cum-secs-in-proc
136 statprof-stats-self-secs-in-proc
137 statprof-stats-calls
138 statprof-stats-self-secs-per-call
139 statprof-stats-cum-secs-per-call
140
141 statprof-display
142 statprof-display-anomolies
143
144 statprof-fetch-stacks
145 statprof-fetch-call-tree
146
e1138ba1 147 statprof
2d239a78
AW
148 with-statprof
149
150 gcprof))
47f3ce52
AW
151
152
153;; This profiler tracks two numbers for every function called while
154;; it's active. It tracks the total number of calls, and the number
155;; of times the function was active when the sampler fired.
156;;
157;; Globally the profiler tracks the total time elapsed and the number
158;; of times the sampler was fired.
159;;
160;; Right now, this profiler is not per-thread and is not thread safe.
161
62fd93e2
AW
162(define-record-type <state>
163 (make-state accumulated-time last-start-time sample-count
164 sampling-frequency remaining-prof-time profile-level
165 count-calls? gc-time-taken record-full-stacks?
56bfce7c 166 stacks procedure-data inside-profiler?)
62fd93e2
AW
167 state?
168 ;; Total time so far.
169 (accumulated-time accumulated-time set-accumulated-time!)
170 ;; Start-time when timer is active.
171 (last-start-time last-start-time set-last-start-time!)
172 ;; Total count of sampler calls.
173 (sample-count sample-count set-sample-count!)
174 ;; (seconds . microseconds)
175 (sampling-frequency sampling-frequency set-sampling-frequency!)
176 ;; Time remaining when prof suspended.
177 (remaining-prof-time remaining-prof-time set-remaining-prof-time!)
178 ;; For user start/stop nesting.
179 (profile-level profile-level set-profile-level!)
180 ;; Whether to catch apply-frame.
181 (count-calls? count-calls? set-count-calls?!)
182 ;; GC time between statprof-start and statprof-stop.
183 (gc-time-taken gc-time-taken set-gc-time-taken!)
184 ;; If #t, stash away the stacks for future analysis.
185 (record-full-stacks? record-full-stacks? set-record-full-stacks?!)
186 ;; If record-full-stacks?, the stashed full stacks.
187 (stacks stacks set-stacks!)
188 ;; A hash where the key is the function object itself and the value is
189 ;; the data. The data will be a vector like this:
190 ;; #(name call-count cum-sample-count self-sample-count)
56bfce7c
AW
191 (procedure-data procedure-data set-procedure-data!)
192 ;; True if we are inside the profiler.
193 (inside-profiler? inside-profiler? set-inside-profiler?!))
62fd93e2
AW
194
195(define profiler-state (make-parameter #f))
196
4eb1fb9b
AW
197(define* (fresh-profiler-state #:key (count-calls? #f)
198 (sampling-frequency '(0 . 10000))
199 (full-stacks? #f))
200 (make-state 0.0 #f 0 sampling-frequency #f 0 count-calls? 0.0 #f '()
201 (make-hash-table) #f))
202
62fd93e2
AW
203(define (ensure-profiler-state)
204 (or (profiler-state)
4eb1fb9b 205 (let ((state (fresh-profiler-state)))
62fd93e2
AW
206 (profiler-state state)
207 state)))
47f3ce52 208
45a7de82
AW
209(define (existing-profiler-state)
210 (or (profiler-state)
211 (error "expected there to be a profiler state")))
212
e70a42d4
AW
213(define-record-type call-data
214 (make-call-data proc call-count cum-sample-count self-sample-count)
215 call-data?
216 (proc call-data-proc)
217 (call-count call-data-call-count set-call-data-call-count!)
218 (cum-sample-count call-data-cum-sample-count set-call-data-cum-sample-count!)
219 (self-sample-count call-data-self-sample-count set-call-data-self-sample-count!))
220
c165c50d
AW
221(define (call-data-name cd) (procedure-name (call-data-proc cd)))
222(define (call-data-printable cd)
223 (or (call-data-name cd)
224 (with-output-to-string (lambda () (write (call-data-proc cd))))))
47f3ce52 225
47f3ce52 226(define (inc-call-data-call-count! cd)
e70a42d4 227 (set-call-data-call-count! cd (1+ (call-data-call-count cd))))
47f3ce52 228(define (inc-call-data-cum-sample-count! cd)
e70a42d4 229 (set-call-data-cum-sample-count! cd (1+ (call-data-cum-sample-count cd))))
47f3ce52 230(define (inc-call-data-self-sample-count! cd)
e70a42d4 231 (set-call-data-self-sample-count! cd (1+ (call-data-self-sample-count cd))))
47f3ce52 232
62fd93e2
AW
233(define (accumulate-time state stop-time)
234 (set-accumulated-time! state
235 (+ (accumulated-time state)
62fd93e2 236 (- stop-time (last-start-time state)))))
47f3ce52
AW
237
238(define (get-call-data proc)
62fd93e2 239 (define state (ensure-profiler-state))
0bd6b1ca 240 (let ((k (cond
d1100525 241 ((program? proc) (program-code proc))
0bd6b1ca 242 (else proc))))
62fd93e2 243 (or (hashv-ref (procedure-data state) k)
663212bb 244 (let ((call-data (make-call-data proc 0 0 0)))
62fd93e2 245 (hashv-set! (procedure-data state) k call-data)
663212bb 246 call-data))))
47f3ce52
AW
247
248;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
249;; SIGPROF handler
250
d20dd74e
AW
251;; FIXME: Instead of this messing about with hash tables and
252;; frame-procedure, just record the stack of return addresses into a
253;; growable vector, and resolve them to procedures when analyzing
254;; instead of at collection time.
255;;
47f3ce52
AW
256(define (sample-stack-procs stack)
257 (let ((stacklen (stack-length stack))
62fd93e2 258 (hit-count-call? #f)
45a7de82 259 (state (existing-profiler-state)))
47f3ce52 260
cad444e3
AW
261 (when (record-full-stacks? state)
262 (set-stacks! state (cons stack (stacks state))))
47f3ce52 263
62fd93e2 264 (set-sample-count! state (+ (sample-count state) 1))
47f3ce52
AW
265 ;; Now accumulate stats for the whole stack.
266 (let loop ((frame (stack-ref stack 0))
267 (procs-seen (make-hash-table 13))
268 (self #f))
269 (cond
270 ((not frame)
271 (hash-fold
272 (lambda (proc val accum)
273 (inc-call-data-cum-sample-count!
274 (get-call-data proc)))
275 #f
276 procs-seen)
277 (and=> (and=> self get-call-data)
278 inc-call-data-self-sample-count!))
279 ((frame-procedure frame)
280 => (lambda (proc)
281 (cond
282 ((eq? proc count-call)
283 ;; We're not supposed to be sampling count-call and
284 ;; its sub-functions, so loop again with a clean
285 ;; slate.
286 (set! hit-count-call? #t)
287 (loop (frame-previous frame) (make-hash-table 13) #f))
c165c50d 288 (else
47f3ce52
AW
289 (hashq-set! procs-seen proc #t)
290 (loop (frame-previous frame)
291 procs-seen
c165c50d 292 (or self proc))))))
47f3ce52
AW
293 (else
294 (loop (frame-previous frame) procs-seen self))))
295 hit-count-call?))
296
47f3ce52 297(define (profile-signal-handler sig)
45a7de82 298 (define state (existing-profiler-state))
62fd93e2 299
56bfce7c 300 (set-inside-profiler?! state #t)
47f3ce52
AW
301
302 ;; FIXME: with-statprof should be able to set an outer frame for the
303 ;; stack cut
cad444e3
AW
304 (when (positive? (profile-level state))
305 (let* ((stop-time (get-internal-run-time))
306 ;; cut down to the signal handler. note that this will only
307 ;; work if statprof.scm is compiled; otherwise we get
308 ;; `eval' on the stack instead, because if it's not
309 ;; compiled, profile-signal-handler is a thunk that
310 ;; tail-calls eval. perhaps we should always compile the
311 ;; signal handler instead...
312 (stack (or (make-stack #t profile-signal-handler)
313 (pk 'what! (make-stack #t))))
314 (inside-apply-trap? (sample-stack-procs stack)))
315
316 (unless inside-apply-trap?
317 ;; disabling here is just a little more efficient, but
318 ;; not necessary given inside-profiler?. We can't just
319 ;; disable unconditionally at the top of this function
320 ;; and eliminate inside-profiler? because it seems to
321 ;; confuse guile wrt re-enabling the trap when
322 ;; count-call finishes.
323 (when (count-calls? state)
324 (set-vm-trace-level! (1- (vm-trace-level))))
325 (accumulate-time state stop-time))
47f3ce52 326
cad444e3
AW
327 (setitimer ITIMER_PROF
328 0 0
329 (car (sampling-frequency state))
330 (cdr (sampling-frequency state)))
47f3ce52 331
cad444e3
AW
332 (unless inside-apply-trap?
333 (set-last-start-time! state (get-internal-run-time))
334 (when (count-calls? state)
335 (set-vm-trace-level! (1+ (vm-trace-level)))))))
e1138ba1 336
56bfce7c 337 (set-inside-profiler?! state #f))
47f3ce52
AW
338
339;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
340;; Count total calls.
341
e1138ba1 342(define (count-call frame)
45a7de82 343 (define state (existing-profiler-state))
62fd93e2 344
cad444e3
AW
345 (unless (inside-profiler? state)
346 (accumulate-time state (get-internal-run-time))
47f3ce52 347
cad444e3
AW
348 (and=> (frame-procedure frame)
349 (lambda (proc)
350 (inc-call-data-call-count!
351 (get-call-data proc))))
47f3ce52 352
cad444e3 353 (set-last-start-time! state (get-internal-run-time))))
47f3ce52
AW
354
355;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
356
357(define (statprof-active?)
358 "Returns @code{#t} if @code{statprof-start} has been called more times
359than @code{statprof-stop}, @code{#f} otherwise."
45a7de82
AW
360 (define state (profiler-state))
361 (and state (positive? (profile-level state))))
47f3ce52
AW
362
363;; Do not call this from statprof internal functions -- user only.
364(define (statprof-start)
365 "Start the profiler.@code{}"
366 ;; After some head-scratching, I don't *think* I need to mask/unmask
367 ;; signals here, but if I'm wrong, please let me know.
62fd93e2
AW
368 (define state (ensure-profiler-state))
369 (set-profile-level! state (+ (profile-level state) 1))
cad444e3
AW
370 (when (= (profile-level state) 1)
371 (let* ((rpt (remaining-prof-time state))
372 (use-rpt? (and rpt
373 (or (positive? (car rpt))
374 (positive? (cdr rpt))))))
375 (set-remaining-prof-time! state #f)
376 ;; FIXME: Use per-thread run time.
377 (set-last-start-time! state (get-internal-run-time))
378 (set-gc-time-taken! state
379 (cdr (assq 'gc-time-taken (gc-stats))))
380 (if use-rpt?
381 (setitimer ITIMER_PROF 0 0 (car rpt) (cdr rpt))
382 (setitimer ITIMER_PROF
383 0 0
384 (car (sampling-frequency state))
385 (cdr (sampling-frequency state))))
386 (when (count-calls? state)
387 (add-hook! (vm-apply-hook) count-call))
388 (set-vm-trace-level! (1+ (vm-trace-level)))
389 #t)))
47f3ce52
AW
390
391;; Do not call this from statprof internal functions -- user only.
392(define (statprof-stop)
393 "Stop the profiler.@code{}"
394 ;; After some head-scratching, I don't *think* I need to mask/unmask
395 ;; signals here, but if I'm wrong, please let me know.
62fd93e2
AW
396 (define state (ensure-profiler-state))
397 (set-profile-level! state (- (profile-level state) 1))
cad444e3
AW
398 (when (zero? (profile-level state))
399 (set-gc-time-taken! state
400 (- (cdr (assq 'gc-time-taken (gc-stats)))
401 (gc-time-taken state)))
402 (set-vm-trace-level! (1- (vm-trace-level)))
403 (when (count-calls? state)
404 (remove-hook! (vm-apply-hook) count-call))
405 ;; I believe that we need to do this before getting the time
406 ;; (unless we want to make things even more complicated).
407 (set-remaining-prof-time! state (setitimer ITIMER_PROF 0 0 0 0))
408 (accumulate-time state (get-internal-run-time))
409 (set-last-start-time! state #f)))
47f3ce52 410
e640b440
AW
411(define* (statprof-reset sample-seconds sample-microseconds count-calls?
412 #:optional full-stacks?)
47f3ce52
AW
413 "Reset the statprof sampler interval to @var{sample-seconds} and
414@var{sample-microseconds}. If @var{count-calls?} is true, arrange to
415instrument procedure calls as well as collecting statistical profiling
416data. If @var{full-stacks?} is true, collect all sampled stacks into a
417list for later analysis.
418
419Enables traps and debugging as necessary."
4d0c358b 420 (when (statprof-active?)
4eb1fb9b
AW
421 (error "Can't reset profiler while profiler is running."))
422 (let ((state (fresh-profiler-state #:count-calls? count-calls?
423 #:sampling-frequency
424 (cons sample-seconds sample-microseconds)
425 #:full-stacks? full-stacks?)))
426 (profiler-state state)
427 (sigaction SIGPROF profile-signal-handler)
428 #t))
47f3ce52
AW
429
430(define (statprof-fold-call-data proc init)
431 "Fold @var{proc} over the call-data accumulated by statprof. Cannot be
432called while statprof is active. @var{proc} should take two arguments,
433@code{(@var{call-data} @var{prior-result})}.
434
435Note that a given proc-name may appear multiple times, but if it does,
436it represents different functions with the same name."
4d0c358b
AW
437 (when (statprof-active?)
438 (error "Can't call statprof-fold-call-data while profiler is running."))
47f3ce52
AW
439 (hash-fold
440 (lambda (key value prior-result)
441 (proc value prior-result))
442 init
4d0c358b 443 (procedure-data (existing-profiler-state))))
47f3ce52
AW
444
445(define (statprof-proc-call-data proc)
446 "Returns the call-data associated with @var{proc}, or @code{#f} if
447none is available."
4d0c358b
AW
448 (when (statprof-active?)
449 (error "Can't call statprof-proc-call-data while profiler is running."))
d724a365 450 (get-call-data proc))
47f3ce52
AW
451
452;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
453;; Stats
454
455(define (statprof-call-data->stats call-data)
456 "Returns an object of type @code{statprof-stats}."
457 ;; returns (vector proc-name
458 ;; %-time-in-proc
459 ;; cum-seconds-in-proc
460 ;; self-seconds-in-proc
461 ;; num-calls
462 ;; self-secs-per-call
463 ;; total-secs-per-call)
464
45a7de82 465 (define state (existing-profiler-state))
62fd93e2 466
c165c50d 467 (let* ((proc-name (call-data-printable call-data))
47f3ce52
AW
468 (self-samples (call-data-self-sample-count call-data))
469 (cum-samples (call-data-cum-sample-count call-data))
470 (all-samples (statprof-sample-count))
471 (secs-per-sample (/ (statprof-accumulated-time)
472 (statprof-sample-count)))
62fd93e2 473 (num-calls (and (count-calls? state) (statprof-call-data-calls call-data))))
47f3ce52
AW
474
475 (vector proc-name
476 (* (/ self-samples all-samples) 100.0)
477 (* cum-samples secs-per-sample 1.0)
478 (* self-samples secs-per-sample 1.0)
479 num-calls
480 (and num-calls ;; maybe we only sampled in children
481 (if (zero? self-samples) 0.0
482 (/ (* self-samples secs-per-sample) 1.0 num-calls)))
483 (and num-calls ;; cum-samples must be positive
e1138ba1
AW
484 (/ (* cum-samples secs-per-sample)
485 1.0
486 ;; num-calls might be 0 if we entered statprof during the
487 ;; dynamic extent of the call
488 (max num-calls 1))))))
47f3ce52
AW
489
490(define (statprof-stats-proc-name stats) (vector-ref stats 0))
491(define (statprof-stats-%-time-in-proc stats) (vector-ref stats 1))
492(define (statprof-stats-cum-secs-in-proc stats) (vector-ref stats 2))
493(define (statprof-stats-self-secs-in-proc stats) (vector-ref stats 3))
494(define (statprof-stats-calls stats) (vector-ref stats 4))
495(define (statprof-stats-self-secs-per-call stats) (vector-ref stats 5))
496(define (statprof-stats-cum-secs-per-call stats) (vector-ref stats 6))
497
498;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
499
500(define (stats-sorter x y)
501 (let ((diff (- (statprof-stats-self-secs-in-proc x)
502 (statprof-stats-self-secs-in-proc y))))
503 (positive?
504 (if (= diff 0)
505 (- (statprof-stats-cum-secs-in-proc x)
506 (statprof-stats-cum-secs-in-proc y))
507 diff))))
508
62fd93e2 509(define* (statprof-display #:optional (port (current-output-port)))
47f3ce52
AW
510 "Displays a gprof-like summary of the statistics collected. Unless an
511optional @var{port} argument is passed, uses the current output port."
45a7de82 512 (define state (existing-profiler-state))
47f3ce52
AW
513
514 (cond
515 ((zero? (statprof-sample-count))
516 (format port "No samples recorded.\n"))
517 (else
518 (let* ((stats-list (statprof-fold-call-data
519 (lambda (data prior-value)
520 (cons (statprof-call-data->stats data)
521 prior-value))
522 '()))
523 (sorted-stats (sort stats-list stats-sorter)))
524
525 (define (display-stats-line stats)
62fd93e2 526 (if (count-calls? state)
e1138ba1 527 (format port "~6,2f ~9,2f ~9,2f ~7d ~8,2f ~8,2f "
47f3ce52
AW
528 (statprof-stats-%-time-in-proc stats)
529 (statprof-stats-cum-secs-in-proc stats)
530 (statprof-stats-self-secs-in-proc stats)
531 (statprof-stats-calls stats)
532 (* 1000 (statprof-stats-self-secs-per-call stats))
533 (* 1000 (statprof-stats-cum-secs-per-call stats)))
534 (format port "~6,2f ~9,2f ~9,2f "
535 (statprof-stats-%-time-in-proc stats)
536 (statprof-stats-cum-secs-in-proc stats)
537 (statprof-stats-self-secs-in-proc stats)))
538 (display (statprof-stats-proc-name stats) port)
539 (newline port))
540
62fd93e2 541 (if (count-calls? state)
47f3ce52
AW
542 (begin
543 (format port "~5a ~10a ~7a ~8a ~8a ~8a ~8@a\n"
544 "% " "cumulative" "self" "" "self" "total" "")
545 (format port "~5a ~9a ~8a ~8a ~8a ~8a ~8@a\n"
546 "time" "seconds" "seconds" "calls" "ms/call" "ms/call" "name"))
547 (begin
548 (format port "~5a ~10a ~7a ~8@a\n"
549 "%" "cumulative" "self" "")
550 (format port "~5a ~10a ~7a ~8@a\n"
551 "time" "seconds" "seconds" "name")))
552
553 (for-each display-stats-line sorted-stats)
554
555 (display "---\n" port)
556 (simple-format #t "Sample count: ~A\n" (statprof-sample-count))
557 (simple-format #t "Total time: ~A seconds (~A seconds in GC)\n"
558 (statprof-accumulated-time)
62fd93e2 559 (/ (gc-time-taken state) 1.0 internal-time-units-per-second))))))
47f3ce52
AW
560
561(define (statprof-display-anomolies)
562 "A sanity check that attempts to detect anomolies in statprof's
563statistics.@code{}"
45a7de82 564 (define state (existing-profiler-state))
62fd93e2 565
47f3ce52
AW
566 (statprof-fold-call-data
567 (lambda (data prior-value)
cad444e3
AW
568 (when (and (count-calls? state)
569 (zero? (call-data-call-count data))
570 (positive? (call-data-cum-sample-count data)))
571 (simple-format #t
572 "==[~A ~A ~A]\n"
573 (call-data-name data)
574 (call-data-call-count data)
575 (call-data-cum-sample-count data))))
47f3ce52
AW
576 #f)
577 (simple-format #t "Total time: ~A\n" (statprof-accumulated-time))
578 (simple-format #t "Sample count: ~A\n" (statprof-sample-count)))
579
580(define (statprof-accumulated-time)
581 "Returns the time accumulated during the last statprof run.@code{}"
4d0c358b
AW
582 (when (statprof-active?)
583 (error "Can't get accumulated time while profiler is running."))
584 (/ (accumulated-time (existing-profiler-state)) internal-time-units-per-second))
47f3ce52
AW
585
586(define (statprof-sample-count)
587 "Returns the number of samples taken during the last statprof run.@code{}"
4d0c358b
AW
588 (when (statprof-active?)
589 (error "Can't get sample count while profiler is running."))
590 (sample-count (existing-profiler-state)))
47f3ce52
AW
591
592(define statprof-call-data-name call-data-name)
593(define statprof-call-data-calls call-data-call-count)
594(define statprof-call-data-cum-samples call-data-cum-sample-count)
595(define statprof-call-data-self-samples call-data-self-sample-count)
596
597(define (statprof-fetch-stacks)
598 "Returns a list of stacks, as they were captured since the last call
599to @code{statprof-reset}.
600
601Note that stacks are only collected if the @var{full-stacks?} argument
602to @code{statprof-reset} is true."
45a7de82 603 (define state (existing-profiler-state))
62fd93e2 604 (stacks state))
47f3ce52
AW
605
606(define procedure=?
663212bb
AW
607 (lambda (a b)
608 (cond
609 ((eq? a b))
0bd1e9c6 610 ((and (program? a) (program? b))
d1100525 611 (eq? (program-code a) (program-code b)))
663212bb
AW
612 (else
613 #f))))
47f3ce52
AW
614
615;; tree ::= (car n . tree*)
616
617(define (lists->trees lists equal?)
618 (let lp ((in lists) (n-terminal 0) (tails '()))
619 (cond
620 ((null? in)
621 (let ((trees (map (lambda (tail)
622 (cons (car tail)
623 (lists->trees (cdr tail) equal?)))
624 tails)))
625 (cons (apply + n-terminal (map cadr trees))
626 (sort trees
627 (lambda (a b) (> (cadr a) (cadr b)))))))
628 ((null? (car in))
629 (lp (cdr in) (1+ n-terminal) tails))
630 ((find (lambda (x) (equal? (car x) (caar in)))
631 tails)
632 => (lambda (tail)
633 (lp (cdr in)
634 n-terminal
635 (assq-set! tails
636 (car tail)
637 (cons (cdar in) (cdr tail))))))
638 (else
639 (lp (cdr in)
640 n-terminal
641 (acons (caar in) (list (cdar in)) tails))))))
642
643(define (stack->procedures stack)
644 (filter identity
645 (unfold-right (lambda (x) (not x))
646 frame-procedure
647 frame-previous
648 (stack-ref stack 0))))
649
650(define (statprof-fetch-call-tree)
651 "Return a call tree for the previous statprof run.
652
653The return value is a list of nodes, each of which is of the type:
654@code
655 node ::= (@var{proc} @var{count} . @var{nodes})
656@end code"
45a7de82 657 (define state (existing-profiler-state))
62fd93e2 658 (cons #t (lists->trees (map stack->procedures (stacks state)) procedure=?)))
47f3ce52 659
e1138ba1
AW
660(define* (statprof thunk #:key (loop 1) (hz 100) (count-calls? #f)
661 (full-stacks? #f))
662 "Profiles the execution of @var{thunk}.
663
664The stack will be sampled @var{hz} times per second, and the thunk itself will
665be called @var{loop} times.
666
667If @var{count-calls?} is true, all procedure calls will be recorded. This
668operation is somewhat expensive.
669
670If @var{full-stacks?} is true, at each sample, statprof will store away the
671whole call tree, for later analysis. Use @code{statprof-fetch-stacks} or
672@code{statprof-fetch-call-tree} to retrieve the last-stored stacks."
673
62fd93e2
AW
674 (define state (ensure-profiler-state))
675
e1138ba1
AW
676 (dynamic-wind
677 (lambda ()
678 (statprof-reset (inexact->exact (floor (/ 1 hz)))
679 (inexact->exact (* 1e6 (- (/ 1 hz)
680 (floor (/ 1 hz)))))
681 count-calls?
682 full-stacks?)
683 (statprof-start))
684 (lambda ()
685 (let lp ((i loop))
cad444e3
AW
686 (unless (zero? i)
687 (thunk)
688 (lp (1- i)))))
e1138ba1
AW
689 (lambda ()
690 (statprof-stop)
691 (statprof-display)
62fd93e2 692 (set-procedure-data! state #f))))
e1138ba1 693
47f3ce52
AW
694(define-macro (with-statprof . args)
695 "Profiles the expressions in its body.
696
697Keyword arguments:
698
699@table @code
700@item #:loop
701Execute the body @var{loop} number of times, or @code{#f} for no looping
702
703default: @code{#f}
704@item #:hz
705Sampling rate
706
707default: @code{20}
708@item #:count-calls?
709Whether to instrument each function call (expensive)
710
711default: @code{#f}
712@item #:full-stacks?
713Whether to collect away all sampled stacks into a list
714
715default: @code{#f}
716@end table"
717 (define (kw-arg-ref kw args def)
718 (cond
719 ((null? args) (error "Invalid macro body"))
720 ((keyword? (car args))
721 (if (eq? (car args) kw)
722 (cadr args)
723 (kw-arg-ref kw (cddr args) def)))
724 ((eq? kw #f def) ;; asking for the body
725 args)
726 (else def))) ;; kw not found
e1138ba1
AW
727 `((@ (statprof) statprof)
728 (lambda () ,@(kw-arg-ref #f args #f))
729 #:loop ,(kw-arg-ref #:loop args 1)
730 #:hz ,(kw-arg-ref #:hz args 100)
731 #:count-calls? ,(kw-arg-ref #:count-calls? args #f)
732 #:full-stacks? ,(kw-arg-ref #:full-stacks? args #f)))
733
2d239a78
AW
734(define* (gcprof thunk #:key (loop 1) (full-stacks? #f))
735 "Do an allocation profile of the execution of @var{thunk}.
736
737The stack will be sampled soon after every garbage collection, yielding
738an approximate idea of what is causing allocation in your program.
739
740Since GC does not occur very frequently, you may need to use the
741@var{loop} parameter, to cause @var{thunk} to be called @var{loop}
742times.
743
744If @var{full-stacks?} is true, at each sample, statprof will store away the
745whole call tree, for later analysis. Use @code{statprof-fetch-stacks} or
746@code{statprof-fetch-call-tree} to retrieve the last-stored stacks."
747
62fd93e2
AW
748 (define state (ensure-profiler-state))
749
2d239a78 750 (define (reset)
cad444e3
AW
751 (when (positive? (profile-level state))
752 (error "Can't reset profiler while profiler is running."))
62fd93e2
AW
753 (set-accumulated-time! state 0)
754 (set-last-start-time! state #f)
755 (set-sample-count! state 0)
756 (set-count-calls?! state #f)
757 (set-procedure-data! state (make-hash-table 131))
758 (set-record-full-stacks?! state full-stacks?)
759 (set-stacks! state '()))
2d239a78
AW
760
761 (define (gc-callback)
762 (cond
56bfce7c 763 ((inside-profiler? state))
2d239a78 764 (else
56bfce7c 765 (set-inside-profiler?! state #t)
2d239a78
AW
766
767 ;; FIXME: should be able to set an outer frame for the stack cut
768 (let ((stop-time (get-internal-run-time))
769 ;; Cut down to gc-callback, and then one before (the
770 ;; after-gc async). See the note in profile-signal-handler
771 ;; also.
772 (stack (or (make-stack #t gc-callback 0 1)
773 (pk 'what! (make-stack #t)))))
774 (sample-stack-procs stack)
62fd93e2
AW
775 (accumulate-time state stop-time)
776 (set-last-start-time! state (get-internal-run-time)))
2d239a78 777
56bfce7c 778 (set-inside-profiler?! state #f))))
2d239a78
AW
779
780 (define (start)
62fd93e2 781 (set-profile-level! state (+ (profile-level state) 1))
cad444e3
AW
782 (when (= (profile-level state) 1)
783 (set-remaining-prof-time! state #f)
784 (set-last-start-time! state (get-internal-run-time))
785 (set-gc-time-taken! state (cdr (assq 'gc-time-taken (gc-stats))))
786 (add-hook! after-gc-hook gc-callback)
787 (set-vm-trace-level! (1+ (vm-trace-level)))
788 #t))
2d239a78
AW
789
790 (define (stop)
62fd93e2 791 (set-profile-level! state (- (profile-level state) 1))
cad444e3
AW
792 (when (zero? (profile-level state))
793 (set-gc-time-taken! state
794 (- (cdr (assq 'gc-time-taken (gc-stats)))
795 (gc-time-taken state)))
796 (remove-hook! after-gc-hook gc-callback)
797 (accumulate-time state (get-internal-run-time))
798 (set-last-start-time! state #f)))
2d239a78
AW
799
800 (dynamic-wind
801 (lambda ()
802 (reset)
803 (start))
804 (lambda ()
805 (let lp ((i loop))
cad444e3
AW
806 (unless (zero? i)
807 (thunk)
808 (lp (1- i)))))
2d239a78
AW
809 (lambda ()
810 (stop)
811 (statprof-display)
62fd93e2 812 (set-procedure-data! state #f))))