about summary refs log blame commit diff stats
path: root/profiler.arc
blob: 928e16f7da5f8e929456d10df11a2320a170cf97 (plain) (tree)












































































































































                                                                               
; A simple call-counting profiler.
; https://bitbucket.org/fallintothis/profiler

(= profiles* (table) originals* (table))

; avoid infinite loops & other badness in profiled fn, e.g. (profile +)
(with (orig-+ +
       orig-is is
       orig-err err
       orig-type type
       orig-sref sref
       orig-apply apply
       orig-atomic-invoke atomic-invoke)

(mac profiled (f)
  ; (= (profiles* f) 0)
  `(profiled-as ',f ,f))

; Not sure I like the order of the arguments, but probably rarely use this.

(def profiled-as (name f (o profile-data profiles*))
  (if (orig-is (orig-is (orig-type f) 'fn) nil)
      (orig-err "Can only profile functions:" f))
  (fn args
    (orig-atomic-invoke
      (fn () (orig-sref profile-data
                        (orig-+ (profile-data name 0) 1)
                        name)))
    (orig-apply f args)))

; Have to be careful here.  (= profiles* (table)) won't work, since profiled-as
; has the table passed in as an arg: after a (= ...), old closed-over
; references from profiled-as will fail to update the profiles* table.
;   (= glob* (table))
;   (def foo ((o y glob*)) (fn (x) (= (y x) t)))
;   (= bar (foo))
;   (bar 5)           ; glob* = #hash((5 . t))
;   (= glob* (table)) ; glob* = #hash()
;   (bar 5)           ; glob* = #hash(), still

(def reset-profiles ((o fns))
  (each f (or fns (keys profiles*))
    (orig-atomic-invoke
      (fn () (orig-sref profiles*
                        nil
                        f))))
  'ok)

)

(mac profile (f)
  `(do
     (= (originals* ',f) ,f)
     (= ,f (profiled ,f))
     (warn (+ ,(string f)
              " is being profiled; "
              "do not redefine it until you (unprofile " ,(string f) ")"))
     t))

(mac unprofile (f)
  `(= ,f (originals* ',f ,f)
      (originals* ',f) nil
      (profiles* ',f) nil))

(def profiles ((o profiler-data profiles*))
  (withs (data   ; avoid counting stuff from the current call to (profiles)
                 (with (atomic-invokes (profiler-data 'atomic-invoke)
                        tables (profiler-data 'table)
                        srefs (profiler-data 'sref)
                        new (table))
                   (maptable (fn (k v) (= (new k) v)) profiler-data)
                   (= (new 'atomic-invoke) atomic-invokes
                      (new 'table) tables
                      (new 'sref) srefs)
                   new)
          lhead  "Function"
          rhead  "Call Count"
          lwidth (apply max (map len:tostring:disp (cons lhead (keys data))))
          prnrow (fn (l r) 
                   (w/bars
                     (do (pr l) (sp (- lwidth (len l))))
                     (prn r))))
    (prn)
    (prnrow lhead rhead)
    (each (f call-count) (sortable data)
      (prnrow (tostring:disp f) call-count))
    (prn)))

(mac profiling-just (fns . bod)
  (unless (acons fns)
    (zap list fns))
  (w/uniq (profiles profiled)
    (let originals (map [uniq] fns)
      `(with (,profiles (table)
              ,profiled profiled-as
              ,@(mappend list originals fns))
         ; ,@(map (fn (f) `(= (,profiles ',f) 0)) fns)
         ,@(map (fn (f o) `(= ,f (,profiled ',f ,o ,profiles)))
                fns
                originals)
         (after (do ,@bod)
           (= ,@(apply + nil (map list fns originals)))
           (if (> (,profiles 'protect 0) 1) ; from (after ...)
               (-- (,profiles 'protect))
               (wipe (,profiles 'protect)))
           (profiles ,profiles))))))

(def all-fns ()
  (let xdefs '(apply cons car cdr is err + - * / mod expt sqrt > < len annotate
               type rep uniq ccc infile outfile instring outstring inside
               stdout stdin stderr call-w/stdout call-w/stdin readc readb peekc
               writec writeb write disp sread coerce open-socket socket-accept
               setuid new-thread kill-thread break-thread current-thread sleep
               system pipe-from table protect rand dir file-exists dir-exists
               rmfile mvfile macex macex1 eval on-err details scar scdr sref
               bound newstring trunc exact msec current-process-milliseconds
               current-gc-milliseconds seconds client-ip atomic-invoke dead
               flushout ssyntax ssexpand quit close force-close memory declare
               timedate sin cos tan asin acos atan log)
    (+ xdefs (keep [isa (eval _) 'fn] (keys sig*)))))

(mac profile-all ()
  (with (fns (all-fns) orig-atomic-invoke (uniq) orig-sref (uniq))
    `(with (,orig-sref sref
            ,orig-atomic-invoke atomic-invoke)
       (do ,@(map (fn (f) `(profile ,f)) fns)
           ,@(map (fn (f) `(,orig-atomic-invoke
                             (fn () (,orig-sref profiles* nil ',f))))
                  fns)
           t))))

(mac unprofile-all ()
  `(do ,@(map (fn (f) `(unprofile ,f)) (keys originals*))))

(mac profiling code
  `(profiling-just ,(all-fns) ,@code))

(mac profile-here (marker . code)
  ; (= (profiles* marker) 0)
  `(do1 (do ,@code)
        (++ (profiles* ',marker 0))))