;;; bench.el --- benchmarking utility for emacsen

;; Copyright (C) 1987,88,89,90,93,94,95,96 Free Software Foundation, Inc.
;; $Id: bench.el,v 1.2 1997/01/11 01:42:07 holder Exp $	
;; $Source: /home/holder/lib/elisp/bench/RCS/bench.el,v $
;; $Revision: 1.2 $
;; $Author: holder $
;; $Date: 1997/01/11 01:42:07 $

;; Author: Shane Holder <holder@rsn.hp.com>
;; Adapted-By: Steve Baur <steve@altair.xemacs.org>
;; Further adapted by: Shane Holder <holder@rsn.hp.com>
;; Keywords: internal, maint

;; This file is part of XEmacs.

;; XEmacs is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; XEmacs is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with XEmacs; see the file COPYING.  If not, write to the Free
;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
;; 02111-1307, USA.

;;; Commentary:

;; Adapted from Shane Holder's bench.el by steve@altair.xemacs.org.

;; To run
;; Extract the shar file in /tmp, or modify bench-lisp-file to
;; point to the gnus.el file.
;; At the shell prompt emacs -q --no-site-file <= don't load users .emacs or site-file
;; M-x byte-compile-file "/tmp/bench.el"
;; M-x load-file "/tmp/bench.elc"
;; In the scratch buffer (bench 1)


;; All bench marks must be named bench-mark-<something>
;; Results are put in bench-mark-<something-times which is a list of
;;  times for the runs.
;; If the bench mark is not simple then there needs to be a
;;  corresponding bench-handler-<something>

;;; Code:

