statprof uses new setitimer magical usecs ability
[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)
e4a8775d 114 #:use-module (srfi srfi-9 gnu)
47f3ce52 115 #:autoload (ice-9 format) (format)
e1138ba1
AW
116 #:use-module (system vm vm)
117 #:use-module (system vm frame)
118 #:use-module (system vm program)
47f3ce52
AW
119 #:export (statprof-active?
120 statprof-start
121 statprof-stop
122 statprof-reset
123
124 statprof-accumulated-time
125 statprof-sample-count
126 statprof-fold-call-data
127 statprof-proc-call-data
128 statprof-call-data-name
129 statprof-call-data-calls
130 statprof-call-data-cum-samples
131 statprof-call-data-self-samples
132 statprof-call-data->stats
133
134 statprof-stats-proc-name
135 statprof-stats-%-time-in-proc
136 statprof-stats-cum-secs-in-proc
137 statprof-stats-self-secs-in-proc
138 statprof-stats-calls
139 statprof-stats-self-secs-per-call
140 statprof-stats-cum-secs-per-call
141
142 statprof-display
143 statprof-display-anomolies
144
145 statprof-fetch-stacks
146 statprof-fetch-call-tree
147
e1138ba1 148 statprof
2d239a78
AW
149 with-statprof
150
151 gcprof))
47f3ce52
AW
152
153
154;; This profiler tracks two numbers for every function called while
155;; it's active. It tracks the total number of calls, and the number
156;; of times the function was active when the sampler fired.
157;;
158;; Globally the profiler tracks the total time elapsed and the number
159;; of times the sampler was fired.
160;;
161;; Right now, this profiler is not per-thread and is not thread safe.
162
62fd93e2
AW
163(define-record-type <state>
164 (make-state accumulated-time last-start-time sample-count
19bf8caf 165 sampling-period remaining-prof-time profile-level
62fd93e2 166 count-calls? gc-time-taken record-full-stacks?
56bfce7c 167 stacks procedure-data inside-profiler?)
62fd93e2
AW
168 state?
169 ;; Total time so far.
170 (accumulated-time accumulated-time set-accumulated-time!)
171 ;; Start-time when timer is active.
172 (last-start-time last-start-time set-last-start-time!)
173 ;; Total count of sampler calls.
174 (sample-count sample-count set-sample-count!)
19bf8caf
AW
175 ;; Microseconds.
176 (sampling-period sampling-period set-sampling-period!)
62fd93e2
AW
177 ;; Time remaining when prof suspended.
178 (remaining-prof-time remaining-prof-time set-remaining-prof-time!)
179 ;; For user start/stop nesting.
180 (profile-level profile-level set-profile-level!)
181 ;; Whether to catch apply-frame.
182 (count-calls? count-calls? set-count-calls?!)
183 ;; GC time between statprof-start and statprof-stop.
184 (gc-time-taken gc-time-taken set-gc-time-taken!)
185 ;; If #t, stash away the stacks for future analysis.
186 (record-full-stacks? record-full-stacks? set-record-full-stacks?!)
187 ;; If record-full-stacks?, the stashed full stacks.
188 (stacks stacks set-stacks!)
189 ;; A hash where the key is the function object itself and the value is
190 ;; the data. The data will be a vector like this:
191 ;; #(name call-count cum-sample-count self-sample-count)
56bfce7c
AW
192 (procedure-data procedure-data set-procedure-data!)
193 ;; True if we are inside the profiler.
194 (inside-profiler? inside-profiler? set-inside-profiler?!))
62fd93e2
AW
195
196(define profiler-state (make-parameter #f))
197
4eb1fb9b 198(define* (fresh-profiler-state #:key (count-calls? #f)
19bf8caf 199 (sampling-period 10000)
4eb1fb9b 200 (full-stacks? #f))
19bf8caf 201 (make-state 0 #f 0 sampling-period 0 0 count-calls? 0 #f '()
4eb1fb9b
AW
202 (make-hash-table) #f))
203
62fd93e2
AW
204(define (ensure-profiler-state)
205 (or (profiler-state)
4eb1fb9b 206 (let ((state (fresh-profiler-state)))
62fd93e2
AW
207 (profiler-state state)
208 state)))
47f3ce52 209
45a7de82
AW
210(define (existing-profiler-state)
211 (or (profiler-state)
212 (error "expected there to be a profiler state")))
213
e70a42d4
AW
214(define-record-type call-data
215 (make-call-data proc call-count cum-sample-count self-sample-count)
216 call-data?
217 (proc call-data-proc)
218 (call-count call-data-call-count set-call-data-call-count!)
219 (cum-sample-count call-data-cum-sample-count set-call-data-cum-sample-count!)
220 (self-sample-count call-data-self-sample-count set-call-data-self-sample-count!))
221
c165c50d
AW
222(define (call-data-name cd) (procedure-name (call-data-proc cd)))
223(define (call-data-printable cd)
224 (or (call-data-name cd)
225 (with-output-to-string (lambda () (write (call-data-proc cd))))))
47f3ce52 226
47f3ce52 227(define (inc-call-data-call-count! cd)
e70a42d4 228 (set-call-data-call-count! cd (1+ (call-data-call-count cd))))
47f3ce52 229(define (inc-call-data-cum-sample-count! cd)
e70a42d4 230 (set-call-data-cum-sample-count! cd (1+ (call-data-cum-sample-count cd))))
47f3ce52 231(define (inc-call-data-self-sample-count! cd)
e70a42d4 232 (set-call-data-self-sample-count! cd (1+ (call-data-self-sample-count cd))))
47f3ce52 233
62fd93e2
AW
234(define (accumulate-time state stop-time)
235 (set-accumulated-time! state
236 (+ (accumulated-time state)
62fd93e2 237 (- stop-time (last-start-time state)))))
47f3ce52 238
e4a8775d 239(define (get-call-data state proc)
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;;
e4a8775d 256(define (sample-stack-procs state stack)
47f3ce52 257 (let ((stacklen (stack-length stack))
e4a8775d 258 (hit-count-call? #f))
47f3ce52 259
cad444e3
AW
260 (when (record-full-stacks? state)
261 (set-stacks! state (cons stack (stacks state))))
47f3ce52 262
62fd93e2 263 (set-sample-count! state (+ (sample-count state) 1))
47f3ce52
AW
264 ;; Now accumulate stats for the whole stack.
265 (let loop ((frame (stack-ref stack 0))
266 (procs-seen (make-hash-table 13))
267 (self #f))
268 (cond
269 ((not frame)
270 (hash-fold
271 (lambda (proc val accum)
272 (inc-call-data-cum-sample-count!
e4a8775d 273 (get-call-data state proc)))
47f3ce52
AW
274 #f
275 procs-seen)
e4a8775d
AW
276 (and=> (and=> self (lambda (proc)
277 (get-call-data state proc)))
47f3ce52
AW
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
19bf8caf 297(define (reset-sigprof-timer usecs)
e68ed839
AW
298 ;; Guile's setitimer binding is terrible.
299 (let ((prev (setitimer ITIMER_PROF 0 0 0 usecs)))
300 (+ (* (caadr prev) #e1e6) (cdadr prev))))
19bf8caf 301
47f3ce52 302(define (profile-signal-handler sig)
45a7de82 303 (define state (existing-profiler-state))
62fd93e2 304
56bfce7c 305 (set-inside-profiler?! state #t)
47f3ce52
AW
306
307 ;; FIXME: with-statprof should be able to set an outer frame for the
308 ;; stack cut
cad444e3
AW
309 (when (positive? (profile-level state))
310 (let* ((stop-time (get-internal-run-time))
311 ;; cut down to the signal handler. note that this will only
312 ;; work if statprof.scm is compiled; otherwise we get
313 ;; `eval' on the stack instead, because if it's not
314 ;; compiled, profile-signal-handler is a thunk that
315 ;; tail-calls eval. perhaps we should always compile the
316 ;; signal handler instead...
317 (stack (or (make-stack #t profile-signal-handler)
546efe25
AW
318 (pk 'what! (make-stack #t)))))
319
320 (sample-stack-procs state stack)
321 (accumulate-time state stop-time)
322 (set-last-start-time! state (get-internal-run-time))
323
19bf8caf 324 (reset-sigprof-timer (sampling-period state))))
e1138ba1 325
56bfce7c 326 (set-inside-profiler?! state #f))
47f3ce52
AW
327
328;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
329;; Count total calls.
330
e1138ba1 331(define (count-call frame)
45a7de82 332 (define state (existing-profiler-state))
62fd93e2 333
cad444e3
AW
334 (unless (inside-profiler? state)
335 (accumulate-time state (get-internal-run-time))
47f3ce52 336
cad444e3
AW
337 (and=> (frame-procedure frame)
338 (lambda (proc)
339 (inc-call-data-call-count!
e4a8775d 340 (get-call-data state proc))))
47f3ce52 341
cad444e3 342 (set-last-start-time! state (get-internal-run-time))))
47f3ce52
AW
343
344;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
345
346(define (statprof-active?)
347 "Returns @code{#t} if @code{statprof-start} has been called more times
348than @code{statprof-stop}, @code{#f} otherwise."
45a7de82
AW
349 (define state (profiler-state))
350 (and state (positive? (profile-level state))))
47f3ce52
AW
351
352;; Do not call this from statprof internal functions -- user only.
353(define (statprof-start)
354 "Start the profiler.@code{}"
355 ;; After some head-scratching, I don't *think* I need to mask/unmask
356 ;; signals here, but if I'm wrong, please let me know.
62fd93e2
AW
357 (define state (ensure-profiler-state))
358 (set-profile-level! state (+ (profile-level state) 1))
cad444e3 359 (when (= (profile-level state) 1)
19bf8caf
AW
360 (let ((rpt (remaining-prof-time state)))
361 (set-remaining-prof-time! state 0)
cad444e3
AW
362 ;; FIXME: Use per-thread run time.
363 (set-last-start-time! state (get-internal-run-time))
3476a369 364 (set-gc-time-taken! state (assq-ref (gc-stats) 'gc-time-taken))
19bf8caf 365 (reset-sigprof-timer (if (zero? rpt) (sampling-period state) rpt))
cad444e3
AW
366 (when (count-calls? state)
367 (add-hook! (vm-apply-hook) count-call))
368 (set-vm-trace-level! (1+ (vm-trace-level)))
369 #t)))
47f3ce52
AW
370
371;; Do not call this from statprof internal functions -- user only.
372(define (statprof-stop)
373 "Stop the profiler.@code{}"
374 ;; After some head-scratching, I don't *think* I need to mask/unmask
375 ;; signals here, but if I'm wrong, please let me know.
62fd93e2
AW
376 (define state (ensure-profiler-state))
377 (set-profile-level! state (- (profile-level state) 1))
cad444e3
AW
378 (when (zero? (profile-level state))
379 (set-gc-time-taken! state
3476a369 380 (- (assq-ref (gc-stats) 'gc-time-taken)
cad444e3
AW
381 (gc-time-taken state)))
382 (set-vm-trace-level! (1- (vm-trace-level)))
383 (when (count-calls? state)
384 (remove-hook! (vm-apply-hook) count-call))
385 ;; I believe that we need to do this before getting the time
386 ;; (unless we want to make things even more complicated).
19bf8caf 387 (set-remaining-prof-time! state (reset-sigprof-timer 0))
cad444e3
AW
388 (accumulate-time state (get-internal-run-time))
389 (set-last-start-time! state #f)))
47f3ce52 390
e640b440
AW
391(define* (statprof-reset sample-seconds sample-microseconds count-calls?
392 #:optional full-stacks?)
47f3ce52
AW
393 "Reset the statprof sampler interval to @var{sample-seconds} and
394@var{sample-microseconds}. If @var{count-calls?} is true, arrange to
395instrument procedure calls as well as collecting statistical profiling
396data. If @var{full-stacks?} is true, collect all sampled stacks into a
397list for later analysis.
398
399Enables traps and debugging as necessary."
4d0c358b 400 (when (statprof-active?)
4eb1fb9b
AW
401 (error "Can't reset profiler while profiler is running."))
402 (let ((state (fresh-profiler-state #:count-calls? count-calls?
19bf8caf
AW
403 #:sampling-period
404 (+ (* sample-seconds #e1e6)
405 sample-microseconds)
4eb1fb9b
AW
406 #:full-stacks? full-stacks?)))
407 (profiler-state state)
408 (sigaction SIGPROF profile-signal-handler)
409 #t))
47f3ce52
AW
410
411(define (statprof-fold-call-data proc init)
412 "Fold @var{proc} over the call-data accumulated by statprof. Cannot be
413called while statprof is active. @var{proc} should take two arguments,
414@code{(@var{call-data} @var{prior-result})}.
415
416Note that a given proc-name may appear multiple times, but if it does,
417it represents different functions with the same name."
4d0c358b
AW
418 (when (statprof-active?)
419 (error "Can't call statprof-fold-call-data while profiler is running."))
47f3ce52
AW
420 (hash-fold
421 (lambda (key value prior-result)
422 (proc value prior-result))
423 init
4d0c358b 424 (procedure-data (existing-profiler-state))))
47f3ce52
AW
425
426(define (statprof-proc-call-data proc)
427 "Returns the call-data associated with @var{proc}, or @code{#f} if
428none is available."
4d0c358b
AW
429 (when (statprof-active?)
430 (error "Can't call statprof-proc-call-data while profiler is running."))
e4a8775d 431 (get-call-data (existing-profiler-state) proc))
47f3ce52
AW
432
433;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
434;; Stats
435
436(define (statprof-call-data->stats call-data)
437 "Returns an object of type @code{statprof-stats}."
438 ;; returns (vector proc-name
439 ;; %-time-in-proc
440 ;; cum-seconds-in-proc
441 ;; self-seconds-in-proc
442 ;; num-calls
443 ;; self-secs-per-call
444 ;; total-secs-per-call)
445
45a7de82 446 (define state (existing-profiler-state))
62fd93e2 447
c165c50d 448 (let* ((proc-name (call-data-printable call-data))
47f3ce52
AW
449 (self-samples (call-data-self-sample-count call-data))
450 (cum-samples (call-data-cum-sample-count call-data))
451 (all-samples (statprof-sample-count))
452 (secs-per-sample (/ (statprof-accumulated-time)
453 (statprof-sample-count)))
62fd93e2 454 (num-calls (and (count-calls? state) (statprof-call-data-calls call-data))))
47f3ce52
AW
455
456 (vector proc-name
457 (* (/ self-samples all-samples) 100.0)
458 (* cum-samples secs-per-sample 1.0)
459 (* self-samples secs-per-sample 1.0)
460 num-calls
461 (and num-calls ;; maybe we only sampled in children
462 (if (zero? self-samples) 0.0
463 (/ (* self-samples secs-per-sample) 1.0 num-calls)))
464 (and num-calls ;; cum-samples must be positive
e1138ba1
AW
465 (/ (* cum-samples secs-per-sample)
466 1.0
467 ;; num-calls might be 0 if we entered statprof during the
468 ;; dynamic extent of the call
469 (max num-calls 1))))))
47f3ce52
AW
470
471(define (statprof-stats-proc-name stats) (vector-ref stats 0))
472(define (statprof-stats-%-time-in-proc stats) (vector-ref stats 1))
473(define (statprof-stats-cum-secs-in-proc stats) (vector-ref stats 2))
474(define (statprof-stats-self-secs-in-proc stats) (vector-ref stats 3))
475(define (statprof-stats-calls stats) (vector-ref stats 4))
476(define (statprof-stats-self-secs-per-call stats) (vector-ref stats 5))
477(define (statprof-stats-cum-secs-per-call stats) (vector-ref stats 6))
478
479;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
480
481(define (stats-sorter x y)
482 (let ((diff (- (statprof-stats-self-secs-in-proc x)
483 (statprof-stats-self-secs-in-proc y))))
484 (positive?
485 (if (= diff 0)
486 (- (statprof-stats-cum-secs-in-proc x)
487 (statprof-stats-cum-secs-in-proc y))
488 diff))))
489
62fd93e2 490(define* (statprof-display #:optional (port (current-output-port)))
47f3ce52
AW
491 "Displays a gprof-like summary of the statistics collected. Unless an
492optional @var{port} argument is passed, uses the current output port."
45a7de82 493 (define state (existing-profiler-state))
47f3ce52
AW
494
495 (cond
496 ((zero? (statprof-sample-count))
497 (format port "No samples recorded.\n"))
498 (else
499 (let* ((stats-list (statprof-fold-call-data
500 (lambda (data prior-value)
501 (cons (statprof-call-data->stats data)
502 prior-value))
503 '()))
504 (sorted-stats (sort stats-list stats-sorter)))
505
506 (define (display-stats-line stats)
62fd93e2 507 (if (count-calls? state)
e1138ba1 508 (format port "~6,2f ~9,2f ~9,2f ~7d ~8,2f ~8,2f "
47f3ce52
AW
509 (statprof-stats-%-time-in-proc stats)
510 (statprof-stats-cum-secs-in-proc stats)
511 (statprof-stats-self-secs-in-proc stats)
512 (statprof-stats-calls stats)
513 (* 1000 (statprof-stats-self-secs-per-call stats))
514 (* 1000 (statprof-stats-cum-secs-per-call stats)))
515 (format port "~6,2f ~9,2f ~9,2f "
516 (statprof-stats-%-time-in-proc stats)
517 (statprof-stats-cum-secs-in-proc stats)
518 (statprof-stats-self-secs-in-proc stats)))
519 (display (statprof-stats-proc-name stats) port)
520 (newline port))
521
62fd93e2 522 (if (count-calls? state)
47f3ce52
AW
523 (begin
524 (format port "~5a ~10a ~7a ~8a ~8a ~8a ~8@a\n"
525 "% " "cumulative" "self" "" "self" "total" "")
526 (format port "~5a ~9a ~8a ~8a ~8a ~8a ~8@a\n"
527 "time" "seconds" "seconds" "calls" "ms/call" "ms/call" "name"))
528 (begin
529 (format port "~5a ~10a ~7a ~8@a\n"
530 "%" "cumulative" "self" "")
531 (format port "~5a ~10a ~7a ~8@a\n"
532 "time" "seconds" "seconds" "name")))
533
534 (for-each display-stats-line sorted-stats)
535
536 (display "---\n" port)
537 (simple-format #t "Sample count: ~A\n" (statprof-sample-count))
538 (simple-format #t "Total time: ~A seconds (~A seconds in GC)\n"
539 (statprof-accumulated-time)
3476a369
AW
540 (/ (gc-time-taken state)
541 1.0 internal-time-units-per-second))))))
47f3ce52
AW
542
543(define (statprof-display-anomolies)
544 "A sanity check that attempts to detect anomolies in statprof's
545statistics.@code{}"
45a7de82 546 (define state (existing-profiler-state))
62fd93e2 547
47f3ce52
AW
548 (statprof-fold-call-data
549 (lambda (data prior-value)
cad444e3
AW
550 (when (and (count-calls? state)
551 (zero? (call-data-call-count data))
552 (positive? (call-data-cum-sample-count data)))
553 (simple-format #t
554 "==[~A ~A ~A]\n"
555 (call-data-name data)
556 (call-data-call-count data)
557 (call-data-cum-sample-count data))))
47f3ce52
AW
558 #f)
559 (simple-format #t "Total time: ~A\n" (statprof-accumulated-time))
560 (simple-format #t "Sample count: ~A\n" (statprof-sample-count)))
561
562(define (statprof-accumulated-time)
563 "Returns the time accumulated during the last statprof run.@code{}"
4d0c358b
AW
564 (when (statprof-active?)
565 (error "Can't get accumulated time while profiler is running."))
3476a369 566 (/ (accumulated-time (existing-profiler-state)) 1.0 internal-time-units-per-second))
47f3ce52
AW
567
568(define (statprof-sample-count)
569 "Returns the number of samples taken during the last statprof run.@code{}"
4d0c358b
AW
570 (when (statprof-active?)
571 (error "Can't get sample count while profiler is running."))
572 (sample-count (existing-profiler-state)))
47f3ce52
AW
573
574(define statprof-call-data-name call-data-name)
575(define statprof-call-data-calls call-data-call-count)
576(define statprof-call-data-cum-samples call-data-cum-sample-count)
577(define statprof-call-data-self-samples call-data-self-sample-count)
578
579(define (statprof-fetch-stacks)
580 "Returns a list of stacks, as they were captured since the last call
581to @code{statprof-reset}.
582
583Note that stacks are only collected if the @var{full-stacks?} argument
584to @code{statprof-reset} is true."
45a7de82 585 (define state (existing-profiler-state))
62fd93e2 586 (stacks state))
47f3ce52
AW
587
588(define procedure=?
663212bb
AW
589 (lambda (a b)
590 (cond
591 ((eq? a b))
0bd1e9c6 592 ((and (program? a) (program? b))
d1100525 593 (eq? (program-code a) (program-code b)))
663212bb
AW
594 (else
595 #f))))
47f3ce52
AW
596
597;; tree ::= (car n . tree*)
598
599(define (lists->trees lists equal?)
600 (let lp ((in lists) (n-terminal 0) (tails '()))
601 (cond
602 ((null? in)
603 (let ((trees (map (lambda (tail)
604 (cons (car tail)
605 (lists->trees (cdr tail) equal?)))
606 tails)))
607 (cons (apply + n-terminal (map cadr trees))
608 (sort trees
609 (lambda (a b) (> (cadr a) (cadr b)))))))
610 ((null? (car in))
611 (lp (cdr in) (1+ n-terminal) tails))
612 ((find (lambda (x) (equal? (car x) (caar in)))
613 tails)
614 => (lambda (tail)
615 (lp (cdr in)
616 n-terminal
617 (assq-set! tails
618 (car tail)
619 (cons (cdar in) (cdr tail))))))
620 (else
621 (lp (cdr in)
622 n-terminal
623 (acons (caar in) (list (cdar in)) tails))))))
624
625(define (stack->procedures stack)
626 (filter identity
627 (unfold-right (lambda (x) (not x))
628 frame-procedure
629 frame-previous
630 (stack-ref stack 0))))
631
632(define (statprof-fetch-call-tree)
633 "Return a call tree for the previous statprof run.
634
635The return value is a list of nodes, each of which is of the type:
636@code
637 node ::= (@var{proc} @var{count} . @var{nodes})
638@end code"
45a7de82 639 (define state (existing-profiler-state))
62fd93e2 640 (cons #t (lists->trees (map stack->procedures (stacks state)) procedure=?)))
47f3ce52 641
e1138ba1
AW
642(define* (statprof thunk #:key (loop 1) (hz 100) (count-calls? #f)
643 (full-stacks? #f))
644 "Profiles the execution of @var{thunk}.
645
646The stack will be sampled @var{hz} times per second, and the thunk itself will
647be called @var{loop} times.
648
649If @var{count-calls?} is true, all procedure calls will be recorded. This
650operation is somewhat expensive.
651
652If @var{full-stacks?} is true, at each sample, statprof will store away the
653whole call tree, for later analysis. Use @code{statprof-fetch-stacks} or
654@code{statprof-fetch-call-tree} to retrieve the last-stored stacks."
655
fd5dfcce
AW
656 (let ((state (fresh-profiler-state)))
657 (parameterize ((profiler-state state))
658 (dynamic-wind
659 (lambda ()
660 (statprof-reset (inexact->exact (floor (/ 1 hz)))
661 (inexact->exact (* 1e6 (- (/ 1 hz)
662 (floor (/ 1 hz)))))
663 count-calls?
664 full-stacks?)
665 (statprof-start))
666 (lambda ()
667 (let lp ((i loop))
668 (unless (zero? i)
669 (thunk)
670 (lp (1- i)))))
671 (lambda ()
672 (statprof-stop)
673 (statprof-display))))))
e1138ba1 674
47f3ce52
AW
675(define-macro (with-statprof . args)
676 "Profiles the expressions in its body.
677
678Keyword arguments:
679
680@table @code
681@item #:loop
682Execute the body @var{loop} number of times, or @code{#f} for no looping
683
684default: @code{#f}
685@item #:hz
686Sampling rate
687
688default: @code{20}
689@item #:count-calls?
690Whether to instrument each function call (expensive)
691
692default: @code{#f}
693@item #:full-stacks?
694Whether to collect away all sampled stacks into a list
695
696default: @code{#f}
697@end table"
698 (define (kw-arg-ref kw args def)
699 (cond
700 ((null? args) (error "Invalid macro body"))
701 ((keyword? (car args))
702 (if (eq? (car args) kw)
703 (cadr args)
704 (kw-arg-ref kw (cddr args) def)))
705 ((eq? kw #f def) ;; asking for the body
706 args)
707 (else def))) ;; kw not found
e1138ba1
AW
708 `((@ (statprof) statprof)
709 (lambda () ,@(kw-arg-ref #f args #f))
710 #:loop ,(kw-arg-ref #:loop args 1)
711 #:hz ,(kw-arg-ref #:hz args 100)
712 #:count-calls? ,(kw-arg-ref #:count-calls? args #f)
713 #:full-stacks? ,(kw-arg-ref #:full-stacks? args #f)))
714
2d239a78
AW
715(define* (gcprof thunk #:key (loop 1) (full-stacks? #f))
716 "Do an allocation profile of the execution of @var{thunk}.
717
718The stack will be sampled soon after every garbage collection, yielding
719an approximate idea of what is causing allocation in your program.
720
721Since GC does not occur very frequently, you may need to use the
722@var{loop} parameter, to cause @var{thunk} to be called @var{loop}
723times.
724
725If @var{full-stacks?} is true, at each sample, statprof will store away the
726whole call tree, for later analysis. Use @code{statprof-fetch-stacks} or
727@code{statprof-fetch-call-tree} to retrieve the last-stored stacks."
728
fd953d7a 729 (let ((state (fresh-profiler-state #:full-stacks? full-stacks?)))
fd5dfcce 730 (parameterize ((profiler-state state))
fd5dfcce
AW
731 (define (gc-callback)
732 (cond
733 ((inside-profiler? state))
734 (else
735 (set-inside-profiler?! state #t)
736
737 ;; FIXME: should be able to set an outer frame for the stack cut
738 (let ((stop-time (get-internal-run-time))
739 ;; Cut down to gc-callback, and then one before (the
740 ;; after-gc async). See the note in profile-signal-handler
741 ;; also.
742 (stack (or (make-stack #t gc-callback 0 1)
743 (pk 'what! (make-stack #t)))))
744 (sample-stack-procs state stack)
745 (accumulate-time state stop-time)
746 (set-last-start-time! state (get-internal-run-time)))
2d239a78 747
fd5dfcce
AW
748 (set-inside-profiler?! state #f))))
749
750 (define (start)
751 (set-profile-level! state (+ (profile-level state) 1))
752 (when (= (profile-level state) 1)
19bf8caf 753 (set-remaining-prof-time! state 0)
fd5dfcce
AW
754 (set-last-start-time! state (get-internal-run-time))
755 (set-gc-time-taken! state (assq-ref (gc-stats) 'gc-time-taken))
756 (add-hook! after-gc-hook gc-callback)
fd5dfcce
AW
757 #t))
758
759 (define (stop)
760 (set-profile-level! state (- (profile-level state) 1))
761 (when (zero? (profile-level state))
762 (set-gc-time-taken! state
763 (- (assq-ref (gc-stats) 'gc-time-taken)
764 (gc-time-taken state)))
765 (remove-hook! after-gc-hook gc-callback)
766 (accumulate-time state (get-internal-run-time))
767 (set-last-start-time! state #f)))
768
769 (dynamic-wind
770 (lambda ()
fd5dfcce
AW
771 (start))
772 (lambda ()
773 (let lp ((i loop))
774 (unless (zero? i)
775 (thunk)
776 (lp (1- i)))))
777 (lambda ()
778 (stop)
779 (statprof-display))))))