add #:resolve-syntax-parameters? kwarg to syntax-local-binding
[bpt/guile.git] / module / statprof.scm
index 8d6f731..33246e5 100644 (file)
@@ -1,7 +1,7 @@
 ;;;; (statprof) -- a statistical profiler for Guile
 ;;;; -*-scheme-*-
 ;;;;
-;;;;   Copyright (C) 2009  Free Software Foundation, Inc.
+;;;;   Copyright (C) 2009, 2010, 2011  Free Software Foundation, Inc.
 ;;;;    Copyright (C) 2004, 2009 Andy Wingo <wingo at pobox dot com>
 ;;;;    Copyright (C) 2001 Rob Browning <rlb at defaultvalue dot org>
 ;;;; 
 (define-module (statprof)
   #:use-module (srfi srfi-1)
   #:autoload   (ice-9 format) (format)
+  #:use-module (system vm vm)
+  #:use-module (system vm frame)
+  #:use-module (system vm program)
   #:export (statprof-active?
             statprof-start
             statprof-stop
             statprof-fetch-stacks
             statprof-fetch-call-tree
 
-            with-statprof))
+            statprof
+            with-statprof
+
+            gcprof))
 
 
 ;; This profiler tracks two numbers for every function called while
          (+ accumulated-time 0.0 (- ,stop-time last-start-time))))
 
 (define (get-call-data proc)
-  (or (hashq-ref procedure-data proc)
-      (let ((call-data (make-call-data proc 0 0 0)))
-        (hashq-set! procedure-data proc call-data)
-        call-data)))
+  (let ((k (if (or (not (program? proc))
+                   (zero? (program-num-free-variables proc)))
+               proc
+               (program-objcode proc))))
+    (or (hashq-ref procedure-data k)
+        (let ((call-data (make-call-data proc 0 0 0)))
+          (hashq-set! procedure-data k call-data)
+          call-data))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; SIGPROF handler
               ;; and eliminate inside-profiler? because it seems to
               ;; confuse guile wrt re-enabling the trap when
               ;; count-call finishes.
-              (if %count-calls? (trap-disable 'apply-frame))
+              (if %count-calls?
+                  (set-vm-trace-level! (the-vm)
+                                       (1- (vm-trace-level (the-vm)))))
               (accumulate-time stop-time)))
         
         (setitimer ITIMER_PROF
         (if (not inside-apply-trap?)
             (begin
               (set! last-start-time (get-internal-run-time))
-              (if %count-calls? (trap-enable 'apply-frame))))))
-
+              (if %count-calls?
+                  (set-vm-trace-level! (the-vm)
+                                       (1+ (vm-trace-level (the-vm)))))))))
+  
   (set! inside-profiler? #f))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Count total calls.
 
-(define (count-call trap-name continuation tail)
+(define (count-call frame)
   (if (not inside-profiler?)
       (begin
         (accumulate-time (get-internal-run-time))
 
-        (and=> (frame-procedure (last-stack-frame continuation))
+        (and=> (frame-procedure frame)
                (lambda (proc)
                  (inc-call-data-call-count!
                   (get-call-data proc))))
@@ -343,7 +357,9 @@ than @code{statprof-stop}, @code{#f} otherwise."
                        0 0
                        (car sampling-frequency)
                        (cdr sampling-frequency)))
-        (trap-enable 'apply-frame)
+        (if %count-calls?
+            (add-hook! (vm-apply-hook (the-vm)) count-call))
+        (set-vm-trace-level! (the-vm) (1+ (vm-trace-level (the-vm))))
         #t)))
   
 ;; Do not call this from statprof internal functions -- user only.
@@ -356,15 +372,17 @@ than @code{statprof-stop}, @code{#f} otherwise."
       (begin
         (set! gc-time-taken
               (- (cdr (assq 'gc-time-taken (gc-stats))) gc-time-taken))
-        (trap-disable 'apply-frame)
+        (set-vm-trace-level! (the-vm) (1- (vm-trace-level (the-vm))))
+        (if %count-calls?
+            (remove-hook! (vm-apply-hook (the-vm)) count-call))
         ;; I believe that we need to do this before getting the time
         ;; (unless we want to make things even more complicated).
         (set! remaining-prof-time (setitimer ITIMER_PROF 0 0 0 0))
         (accumulate-time (get-internal-run-time))
         (set! last-start-time #f))))
 
-(define (statprof-reset sample-seconds sample-microseconds count-calls?
-                        . full-stacks?)
+(define* (statprof-reset sample-seconds sample-microseconds count-calls?
+                         #:optional full-stacks?)
   "Reset the statprof sampler interval to @var{sample-seconds} and
 @var{sample-microseconds}. If @var{count-calls?} is true, arrange to
 instrument procedure calls as well as collecting statistical profiling
@@ -381,13 +399,8 @@ Enables traps and debugging as necessary."
   (set! sampling-frequency (cons sample-seconds sample-microseconds))
   (set! remaining-prof-time #f)
   (set! procedure-data (make-hash-table 131))
-  (if %count-calls?
-      (begin
-        (trap-set! apply-frame-handler count-call)
-        (trap-enable 'traps)))
-  (set! record-full-stacks? (and (pair? full-stacks?) (car full-stacks?)))
+  (set! record-full-stacks? full-stacks?)
   (set! stacks '())
-  (debug-enable 'debug)
   (sigaction SIGPROF profile-signal-handler)
   #t)
 
@@ -445,7 +458,11 @@ none is available."
                  (if (zero? self-samples) 0.0
                      (/ (* self-samples secs-per-sample) 1.0 num-calls)))
             (and num-calls ;; cum-samples must be positive
-                 (/ (* cum-samples secs-per-sample) 1.0 num-calls)))))
+                 (/ (* cum-samples secs-per-sample)
+                    1.0
+                    ;; num-calls might be 0 if we entered statprof during the
+                    ;; dynamic extent of the call
+                    (max num-calls 1))))))
 
 (define (statprof-stats-proc-name stats) (vector-ref stats 0))
 (define (statprof-stats-%-time-in-proc stats) (vector-ref stats 1))
@@ -484,7 +501,7 @@ optional @var{port} argument is passed, uses the current output port."
 
       (define (display-stats-line stats)
         (if %count-calls?
-            (format  port "~6,2f ~9,2f ~9,2f ~8r ~8,2f ~8,2f  "
+            (format  port "~6,2f ~9,2f ~9,2f ~7d ~8,2f ~8,2f  "
                      (statprof-stats-%-time-in-proc stats)
                      (statprof-stats-cum-secs-in-proc stats)
                      (statprof-stats-self-secs-in-proc stats)
@@ -516,7 +533,7 @@ optional @var{port} argument is passed, uses the current output port."
       (simple-format #t "Sample count: ~A\n" (statprof-sample-count))
       (simple-format #t "Total time: ~A seconds (~A seconds in GC)\n"
                      (statprof-accumulated-time)
-                     (/ gc-time-taken internal-time-units-per-second))))))
+                     (/ gc-time-taken 1.0 internal-time-units-per-second))))))
 
 (define (statprof-display-anomolies)
   "A sanity check that attempts to detect anomolies in statprof's
@@ -561,29 +578,13 @@ to @code{statprof-reset} is true."
   stacks)
 
 (define procedure=?
-  (if (false-if-exception (resolve-interface '(system base compile)))
-      (lambda (a b)
-        (cond
-         ((eq? a b))
-         ((and ((@ (system vm program) program?) a)
-               ((@ (system vm program) program?) b))
-          (eq? ((@ (system vm program) program-objcode) a)
-               ((@ (system vm program) program-objcode) b)))
-         ((and (closure? a) (closure? b)
-               (procedure-source a) (procedure-source b))
-          (and (eq? (procedure-name a) (procedure-name b))
-               (equal? (procedure-source a) (procedure-source b))))
-         (else
-          #f)))
-      (lambda (a b)
-        (cond
-         ((eq? a b))
-         ((and (closure? a) (closure? b)
-               (procedure-source a) (procedure-source b))
-          (and (eq? (procedure-name a) (procedure-name b))
-               (equal? (procedure-source a) (procedure-source b))))
-         (else
-          #f)))))
+  (lambda (a b)
+    (cond
+     ((eq? a b))
+     ((and (program? a) (program? b))
+      (eq? (program-objcode a) (program-objcode b)))
+     (else
+      #f))))
 
 ;; tree ::= (car n . tree*)
 
@@ -629,6 +630,39 @@ The return value is a list of nodes, each of which is of the type:
 @end code"
   (cons #t (lists->trees (map stack->procedures stacks) procedure=?)))
 
+(define* (statprof thunk #:key (loop 1) (hz 100) (count-calls? #f)
+                   (full-stacks? #f))
+  "Profiles the execution of @var{thunk}.
+
+The stack will be sampled @var{hz} times per second, and the thunk itself will
+be called @var{loop} times.
+
+If @var{count-calls?} is true, all procedure calls will be recorded. This
+operation is somewhat expensive.
+
+If @var{full-stacks?} is true, at each sample, statprof will store away the
+whole call tree, for later analysis. Use @code{statprof-fetch-stacks} or
+@code{statprof-fetch-call-tree} to retrieve the last-stored stacks."
+  
+  (dynamic-wind
+    (lambda ()
+      (statprof-reset (inexact->exact (floor (/ 1 hz)))
+                      (inexact->exact (* 1e6 (- (/ 1 hz)
+                                                (floor (/ 1 hz)))))
+                      count-calls?
+                      full-stacks?)
+      (statprof-start))
+    (lambda ()
+      (let lp ((i loop))
+        (if (not (zero? i))
+            (begin
+              (thunk)
+              (lp (1- i))))))
+    (lambda ()
+      (statprof-stop)
+      (statprof-display)
+      (set! procedure-data #f))))
+
 (define-macro (with-statprof . args)
   "Profiles the expressions in its body.
 
@@ -662,29 +696,89 @@ default: @code{#f}
      ((eq? kw #f def) ;; asking for the body
       args)
      (else def))) ;; kw not found
-  (let ((loop (kw-arg-ref #:loop args #f))
-        (hz (kw-arg-ref #:hz args 20))
-        (count-calls? (kw-arg-ref #:count-calls? args #f))
-        (full-stacks? (kw-arg-ref #:full-stacks? args #f))
-        (body (kw-arg-ref #f args #f)))
-    `(dynamic-wind
-         (lambda ()
-            (statprof-reset (inexact->exact (floor (/ 1 ,hz)))
-                            (inexact->exact (* 1e6 (- (/ 1 ,hz)
-                                                      (floor (/ 1 ,hz)))))
-                            ,count-calls?
-                            ,full-stacks?)
-            (statprof-start))
-         (lambda ()
-           ,(if loop
-                (let ((lp (gensym "statprof ")) (x (gensym)))
-                  `(let ,lp ((,x ,loop))
-                        (if (not (zero? ,x))
-                            (begin ,@body (,lp (1- ,x))))))
-                `(begin ,@body)))
-         (lambda ()
-            (statprof-stop)
-            (statprof-display)
-            (set! (@@ (statprof) procedure-data) #f)))))
-
-;;; arch-tag: 83969178-b576-4c52-a31c-6a9c2be85d10
+  `((@ (statprof) statprof)
+    (lambda () ,@(kw-arg-ref #f args #f))
+    #:loop ,(kw-arg-ref #:loop args 1)
+    #:hz ,(kw-arg-ref #:hz args 100)
+    #:count-calls? ,(kw-arg-ref #:count-calls? args #f)
+    #:full-stacks? ,(kw-arg-ref #:full-stacks? args #f)))
+
+(define* (gcprof thunk #:key (loop 1) (full-stacks? #f))
+  "Do an allocation profile of the execution of @var{thunk}.
+
+The stack will be sampled soon after every garbage collection, yielding
+an approximate idea of what is causing allocation in your program.
+
+Since GC does not occur very frequently, you may need to use the
+@var{loop} parameter, to cause @var{thunk} to be called @var{loop}
+times.
+
+If @var{full-stacks?} is true, at each sample, statprof will store away the
+whole call tree, for later analysis. Use @code{statprof-fetch-stacks} or
+@code{statprof-fetch-call-tree} to retrieve the last-stored stacks."
+  
+  (define (reset)
+    (if (positive? profile-level)
+        (error "Can't reset profiler while profiler is running."))
+    (set! accumulated-time 0)
+    (set! last-start-time #f)
+    (set! sample-count 0)
+    (set! %count-calls? #f)
+    (set! procedure-data (make-hash-table 131))
+    (set! record-full-stacks? full-stacks?)
+    (set! stacks '()))
+
+  (define (gc-callback)
+    (cond
+     (inside-profiler?)
+     (else
+      (set! inside-profiler? #t)
+
+      ;; FIXME: should be able to set an outer frame for the stack cut
+      (let ((stop-time (get-internal-run-time))
+            ;; Cut down to gc-callback, and then one before (the
+            ;; after-gc async).  See the note in profile-signal-handler
+            ;; also.
+            (stack (or (make-stack #t gc-callback 0 1)
+                       (pk 'what! (make-stack #t)))))
+        (sample-stack-procs stack)
+        (accumulate-time stop-time)
+        (set! last-start-time (get-internal-run-time)))
+      
+      (set! inside-profiler? #f))))
+
+  (define (start)
+    (set! profile-level (+ profile-level 1))
+    (if (= profile-level 1)
+        (begin
+          (set! remaining-prof-time #f)
+          (set! last-start-time (get-internal-run-time))
+          (set! gc-time-taken (cdr (assq 'gc-time-taken (gc-stats))))
+          (add-hook! after-gc-hook gc-callback)
+          (set-vm-trace-level! (the-vm) (1+ (vm-trace-level (the-vm))))
+          #t)))
+
+  (define (stop)
+    (set! profile-level (- profile-level 1))
+    (if (zero? profile-level)
+        (begin
+          (set! gc-time-taken
+                (- (cdr (assq 'gc-time-taken (gc-stats))) gc-time-taken))
+          (remove-hook! after-gc-hook gc-callback)
+          (accumulate-time (get-internal-run-time))
+          (set! last-start-time #f))))
+
+  (dynamic-wind
+    (lambda ()
+      (reset)
+      (start))
+    (lambda ()
+      (let lp ((i loop))
+        (if (not (zero? i))
+            (begin
+              (thunk)
+              (lp (1- i))))))
+    (lambda ()
+      (stop)
+      (statprof-display)
+      (set! procedure-data #f))))