Mercurial > emacs
changeset 655:02591d9e7ad3
Initial revision
author | Eric S. Raymond <esr@snark.thyrsus.com> |
---|---|
date | Sat, 30 May 1992 18:52:42 +0000 |
parents | 6d56ce1261a7 |
children | d74e65773062 |
files | lisp/emacs-lisp/cust-print.el lisp/emacs-lisp/profile.el |
diffstat | 2 files changed, 924 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/emacs-lisp/cust-print.el Sat May 30 18:52:42 1992 +0000 @@ -0,0 +1,569 @@ +;; cus-print.el -- handles print-level and print-circle. + +;; LCD Archive Entry: +;; custom-print|Daniel LaLiberte|liberte@cs.uiuc.edu +;; |Handle print-level, print-circle and more. +;; |$Date: Tue Mar 17, 1992$|$Revision: 1.0$| + +;; Copyright (C) 1992 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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 1, or (at your option) +;; any later version. + +;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +;; This package provides a general print handler for prin1 and princ +;; that supports print-level and print-circle, and by the way, +;; print-length since the standard routines are being replaced. Also, +;; to print custom types constructed from lists and vectors, use +;; custom-print-list and custom-print-vector. See the documentation +;; strings of these variables for more details. + +;; If the results of your expressions contain circular references to +;; other parts of the same structure, the standard Emacs print +;; subroutines may fail to print with an untrappable error, +;; "Apparently circular structure being printed". If you only use cdr +;; circular lists (where cdrs of lists point back; what is the right +;; term here?), you can limit the length of printing with +;; print-length. But car circular lists and circular vectors generate +;; the above mentioned untrappable error in Emacs version 18. Version +;; 19 will support print-level, but it is often useful to get a better +;; print representation of circular structures; the print-circle +;; option may be used to print more concise representations. + +;; There are two main ways to use this package. First, you may +;; replace prin1, princ, and some subroutines that use them by calling +;; install-custom-print-funcs so that any use of these functions in +;; lisp code will be affected. Second, you could call the custom +;; routines directly, thus only affecting the printing that requires +;; them. + +;; Note that subroutines which call print subroutines directly will not +;; use the custom print functions. In particular, the evaluation +;; functions like eval-region call the print subroutines directly. +;; Therefore, evaluating (aref circ-list 0), which calls error +;; directly (because circ-list is not an array), will jump to the top +;; level instead of printing the circular list. + +;; Obviously the right way to implement this custom-print facility +;; is in C. Please volunteer since I don't have the time or need. + +;; Implementation design: we want to use the same list and vector +;; processing algorithm for all versions of prin1 and princ, since how +;; the processing is done depends on print-length, print-level, and +;; print-circle. For circle printing, a preprocessing step is +;; required before the final printing. Thanks to Jamie Zawinski +;; for motivation and algorithms. + +;;========================================================= +;; export list: + +;; print-level +;; print-circle + +;; custom-print-list +;; custom-print-vector +;; add-custom-print-list +;; add-custom-print-vector + +;; install-custom-print-funcs +;; uninstall-custom-print-funcs + +;; custom-prin1 +;; custom-princ +;; custom-prin1-to-string +;; custom-print +;; custom-format +;; custom-message +;; custom-error + + +(provide 'custom-print) +;; Abbreviated package name: "CP" + +;;(defvar print-length nil +;; "*Controls how many elements of a list, at each level, are printed. +;;This is defined by emacs.") + +(defvar print-level nil + "*Controls how many levels deep a nested data object will print. + +If nil, printing proceeds recursively and may lead to +max-lisp-eval-depth being exceeded or an untrappable error may occur: +\"Apparently circular structure being printed.\" Also see +print-length and print-circle. + +If non-nil, components at levels equal to or greater than print-level +are printed simply as \"#\". The object to be printed is at level 0, +and if the object is a list or vector, its top-level components are at +level 1.") + + +(defvar print-circle nil + "*Controls the printing of recursive structures. + +If nil, printing proceeds recursively and may lead to +max-lisp-eval-depth being exceeded or an untrappable error may occur: +\"Apparently circular structure being printed.\" Also see +print-length and print-level. + +If non-nil, shared substructures anywhere in the structure are printed +with \"#n=\" before the first occurance (in the order of the print +representation) and \"#n#\" in place of each subsequent occurance, +where n is a positive decimal integer. + +Currently, there is no way to read this representation in Emacs.") + + +(defconst custom-print-list + nil + ;; e.g. '((floatp . float-to-string)) + "If non-nil, an alist for printing of custom list objects. +Pairs are of the form (pred . converter). If the predicate is true +for an object, the converter is called with the object and should +return a string which will be printed with princ. +Also see custom-print-vector.") + +(defconst custom-print-vector + nil + "If non-nil, an alist for printing of custom vector objects. +Pairs are of the form (pred . converter). If the predicate is true +for an object, the converter is called with the object and should +return a string which will be printed with princ. +Also see custom-print-list.") + + +(defun add-custom-print-list (pred converter) + "Add the pair, a PREDICATE and a CONVERTER, to custom-print-list. +Any pair that has the same PREDICATE is first removed." + (setq custom-print-list (cons (cons pred converter) + (delq (assq pred custom-print-list) + custom-print-list)))) +;; e.g. (add-custom-print-list 'floatp 'float-to-string) + + +(defun add-custom-print-vector (pred converter) + "Add the pair, a PREDICATE and a CONVERTER, to custom-print-vector. +Any pair that has the same PREDICATE is first removed." + (setq custom-print-vector (cons (cons pred converter) + (delq (assq pred custom-print-vector) + custom-print-vector)))) + + +;;==================================================== +;; Saving and restoring internal printing routines. + +(defun CP::set-function-cell (symbol-pair) + (fset (car symbol-pair) + (symbol-function (car (cdr symbol-pair))))) + + +(if (not (fboundp 'CP::internal-prin1)) + (mapcar 'CP::set-function-cell + '((CP::internal-prin1 prin1) + (CP::internal-princ princ) + (CP::internal-print print) + (CP::internal-prin1-to-string prin1-to-string) + (CP::internal-format format) + (CP::internal-message message) + (CP::internal-error error)))) + + +(defun install-custom-print-funcs () + "Replace print functions with general, customizable, lisp versions. +The internal subroutines are saved away and may be recovered with +uninstall-custom-print-funcs." + (interactive) + (mapcar 'CP::set-function-cell + '((prin1 custom-prin1) + (princ custom-princ) + (print custom-print) + (prin1-to-string custom-prin1-to-string) + (format custom-format) + (message custom-message) + (error custom-error) + ))) + +(defun uninstall-custom-print-funcs () + "Reset print functions to their internal subroutines." + (interactive) + (mapcar 'CP::set-function-cell + '((prin1 CP::internal-prin1) + (princ CP::internal-princ) + (print CP::internal-print) + (prin1-to-string CP::internal-prin1-to-string) + (format CP::internal-format) + (message CP::internal-message) + (error CP::internal-error) + ))) + + +;;=============================================================== +;; Lisp replacements for prin1 and princ and for subrs that use prin1 +;; (or princ) -- so far only the printing and formatting subrs. + +(defun custom-prin1 (object &optional stream) + "Replacement for standard prin1 that uses the appropriate +printer depending on the values of print-level and print-circle (which see). + +Output the printed representation of OBJECT, any Lisp object. +Quoting characters are printed when needed to make output that `read' +can handle, whenever this is possible. +Output stream is STREAM, or value of `standard-output' (which see)." + (CP::top-level object stream 'CP::internal-prin1)) + + +(defun custom-princ (object &optional stream) + "Same as custom-prin1 except no quoting." + (CP::top-level object stream 'CP::internal-princ)) + +(defun custom-prin1-to-string-func (c) + "Stream function for custom-prin1-to-string." + (setq prin1-chars (cons c prin1-chars))) + +(defun custom-prin1-to-string (object) + "Replacement for standard prin1-to-string." + (let ((prin1-chars nil)) + (custom-prin1 object 'custom-prin1-to-string-func) + (concat (nreverse prin1-chars)))) + + +(defun custom-print (object &optional stream) + "Replacement for standard print." + (CP::internal-princ "\n") + (custom-prin1 object stream) + (CP::internal-princ "\n")) + + +(defun custom-format (fmt &rest args) + "Replacement for standard format. + +Calls format after first making strings for list or vector args. +The format specification for such args should be %s in any case, so a +string argument will also work. The string is generated with +custom-prin1-to-string, which quotes quotable characters." + (apply 'CP::internal-format fmt + (mapcar (function (lambda (arg) + (if (or (listp arg) (vectorp arg)) + (custom-prin1-to-string arg) + arg))) + args))) + + + +(defun custom-message (fmt &rest args) + "Replacement for standard message that works like custom-format." + ;; It doesnt work to princ the result of custom-format + ;; because the echo area requires special handling + ;; to avoid duplicating the output. CP::internal-message does it right. + ;; (CP::internal-princ (apply 'custom-format fmt args)) + (apply 'CP::internal-message fmt + (mapcar (function (lambda (arg) + (if (or (listp arg) (vectorp arg)) + (custom-prin1-to-string arg) + arg))) + args))) + + +(defun custom-error (fmt &rest args) + "Replacement for standard error that uses custom-format" + (signal 'error (list (apply 'custom-format fmt args)))) + + +;;========================================= +;; Support for custom prin1 and princ + +(defun CP::top-level (object stream internal-printer) + "Set up for printing." + (let ((standard-output (or stream standard-output)) + (circle-table (and print-circle (CP::preprocess-circle-tree object))) + (level (or print-level -1)) + ) + + (fset 'CP::internal-printer internal-printer) + (fset 'CP::low-level-prin + (cond + ((or custom-print-list + custom-print-vector + print-level ; comment out for version 19 + ) + 'CP::custom-object) + (circle-table + 'CP::object) + (t 'CP::internal-printer))) + (fset 'CP::prin (if circle-table 'CP::circular 'CP::low-level-prin)) + + (CP::prin object) + object)) + + +(defun CP::object (object) + "Test object type and print accordingly." + ;; Could be called as either CP::low-level-prin or CP::prin. + (cond + ((null object) (CP::internal-printer object)) + ((consp object) (CP::list object)) + ((vectorp object) (CP::vector object)) + ;; All other types, just print. + (t (CP::internal-printer object)))) + + +(defun CP::custom-object (object) + "Test object type and print accordingly." + ;; Could be called as either CP::low-level-prin or CP::prin. + (cond + ((null object) (CP::internal-printer object)) + + ((consp object) + (or (and custom-print-list + (CP::custom-object1 object custom-print-list)) + (CP::list object))) + + ((vectorp object) + (or (and custom-print-vector + (CP::custom-object1 object custom-print-vector)) + (CP::vector object))) + + ;; All other types, just print. + (t (CP::internal-printer object)))) + + +(defun CP::custom-object1 (object alist) + "Helper for CP::custom-object. +Print the custom OBJECT using the custom type ALIST. +For the first predicate that matches the object, the corresponding +converter is evaluated with the object and the string that results is +printed with princ. Return nil if no predicte matches the object." + (while (and alist (not (funcall (car (car alist)) object))) + (setq alist (cdr alist))) + ;; If alist is not null, then something matched. + (if alist + (CP::internal-princ + (funcall (cdr (car alist)) object) ; returns string + ))) + + +(defun CP::circular (object) + "Printer for prin1 and princ that handles circular structures. +If OBJECT appears multiply, and has not yet been printed, +prefix with label; if it has been printed, use #n# instead. +Otherwise, print normally." + (let ((tag (assq object circle-table))) + (if tag + (let ((id (cdr tag))) + (if (> id 0) + (progn + ;; Already printed, so just print id. + (CP::internal-princ "#") + (CP::internal-princ id) + (CP::internal-princ "#")) + ;; Not printed yet, so label with id and print object. + (setcdr tag (- id)) ; mark it as printed + (CP::internal-princ "#") + (CP::internal-princ (- id)) + (CP::internal-princ "=") + (CP::low-level-prin object) + )) + ;; Not repeated in structure. + (CP::low-level-prin object)))) + + +;;================================================ +;; List and vector processing for print functions. + +(defun CP::list (list) + "Print a list using print-length, print-level, and print-circle." + (if (= level 0) + (CP::internal-princ "#") + (let ((level (1- level))) + (CP::internal-princ "(") + (let ((length (or print-length 0))) + + ;; Print the first element always (even if length = 0). + (CP::prin (car list)) + (setq list (cdr list)) + (if list (CP::internal-princ " ")) + (setq length (1- length)) + + ;; Print the rest of the elements. + (while (and list (/= 0 length)) + (if (and (listp list) + (not (assq list circle-table))) + (progn + (CP::prin (car list)) + (setq list (cdr list))) + + ;; cdr is not a list, or it is in circle-table. + (CP::internal-princ ". ") + (CP::prin list) + (setq list nil)) + + (setq length (1- length)) + (if list (CP::internal-princ " "))) + + (if (and list (= length 0)) (CP::internal-princ "...")) + (CP::internal-princ ")")))) + list) + + +(defun CP::vector (vector) + "Print a vector using print-length, print-level, and print-circle." + (if (= level 0) + (CP::internal-princ "#") + (let ((level (1- level)) + (i 0) + (len (length vector))) + (CP::internal-princ "[") + + (if print-length + (setq len (min print-length len))) + ;; Print the elements + (while (< i len) + (CP::prin (aref vector i)) + (setq i (1+ i)) + (if (< i (length vector)) (CP::internal-princ " "))) + + (if (< i (length vector)) (CP::internal-princ "...")) + (CP::internal-princ "]") + )) + vector) + + +;;================================== +;; Circular structure preprocessing + +(defun CP::preprocess-circle-tree (object) + ;; Fill up the table. + (let (;; Table of tags for each object in an object to be printed. + ;; A tag is of the form: + ;; ( <object> <nil-t-or-id-number> ) + ;; The id-number is generated after the entire table has been computed. + ;; During walk through, the real circle-table lives in the cdr so we + ;; can use setcdr to add new elements instead of having to setq the + ;; variable sometimes (poor man's locf). + (circle-table (list nil))) + (CP::walk-circle-tree object) + + ;; Reverse table so it is in the order that the objects will be printed. + ;; This pass could be avoided if we always added to the end of the + ;; table with setcdr in walk-circle-tree. + (setcdr circle-table (nreverse (cdr circle-table))) + + ;; Walk through the table, assigning id-numbers to those + ;; objects which will be printed using #N= syntax. Delete those + ;; objects which will be printed only once (to speed up assq later). + (let ((rest circle-table) + (id -1)) + (while (cdr rest) + (let ((tag (car (cdr rest)))) + (cond ((cdr tag) + (setcdr tag id) + (setq id (1- id)) + (setq rest (cdr rest))) + ;; Else delete this object. + (t (setcdr rest (cdr (cdr rest)))))) + )) + ;; Drop the car. + (cdr circle-table) + )) + + + +(defun CP::walk-circle-tree (object) + (let (read-equivalent-p tag) + (while object + (setq read-equivalent-p (or (numberp object) (symbolp object)) + tag (and (not read-equivalent-p) + (assq object (cdr circle-table)))) + (cond (tag + ;; Seen this object already, so note that. + (setcdr tag t)) + + ((not read-equivalent-p) + ;; Add a tag for this object. + (setcdr circle-table + (cons (list object) + (cdr circle-table))))) + (setq object + (cond + (tag ;; No need to descend since we have already. + nil) + + ((consp object) + ;; Walk the car of the list recursively. + (CP::walk-circle-tree (car object)) + ;; But walk the cdr with the above while loop + ;; to avoid problems with max-lisp-eval-depth. + ;; And it should be faster than recursion. + (cdr object)) + + ((vectorp object) + ;; Walk the vector. + (let ((i (length object)) + (j 0)) + (while (< j i) + (CP::walk-circle-tree (aref object j)) + (setq j (1+ j)))))))))) + + + +;;======================================= + +(quote + examples + + (progn + ;; Create some circular structures. + (setq circ-sym (let ((x (make-symbol "FOO"))) (list x x))) + (setq circ-list (list 'a 'b (vector 1 2 3 4) 'd 'e 'f)) + (setcar (nthcdr 3 circ-list) circ-list) + (aset (nth 2 circ-list) 2 circ-list) + (setq dotted-circ-list (list 'a 'b 'c)) + (setcdr (cdr (cdr dotted-circ-list)) dotted-circ-list) + (setq circ-vector (vector 1 2 3 4 (list 'a 'b 'c 'd) 6 7)) + (aset circ-vector 5 (make-symbol "-gensym-")) + (setcar (cdr (aref circ-vector 4)) (aref circ-vector 5)) + nil) + + (install-custom-print-funcs) + ;; (setq print-circle t) + + (let ((print-circle t)) + (or (equal (prin1-to-string circ-list) "#1=(a b [1 2 #1# 4] #1# e f)") + (error "circular object with array printing"))) + + (let ((print-circle t)) + (or (equal (prin1-to-string dotted-circ-list) "#1=(a b c . #1#)") + (error "circular object with array printing"))) + + (let* ((print-circle t) + (x (list 'p 'q)) + (y (list (list 'a 'b) x 'foo x))) + (setcdr (cdr (cdr (cdr y))) (cdr y)) + (or (equal (prin1-to-string y) "((a b) . #1=(#2=(p q) foo #2# . #1#))" + ) + (error "circular list example from CL manual"))) + + ;; There's no special handling of uninterned symbols in custom-print. + (let ((print-circle nil)) + (or (equal (prin1-to-string circ-sym) "(#:FOO #:FOO)") + (error "uninterned symbols in list"))) + (let ((print-circle t)) + (or (equal (prin1-to-string circ-sym) "(#1=FOO #1#)") + (error "circular uninterned symbols in list"))) + + (uninstall-custom-print-funcs) + ) + +;;; cus-print.el ends here
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/emacs-lisp/profile.el Sat May 30 18:52:42 1992 +0000 @@ -0,0 +1,355 @@ +;;; profile.el -- generate run time measurements of elisp functions +;;; +;;; Author: Boaz Ben-Zvi <boaz@lcs.mit.edu> +;;; Created: Feb. 7, 1992 +;;; Last Modified: Feb. 7, 1992 +;;; Version: 1.0 + +;; Copyright (C) 1992 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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 1, or (at your option) +;; any later version. + +;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + + +; DESCRIPTION: +; ------------ +; This program can be used to monitor running time performance of elisp +; functions. It takes a list of functions and report the real time spent +; inside these functions. It runs a process with a separate timer program. +; Caveat: the C code included with this package requires BSD-compatible +; time-of-day functions. If you're running an AT&T version prior to SVr4, +; you may have difficulty getting it to work. Your X library may supply +; the required routines if the standard C library does not. + +; HOW TO USE: +; ----------- +; Set the variable profile-functions-list to the list of functions +; (as symbols) You want to profile. Call M-x profile-functions to set +; this list on and start using your program. Note that profile-functions +; MUST be called AFTER all the functions in profile-functions-list have +; been loaded !! (This call modifies the code of the profiled functions. +; Hence if you reload these functions, you need to call profile-functions +; again! ). +; To display the results do M-x profile-results . For example: +;------------------------------------------------------------------- +; (setq profile-functions-list '(sokoban-set-mode-line sokoban-load-game +; sokoban-move-vertical sokoban-move)) +; (load "sokoban") +; M-x profile-functions +; ... I play the sokoban game .......... +; M-x profile-results +; +; Function Time (Seconds.Useconds) +; ======== ======================= +; sokoban-move 0.539088 +; sokoban-move-vertical 0.410130 +; sokoban-load-game 0.453235 +; sokoban-set-mode-line 1.949203 +;----------------------------------------------------- +; To clear all the settings to profile use profile-finish. +; To set one function at a time (instead of or in addition to setting the +; above list and M-x profile-functions ) use M-x profile-a-function . + +; HOW TO INSTALL: +; --------------- +; First you need to compile and install the following C program in your +; path under the name "emacs-timer" (or set the variable +; profile-timer-program to whatever name you picked). +; +;/** +; ** To be run as an emacs process. Input string that starts with: +; ** 'z' -- resets the watch (to zero). +; ** 'p' -- return time (on stdout) as string with format <sec>.<micro-sec> +; ** 'q' -- exit. +; ** +; ** abstraction : a stopwatch +; ** operations: reset_watch, get_time +; */ +;#include <strings.h> +;#include <sys/time.h> +;#include <stdio.h> +;static struct timeval TV1,TV2; +;static struct timezone *tzp = (struct timezone *) NULL; /* no need timezone */ +;static int watch_not_started = 1 ; /* flag */ +;static char time_string[30] +; +;int reset_watch() /* this call resets the stopwatch to zero */ +;{ +; gettimeofday(&TV1, tzp) ; +; watch_not_started = 0; +;} +; +;char *get_time() +; /* this call returns the time since the last reset_watch() call. The time +; is returned as a string with the format <seconds>.<micro-seconds> +; If reset_watch() was not called yet, returns NULL */ +;{ +; char *result = time_string ; +; int i; +; if (watch_not_started) return((char *) 0); /* call reset_watch first ! */ +; gettimeofday(&TV2, tzp); +; if ( TV1.tv_usec > TV2.tv_usec ) +; { +; TV2.tv_usec += 1000000; +; TV2.tv_sec--; +; } +; sprintf(result,"%lu.%6lu", +; TV2.tv_sec - TV1.tv_sec, TV2.tv_usec - TV1.tv_usec); +; for (result = index(result,'.') + 1 ; *result == ' ' ; result++ ) +; *result = '0'; +; return(time_string); +;} +; +;void main() +;{ +; char inp[10]; +; while (1) +; { +; gets(inp); +; switch (inp[0]) +; { +; case 'z': reset_watch(); +; break; +; case 'p': puts(get_time()); +; break; +; case 'q': exit(0); +; } +; } +;} +; -------- end of clip ---------------- + +;;; +;;; User modifiable VARIABLES +;;; + +(defvar profile-functions-list nil "*List of functions to profile") +(defvar profile-timer-program "emacs-timer" "*Name of the timer program") + +;;; +;;; V A R I A B L E S +;;; + +(defvar profile-timer-process nil "Process running the timer") +(defvar profile-time-list nil + "List of accumulative time for each profiled function") +(defvar profile-init-list nil + "List of entry time for each function. \n\ +Both how many times invoked and real time of start.") +(defvar profile-max-fun-name 0 "Max length of name of any function profiled") +(defvar profile-temp-result- nil "Should NOT be used anywhere else") +(defvar profile-time (cons 0 0) "Used to return result from a filter") +(defvar profile-buffer "*profile*" "Name of profile buffer") + +;;; +;;; F U N C T I O N S +;;; + +(defun profile-functions (&optional flist) + "Profile all the functions listed in profile-functions-list.\n\ +With argument FLIST, use the list FLIST instead." + (interactive "*P") + (if (null flist) (setq flist profile-functions-list)) + (mapcar 'profile-a-function flist)) + +(defun profile-filter (process input) + "Filter for the timer process. Sets profile-time to the returned time." + (if (zerop (string-match "\\." input)) + (error "Bad output from %s" profile-timer-program) + (setcar profile-time + (string-to-int (substring input 0 (match-beginning 0)))) + (setcdr profile-time + (string-to-int (substring input (match-end 0)))))) + + +(defun profile-print (entry) + "Print one ENTRY (from profile-time-list) ." + (let ((time (cdr entry)) str (offset 5)) + (insert (format "%s" (car entry)) space) + (move-to-column ref-column) + (setq str (int-to-string (car time))) + (insert str) + (if (>= (length str) offset) nil + (move-to-column ref-column) + (insert (substring spaces 0 (- offset (length str)))) + (forward-char (length str))) + (setq str (int-to-string (cdr time))) + (insert "." (substring "000000" 0 (- 6 (length str))) str "\n") + )) + +(defconst spaces " ") + +(defun profile-results () + "Display profiling results in profile-buffer ." + (interactive) + (let* ((ref-column (+ 8 profile-max-fun-name)) + (space (substring spaces 0 ref-column))) + (switch-to-buffer profile-buffer) + (erase-buffer) + (insert "Function" space) + (move-to-column ref-column) + (insert "Time (Seconds.Useconds)\n" "========" space ) + (move-to-column ref-column) + (insert "=======================\n") + (mapcar 'profile-print profile-time-list))) + +(defun profile-reset-timer () + (process-send-string profile-timer-process "z\n")) + +(defun profile-check-zero-init-times (entry) + "If ENTRY has non zero time, give an error." + (let ((time (cdr (cdr entry)))) + (if (and (zerop (car time)) (zerop (cdr time))) nil ; OK + (error "Process timer died while making performance profile.")))) + +(defun profile-get-time () + "Get time from timer process into profile-time ." + ;; first time or if process dies + (if (and (processp profile-timer-process) + (eq 'run (process-status profile-timer-process))) nil + (setq profile-timer-process ;; [re]start the timer process + (start-process "timer" + (get-buffer-create profile-buffer) + profile-timer-program)) + (set-process-filter profile-timer-process 'profile-filter) + (process-kill-without-query profile-timer-process) + (profile-reset-timer) + ;; check if timer died during time measurement + (mapcar 'profile-check-zero-init-times profile-init-list)) + ;; make timer process return current time + (process-send-string profile-timer-process "p\n") + (accept-process-output)) + +(defun profile-find-function (fun flist) + "Linear search for FUN in FLIST ." + (if (null flist) nil + (if (eq fun (car (car flist))) (cdr (car flist)) + (profile-find-function fun (cdr flist))))) + +(defun profile-start-function (fun) + "On entry, keep current time for function FUN." + ;; assumes that profile-time contains the current time + (let ((init-time (profile-find-function fun profile-init-list))) + (if (null init-time) (error "Function %s missing from list" fun)) + (if (not (zerop (car init-time))) ;; is it a recursive call ? + (setcar init-time (1+ (car init-time))) + (setcar init-time 1) ; mark first entry + (setq init-time (cdr init-time)) + (setcar init-time (car profile-time)) + (setcdr init-time (cdr profile-time))) + )) + +(defconst profile-million 1000000) + +(defun profile-update-function (fun) + "When the call to the function FUN is finished, add its run time." + ;; assumes that profile-time contains the current time + (let ((init-time (profile-find-function fun profile-init-list)) + (accum (profile-find-function fun profile-time-list)) + sec usec) + (if (or (null init-time) + (null accum)) (error "Function %s missing from list" fun)) + (setcar init-time (1- (car init-time))) ; pop one level in recursion + (if (not (zerop (car init-time))) + nil ; in some recursion level, do not update accum. time + (setq init-time (cdr init-time)) + (setq sec (- (car profile-time) (car init-time)) + usec (- (cdr profile-time) (cdr init-time))) + (setcar init-time 0) ; reset time to check for error + (setcdr init-time 0) ; in case timer process dies + (if (>= usec 0) nil + (setq usec (+ usec profile-million)) + (setq sec (1- sec))) + (setcar accum (+ sec (car accum))) + (setcdr accum (+ usec (cdr accum))) + (if (< (cdr accum) profile-million) nil + (setcar accum (1+ (car accum))) + (setcdr accum (- (cdr accum) profile-million))) + ))) + +(defun profile-a-function (fun) + "Profile the function FUN" + (interactive "aFunction to profile: ") + (let ((def (symbol-function fun)) (funlen (length (symbol-name fun)))) + (if (eq (car def) 'lambda) nil + (error "To profile: %s must be a user-defined function" fun)) + (setq profile-time-list ; add a new entry + (cons (cons fun (cons 0 0)) profile-time-list)) + (setq profile-init-list ; add a new entry + (cons (cons fun (cons 0 (cons 0 0))) profile-init-list)) + (if (< profile-max-fun-name funlen) (setq profile-max-fun-name funlen)) + (fset fun (profile-fix-fun fun def)))) + +(defun profile-fix-fun (fun def) + "Take function FUN and return it fixed for profiling.\n\ +DEF is (symbol-function FUN) ." + (let (prefix first second third (count 2) inter suffix) + (if (< (length def) 3) nil ; nothing to see + (setq first (car def) second (car (cdr def)) + third (car (nthcdr 2 def))) + (setq prefix (list first second)) + (if (and (stringp third) (< (length def) 3)) nil ; nothing to see + (if (not (stringp third)) (setq inter third) + (setq count 3 ; suffix to start after doc string + prefix (nconc prefix (list third)) + inter (car (nthcdr 3 def))) ; fourth sexp + ) + (if (not (and (listp inter) + (eq (car inter) 'interactive))) nil + (setq prefix (nconc prefix (list inter))) + (setq count (1+ count))) ; skip this sexp for suffix + (setq suffix (nthcdr count def)) + (if (equal (car suffix) '(profile-get-time)) nil ;; already set + ;; prepare new function + (nconc prefix + (list '(profile-get-time)) ; read time + (list (list 'profile-start-function + (list 'quote fun))) + (list (list 'setq 'profile-temp-result- + (nconc (list 'progn) suffix))) + (list '(profile-get-time)) ; read time + (list (list 'profile-update-function + (list 'quote fun))) + (list 'profile-temp-result-) + )))))) + +(defun profile-restore-fun (fun) + "Restore profiled function FUN to its original state." + (let ((def (symbol-function (car fun))) body index) + ;; move index beyond header + (setq index (cdr def)) + (if (stringp (car (cdr index))) (setq index (cdr index))) + (if (and (listp (car (cdr index))) + (eq (car (car (cdr index))) 'interactive)) + (setq index (cdr index))) + (setq body (car (nthcdr 3 index))) + (if (and (listp body) ; the right element ? + (eq (car (cdr body)) 'profile-temp-result-)) + (setcdr index (cdr (car (cdr (cdr body)))))))) + +(defun profile-finish () + "Stop profiling functions. Clear all the settings." + (interactive) + (mapcar 'profile-restore-fun profile-time-list) + (setq profile-max-fun-name 0) + (setq profile-time-list nil) + (setq profile-init-list nil)) + +(defun profile-quit () + "Kill the timer process." + (interactive) + (process-send-string profile-timer-process "q\n")) + +;; profile.el ends here