;;; The profile utility, based on Norvig's "Paradigms of Artificial
;;; Intelligence programming".
;;; Adapted for XLisp by Leo Sarasua (modifications marked LSG)


#+:packages
(unless (find-package "TOOLS")
	(make-package "TOOLS" :use '("XLISP")))

(in-package "TOOLS")

(export '(profile unprofile profile-report with-profiling))


(defmacro profile (&rest fn-names)
  "Profile fn-names. With no args, list profiled functions."
  `(mapcar #'profile1
           (setf *profiled-functions*
                 (union *profiled-functions*
                        (remove-if-not #'fboundp ',fn-names) )))) ; LSG


(defmacro unprofile (&rest fn-names)
  "Stop profiling fn-names. With no args, stop all profiling."
  `(progn
      (mapcar #'unprofile1
              ,(if fn-names `',fn-names *profiled-functions*) )
      (setf *profiled-functions*
            ,(if (null fn-names)
                 nil
                 `(set-difference *profiled-functions*
                                  ',fn-names )))))


(defun profile1 (fn-name)
  "Make the function count how often it is called"
  ;; First save away the old, unprofiled function.
  ;; Then make the name be a new function that increments
  ;; a counter and then calls the original function.
  (let ((fn (symbol-function fn-name)))
    (unless (eq fn (get fn-name 'profiled-fn))
      (let ((new-fn (profiled-fn fn-name fn)))
        (setf (symbol-function fn-name) new-fn
              (get fn-name 'profiled-fn) new-fn
              (get fn-name 'unprofiled-fn) fn
              (get fn-name 'profile-time) 0
              (get fn-name 'profile-count) 0 ))))
  fn-name )



(defun unprofile1 (fn-name)
  "Make the function stop counting how often it is called"
  (when (fboundp fn-name)  ; LSG
    (setf (get fn-name 'profile-time) 0)
    (setf (get fn-name 'profile-count) 0)
    (when (eq (symbol-function fn-name) (get fn-name 'profiled-fn))
       ;; normal case: restore unprofiled version
       (setf (symbol-function fn-name)
             (get fn-name 'unprofiled-fn) ))
    fn-name ))


(defun profile-report (&optional
                       (fn-names (copy-list *profiled-functions*))
                       (key #'profile-count))
  "Report profiling statistics on given functions."
  (let ((total-time (reduce #'+ (mapcar #'profile-time fn-names))))
    (unless (null key)
      (setf fn-names (sort fn-names #'> :key key)) )
    (format t "~&Total elapsed time: ~d seconds."
            (fast-time->seconds total-time) )
    (format t "~&  Count     Secs Time% Name")
    (dolist (name fn-names)
       (format t "~&~7D   ~6,2F  ~3d% ~A"
               (profile-count name)
               (fast-time->seconds (profile-time name))
               (if (< total-time 1e-9)
                   0
                   (round (/ (profile-time name) total-time) .01) )
               name ))))


(defmacro with-profiling (fn-names &rest body)
  `(progn
     (unprofile . ,fn-names)
     (profile . ,fn-names)
     (setf *profile-call-stack* nil)
     (unwind-protect
       (progn . ,body)
       (profile-report ',fn-names)
       (unprofile . ,fn-names) )))


(defun profiled-fn (fn-name fn)
  "Return a function that increments the count, and times."
  #'(lambda (&rest args)
      (profile-enter fn-name)
      (multiple-value-prog1
         (apply fn args)
         (profile-exit fn-name) )))

(defun profile-count (fn-name) (get fn-name 'profile-count))

(defun profile-time (fn-name) (get fn-name 'profile-time))


(defvar *profiled-functions* nil
  "Function names that are currently profiled" )

(defvar *profile-call-stack* nil)


(defun profile-enter (fn-name)
   (incf (get fn-name 'profile-count))
   (unless (null *profile-call-stack*)
     ;; Time charged against the calling function:
     (inc-profile-time (first *profile-call-stack*)
                       (car (first *profile-call-stack*)) ))
   ;; Put a new entry on the stack
   (push (cons fn-name (get-fast-time))
         *profile-call-stack* ))


(defun profile-exit (fn-name)
  ;; Time charged against the current function:
  (inc-profile-time (pop *profile-call-stack*)
                    fn-name )
  ;; Change the top entry to reflect current time
  (unless (null *profile-call-stack*)
    (setf (cdr (first *profile-call-stack*))
          (get-fast-time) )))


(defun inc-profile-time (entry fn-name)
  (incf (get fn-name 'profile-time)
        (fast-time-difference (get-fast-time) (cdr entry)) ))

(defun fast-time->seconds (time)
  "Convert a fast time interval into seconds"
  (float (/ time internal-time-units-per-second)) )


(defun get-fast-time ()
  "Return the elapsed time. This may wrap around;
  use FAST-TIME-DIFFERENCE to compare."
  (get-internal-real-time) )

(defun fast-time-difference (end start)
  "Subtract two time points."
  (- end start) )



syntax highlighted by Code2HTML, v. 0.9.1