;; Use elp to profile benchmarks
(require 'cl)				;Emacs doesn't have when and cdar

;-----------------------------------------------------------------------------
(defvar bench-mark-hanoi-times nil)

(defun bench-handler-hanoi (times)
  (let ((start-time))
  (while (> times 0)
;    (setq start-time (bench-get-time))
    (bench-mark-hanoi)
;    (setq bench-mark-hanoi-times (cons (- (bench-get-time) start-time ) bench-mark-hanoi-times ))
    (setq times (- times 1))))
)

(defun bench-mark-hanoi ()
  "How long to complete the tower of hanoi."
  (hanoi 4))

;-----------------------------------------------------------------------------
(defvar bench-mark-font-lock-buffer nil "buffer used for bench-mark-fontlock")

(defun bench-handler-font-lock (times)
  (setq bench-mark-font-lock-buffer (find-file bench-lisp-file))
  (while (> times 0)
    (bench-mark-font-lock)
    (font-lock-mode)			; Turn it off
    (setq times (- times 1)))
  (kill-buffer bench-mark-font-lock-buffer)
)

(defun bench-mark-font-lock ()
  "How long to fonitfy a large file."
  (font-lock-fontify-buffer)
)

;-----------------------------------------------------------------------------
(defvar bench-mark-scrolling-buffer nil "buffer used for bench-mark-scrolling")

(defun bench-handler-scrolling (times)
  (setq bench-mark-scrolling-buffer (find-file bench-lisp-file))
  (set-buffer bench-mark-scrolling-buffer)
;  (setq scroll-step 1)
  (font-lock-mode -1)
  (goto-char (point-min))		;Start at point min
  (let ((temp-times times))
    (while (> temp-times 0)
      (bench-mark-scrolling-down)
      (bench-mark-scrolling-up)
      (setq temp-times (- temp-times 1))))

  (font-lock-fontify-buffer)

  (goto-char (point-min))		;Start at point min
  (let ((temp-times times))
    (while (> temp-times 0)
      (bench-mark-scrolling-down-fontified)
      (bench-mark-scrolling-up-fontified)
      (setq temp-times (- temp-times 1))))
  (kill-buffer bench-mark-scrolling-buffer)
)

(defun bench-mark-scrolling-down ()
  "How long does it take to scroll down through a large file.
Expect point to be at point min"
  (let ((buffer-read-only t))
    (while (< (point) (point-max))
      (next-line 1)
      (sit-for 0))))

(defun bench-mark-scrolling-up ()
  "How long does it take to scroll up through a large fontified ile."
  (let ((buffer-read-only t))
    (while (> (point) (point-min))
      (previous-line 1)
      (sit-for 0))))

(defun bench-mark-scrolling-down-fontified ()
  "How long does it take to scroll down through a large fontified file."
  (let ((buffer-read-only t))
    (goto-char (point-min))
    (while (< (point) (point-max))
      (next-line 1)
      (sit-for 0))))

(defun bench-mark-scrolling-up-fontified ()
  "How long does it take to scroll up through a large fontified ile."
  (let ((buffer-read-only t))
    (while (> (point) (point-min))
      (previous-line 1)
      (sit-for 0))))

;-----------------------------------------------------------------------------

(defun bench-handler-make-frames (times)
  (let ((temp-times times)
	(frame))
    (while (> temp-times 0)
      (setq frame (bench-mark-make-frame)) ;Make frame
      (bench-mark-delete-frame frame)	;Delete frame
      (setq temp-times (- temp-times 1))))

  (let ((temp-times times)
	(frames))
    (while (> temp-times 0)
      (setq frames (cons (bench-mark-make-multiple-frames) frames)) ;Make frames
      (setq temp-times (- temp-times 1)))

    (setq temp-times times)

    (while (> temp-times 0)
      (bench-mark-delete-multiple-frames (car frames))	;Delete frames
      (setq frames (cdr frames))
      (setq temp-times (- temp-times 1))))

)

(defun bench-mark-make-frame ()
  "How quickly can emacs create a new frame."
  (make-frame))

(defun bench-mark-delete-frame (frame)
  "How quickly can emacs create a new frame."
  (delete-frame frame))

(defun bench-mark-make-multiple-frames ()
  "How quickly can emacs create a new frame."
  (make-frame))

(defun bench-mark-delete-multiple-frames (frame)
  "How quickly can emacs create a new frame."
  (delete-frame frame))


;-----------------------------------------------------------------------------
(defconst bench-mark-make-words-buffer nil)
(defconst bench-mark-make-words-buffer-name "*bench-mark-make-words*")
(defconst bench-mark-make-words-number-of-words 10000)

(defun bench-handler-make-words (times)
  (setq bench-mark-make-words-buffer (get-buffer-create bench-mark-make-words-buffer-name))
  (set-buffer bench-mark-make-words-buffer)
  (while (> times 0)
    (bench-mark-make-words)
    (erase-buffer)
    (setq times (- times 1)))
  (kill-buffer bench-mark-make-words-buffer)
)

(defun bench-mark-make-words ()
  "How long does it take to generate lots of random words."
  (let ((tmp-words bench-mark-make-words-number-of-words))
    (while (not (= tmp-words 0))
      (let ((word-len (random 10)))
	(while (not (= word-len 0))
	  (insert (+ ?a (random 25)))
	  (setq word-len (- word-len 1))))
      (insert "\n")
      (setq tmp-words (- tmp-words 1)))))

;-----------------------------------------------------------------------------
(defconst bench-mark-sort-words-buffer-name "*bench-mark-sort-words*")
(defconst bench-mark-sort-words-buffer nil)
(defconst bench-mark-sort-words-number-words 10000)

(defun bench-handler-sort-words (times)
  (setq bench-mark-sort-words-buffer (get-buffer-create bench-mark-sort-words-buffer-name))
  (switch-to-buffer bench-mark-sort-words-buffer)
  (while (> times 0)
    (bench-pre-sort-words)			;Generate the random words
    (bench-mark-sort-words)			;Sort those puppies
    (erase-buffer)
    (setq times (- times 1)))
  (kill-buffer bench-mark-sort-words-buffer)
)

(defun bench-pre-sort-words ()
  "How long does it take to generate lots of random words."
  (let ((tmp-words bench-mark-sort-words-number-words))
    (while (not (= tmp-words 0))
      (let ((word-len (random 10)))
	(while (not (= word-len 0))
	  (insert (+ ?a (random 25)))
	  (setq word-len (- word-len 1))))
      (insert "\n")
      (setq tmp-words (- tmp-words 1)))))

(defun bench-mark-sort-words ()
  (sort-lines nil (point-min) (point-max))
)

;-----------------------------------------------------------------------------
; Byte compile a file
(defun bench-handler-byte-compile (times)
  (while (> times 0)
    (bench-mark-byte-compile)
    (setq times (- times 1)))
)

(defun bench-mark-byte-compile ()
  "How long does it take to byte-compile a large lisp file"
  (byte-compile-file bench-lisp-file)
)

;-----------------------------------------------------------------------------
; Run through a loop

(defconst bench-mark-loop-count 250000)

(defun bench-handler-loop (times)
  (while (> times 0)
    (bench-mark-loop)
    (setq times (- times 1)))
)

(defun bench-mark-loop ()
  "How long does it take to run through a loop."
  (let ((count bench-mark-loop-count))
    (let ((i 0) (gcount 0))
      (while (< i count)
	(increment)
	(setq i (1+ i)))
      (message "gcount = %d" gcount))))

(defun increment ()
  "Increment a variable for bench-mark-loop."
  (setq gcount (1+ gcount)))

;-----------------------------------------------------------------------------
(defconst bench-mark-large-list-list-size 500000
  "Size of list to use in small list creation/garbage collection")
(defconst bench-mark-large-list-num-lists 10)

(defun bench-handler-large-list (times)
  (let ((tmp-foo bench-mark-large-list-num-lists))
    (while (> tmp-foo 0)
      (bench-mark-large-list)
      (setq tmp-foo (- tmp-foo 1))))
)

(defun bench-mark-large-list ()
  (make-list bench-mark-large-list-list-size '1)
)

;-----------------------------------------------------------------------------
(defun bench-mark-large-list-garbage-collect (times)
  (garbage-collect)
)

;-----------------------------------------------------------------------------
(defconst bench-mark-small-list-list-size 10
  "Size of list to use in small list creation/garbage collection")

(defconst bench-mark-small-list-num-lists 100000
  "Number of lists to use in small list creation/garbage collections")

(defun bench-handler-small-list (times)
  (let ((tmp-foo bench-mark-small-list-num-lists))
    (while (> tmp-foo 0)
      (bench-mark-small-list)
      (setq tmp-foo (- tmp-foo 1)))
))

(defun bench-mark-small-list ()
  (make-list bench-mark-small-list-list-size '1)
)

;-----------------------------------------------------------------------------
(defun bench-mark-small-list-garbage-collect (times)
  (garbage-collect)
)

;-----------------------------------------------------------------------------
(defconst bench-mark-insert-into-empty-buffer-num-words 100000)

(defun bench-handler-insert-into-empty-buffer (times)
  (set-buffer (get-buffer-create "*tmp*"))
  (bench-mark-insert-into-empty-buffer)
  (erase-buffer)
  (kill-buffer "*tmp*")
)

(defun bench-mark-insert-into-empty-buffer ()
  (let ((a bench-mark-insert-into-empty-buffer-num-words))
    (while (> a 0)
      (insert "0123456789\n")
      (setq a (1- a))))
)

;=============================================================================
(defconst bench-version (let ((rcsvers "$Revision: 1.2 $"))
			  (substring rcsvers 11 (- (length rcsvers) 2)))
  "*Version number of bench.el")

(defconst temp-dir (file-name-as-directory
		    (or (getenv "TMPDIR")
			(getenv "TMP")
			(getenv "TEMP")
			"/tmp/")))

(defconst bench-large-lisp-file (concat temp-dir "./bench-large.el")
  "Large lisp file to use in benchmarks should be /temp-dir/bench-text.el")

(defconst bench-small-lisp-file (concat temp-dir "./bench-small.el")
  "Large lisp file to use in benchmarks should be /temp-dir/bench-text.el")

(defconst bench-lisp-file bench-large-lisp-file)

(defconst bench-pre-bench-hook nil
  "Hook for individual bench mark initialization.")

(defconst bench-post-bench-hook nil
  "Hook for individual bench mark statistic collection.")

(defconst bench-mark-function-alist 
  '(
    (bench-handler-hanoi . "Tower of Hanoi")
    (bench-handler-font-lock               . "Font Lock")
    (bench-handler-scrolling               . "Large File scrolling")
    (bench-handler-make-frames             . "Frame Creation")
    (bench-handler-make-words              . "Generate Words")
    (bench-handler-sort-words              . "Sort Buffer")
    (bench-handler-byte-compile            . "Large File bytecompilation")
    (bench-handler-loop                    . "Loop Computation")
    (bench-handler-large-list              . "Make a Few Large Size List")
    (bench-mark-large-list-garbage-collect . "Garbage Collection Large Size List")
    (bench-handler-small-list              . "Make Several Small Size List")
    (bench-mark-small-list-garbage-collect  . "Garbage Collection Small Size List")
    (bench-handler-insert-into-empty-buffer . "Text Insertion")
))

(defconst bench-enabled-profiling nil
  "If non-nil and the underlying emacs supports it, do function profiling.")

(defconst bench-mark-profile-buffer "*Profile*"
  "Buffer used for collection of profiling data.")

(setq gc-cons-threshold 40000000)

(defconst bench-small-frame-alist '((height . 24) (width . 80)))
(defconst bench-medium-frame-alist '((height . 48) (width . 80)))
(defconst bench-large-frame-alist '((height . 72) (width . 80)))

(defsubst bench-get-time ()
  ;; Stolen from elp
  ;; get current time in seconds and microseconds. I throw away the
  ;; most significant 16 bits of seconds since I doubt we'll ever want
  ;; to profile lisp on the order of 18 hours. See notes at top of file.
  (let ((now (current-time)))
    (+ (float (nth 1 now)) (/ (float (nth 2 now)) 1000000.0))))

(defun bench-init ()
  "Initialize profiling for bench marking package."
  (if (fboundp 'start-profiling)
      (let ((buf (get-buffer-create bench-mark-profile-buffer)))
	(erase-buffer buf)
	(when (profiling-active-p)
	  (stop-profiling)
	  (clear-profiling-info)))
    (message "Profiling not available in this XEmacs.")
    (sit-for 2)))

(defun bench-test-init ()
  "Initialize profiling for bench marking package."
  (if (fboundp 'start-profiling)
      (let ((buf (get-buffer-create bench-mark-profile-buffer)))
	(erase-buffer buf)
	(when (profiling-active-p)
	  (stop-profiling)
	  (clear-profiling-info)))
    (message "Profiling not available in this XEmacs.")
    (sit-for 2))
  (setq bench-lisp-file bench-small-lisp-file)
  (setq bench-mark-make-words-number-of-words 100)
  (setq bench-mark-sort-words-number-of-words 100)
  (setq bench-mark-loop-count 10000)
  (setq bench-mark-large-list-list-size 500)
  (setq bench-mark-small-list-num-lists 100)
  (setq bench-mark-insert-into-empty-buffer-num-words 100)
  
)

(defun bench-profile-start (test-name)
  "Turn on profiling for test `test-name'."
  (when (and bench-enabled-profiling
	     (fboundp 'start-profiling))
    (when (profiling-active-p)
      (stop-profiling))
    (let ((buf (get-buffer-create bench-mark-profile-buffer)))
      (save-excursion
	(set-buffer buf)
	(insert "Test `" test-name "'\n")
	(start-profiling)))))

(defun bench-profile-stop (test-name)
  "Turn off profiling for test `test-name'."
  (when (and bench-enabled-profiling
	     (fboundp 'stop-profiling))
    (stop-profiling)
    (let ((buf (get-buffer-create bench-mark-profile-buffer)))
      (save-excursion
	(set-buffer buf)
	(insert (with-output-to-string
		 (pretty-print-profiling-info)) "\n")))
    (clear-profiling-info)))

(add-hook 'bench-pre-bench-hook 'bench-profile-start)
(add-hook 'bench-post-bench-hook 'bench-profile-stop)

(defun bench-post ()
"Post processing of elp results"
; I can't figure out a good way to sort the lines numerically.
; If someone comes up with a good way, let me know.
  (goto-char (point-min))
  (next-line 2)
  (sort-lines nil (point) (point-max))
  (mail-results (current-buffer))
)

(defun bench (arg)
  "Run a series of benchmarks."
  (interactive "p")
  (elp-instrument-package "bench-mark") ;Only instrument functions
                                        ;beginning with bench-mark
  (bench-init)
  (if (fboundp 'byte-optimize)		;Turn off byte-compile optimization in XEmacs
      (setq byte-optimize nil))
  (if (fboundp 'menu-bar-mode)
      (menu-bar-mode -1))			;Turn off menu-bar
  (let ((benches bench-mark-function-alist))
    (while benches
      (let ((test-name (cdar benches)))
	(run-hook-with-args 'bench-pre-bench-hook test-name)
	(message "Running %s - %s." (symbol-name (caar benches)) test-name)
	(funcall (caar benches) arg)
	(setq benches (cdr benches))
	(run-hook-with-args 'bench-post-bench-hook test-name))
      ))
  (elp-results)
  (bench-post)
)

(defun bench-test (arg)
  "Run all the tests but with smaller values so the tests run quicker.
This way I don't have to sit around to see if the tests complete"
  (interactive "p")
  (elp-instrument-package "bench-mark") ;Only instrument functions
                                        ;beginning with bench-mark
  (bench-test-init)
  (if (fboundp 'byte-optimize)		;Turn off byte-compile optimization in XEmacs
      (setq byte-optimize nil))
  (if (fboundp 'menu-bar-mode)
      (menu-bar-mode -1))			;Turn off menu-bar
  (let ((benches bench-mark-function-alist))
    (while benches
      (let ((test-name (cdar benches)))
	(run-hook-with-args 'bench-pre-bench-hook test-name)
	(message "Running %s - %s." (symbol-name (caar benches)) test-name)
	(funcall (caar benches) arg)
	(setq benches (cdr benches))
	(run-hook-with-args 'bench-post-bench-hook test-name))
      ))
  (elp-results)
  (bench-post)
)


(defconst bench-send-results-to "holder@rsn.hp.com")
(defconst bench-subject "Bench Mark Results")
(defconst bench-system-form (format "

Please fill in as much of the following as you can
and then hit C-cC-c to send.

CPU Manufacturer (Intel,HP,DEC,etc.): 
CPU Type (Pentium,Alpha): 
CPU Speed: 
RAM (in meg): 
Emacs Version: %s
Emacs (version): %s
Compile line:
Bench Version: %s
" emacs-version (emacs-version) bench-version))

(defun mail-results (buffer)
  (mail nil bench-send-results-to bench-subject)
  (sit-for 0)
  (goto-char (point-max))
  (insert bench-system-form)
  (insert-buffer buffer)
)
;;; bench.el ends here
