1;;; elp.el --- Emacs Lisp Profiler
2
3;; Copyright (C) 1994, 1995, 1997, 1998, 2001, 2002, 2003, 2004,
4;;   2005, 2006, 2007 Free Software Foundation, Inc.
5
6;; Author: Barry A. Warsaw
7;; Maintainer: FSF
8;; Created: 26-Feb-1994
9;; Keywords: debugging lisp tools
10
11;; This file is part of GNU Emacs.
12
13;; GNU Emacs is free software; you can redistribute it and/or modify
14;; it under the terms of the GNU General Public License as published by
15;; the Free Software Foundation; either version 2, or (at your option)
16;; any later version.
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
24;; along with GNU Emacs; see the file COPYING.  If not, write to the
25;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26;; Boston, MA 02110-1301, USA.
27
28;;; Commentary:
29;;
30;; If you want to profile a bunch of functions, set elp-function-list
31;; to the list of symbols, then do a M-x elp-instrument-list.  This
32;; hacks those functions so that profiling information is recorded
33;; whenever they are called.  To print out the current results, use
34;; M-x elp-results.  If you want output to go to standard-output
35;; instead of a separate buffer, setq elp-use-standard-output to
36;; non-nil.  With elp-reset-after-results set to non-nil, profiling
37;; information will be reset whenever the results are displayed.  You
38;; can also reset all profiling info at any time with M-x
39;; elp-reset-all.
40;;
41;; You can also instrument all functions in a package, provided that
42;; the package follows the GNU coding standard of a common textual
43;; prefix.  Use M-x elp-instrument-package for this.
44;;
45;; If you want to sort the results, set elp-sort-by-function to some
46;; predicate function.  The three most obvious choices are predefined:
47;; elp-sort-by-call-count, elp-sort-by-average-time, and
48;; elp-sort-by-total-time.  Also, you can prune from the output, all
49;; functions that have been called fewer than a given number of times
50;; by setting elp-report-limit.
51;;
52;; Elp can instrument byte-compiled functions just as easily as
53;; interpreted functions, but it cannot instrument macros.  However,
54;; when you redefine a function (e.g. with eval-defun), you'll need to
55;; re-instrument it with M-x elp-instrument-function.  This will also
56;; reset profiling information for that function.  Elp can handle
57;; interactive functions (i.e. commands), but of course any time spent
58;; idling for user prompts will show up in the timing results.
59;;
60;; You can also designate a `master' function.  Profiling times will
61;; be gathered for instrumented functions only during execution of
62;; this master function.  Thus, if you have some defuns like:
63;;
64;;  (defun foo () (do-something-time-intensive))
65;;  (defun bar () (foo))
66;;  (defun baz () (bar) (foo))
67;;
68;; and you want to find out the amount of time spent in bar and foo,
69;; but only during execution of bar, make bar the master.  The call of
70;; foo from baz will not add to foo's total timing sums.  Use M-x
71;; elp-set-master and M-x elp-unset-master to utilize this feature.
72;; Only one master function can be set at a time.
73
74;; You can restore any function's original function definition with
75;; elp-restore-function.  The other instrument, restore, and reset
76;; functions are provided for symmetry.
77
78;; Here is a list of variable you can use to customize elp:
79;;   elp-function-list
80;;   elp-reset-after-results
81;;   elp-sort-by-function
82;;   elp-report-limit
83;;
84;; Here is a list of the interactive commands you can use:
85;;   elp-instrument-function
86;;   elp-restore-function
87;;   elp-instrument-list
88;;   elp-restore-list
89;;   elp-instrument-package
90;;   elp-restore-all
91;;   elp-reset-function
92;;   elp-reset-list
93;;   elp-reset-all
94;;   elp-set-master
95;;   elp-unset-master
96;;   elp-results
97
98;; Note that there are plenty of factors that could make the times
99;; reported unreliable, including the accuracy and granularity of your
100;; system clock, and the overhead spent in lisp calculating and
101;; recording the intervals.  I figure the latter is pretty constant,
102;; so while the times may not be entirely accurate, I think they'll
103;; give you a good feel for the relative amount of work spent in the
104;; various lisp routines you are profiling.  Note further that times
105;; are calculated using wall-clock time, so other system load will
106;; affect accuracy too.
107
108;;; Background:
109
110;; This program was inspired by the only two existing Emacs Lisp
111;; profilers that I'm aware of, Boaz Ben-Zvi's profile.el, and Root
112;; Boy Jim's profiler.el. Both were written for Emacs 18 and both were
113;; pretty good first shots at profiling, but I found that they didn't
114;; provide the functionality or interface that I wanted, so I wrote
115;; this.  I've tested elp in XEmacs 19 and Emacs 19.  There's no point
116;; in even trying to make this work with Emacs 18.
117
118;; Unlike previous profilers, elp uses Emacs 19's built-in function
119;; current-time to return interval times.  This obviates the need for
120;; both an external C program and Emacs processes to communicate with
121;; such a program, and thus simplifies the package as a whole.
122
123;; TBD:
124;; Make this act like a real profiler, so that it records time spent
125;; in all branches of execution.
126
127;;; Code:
128
129
130;; start of user configuration variables
131;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
132
133(defgroup elp nil
134  "Emacs Lisp Profiler."
135  :group 'lisp)
136
137(defcustom elp-function-list nil
138  "*List of functions to profile.
139Used by the command `elp-instrument-list'."
140  :type '(repeat function)
141  :group 'elp)
142
143(defcustom elp-reset-after-results t
144  "*Non-nil means reset all profiling info after results are displayed.
145Results are displayed with the `elp-results' command."
146  :type 'boolean
147  :group 'elp)
148
149(defcustom elp-sort-by-function 'elp-sort-by-total-time
150  "*Non-nil specifies elp results sorting function.
151These functions are currently available:
152
153  elp-sort-by-call-count   -- sort by the highest call count
154  elp-sort-by-total-time   -- sort by the highest total time
155  elp-sort-by-average-time -- sort by the highest average times
156
157You can write you're own sort function. It should adhere to the
158interface specified by the PRED argument for the `sort' defun.  Each
159\"element of LIST\" is really a 4 element vector where element 0 is
160the call count, element 1 is the total time spent in the function,
161element 2 is the average time spent in the function, and element 3 is
162the symbol's name string."
163  :type 'function
164  :group 'elp)
165
166(defcustom elp-report-limit 1
167  "*Prevents some functions from being displayed in the results buffer.
168If a number, no function that has been called fewer than that number
169of times will be displayed in the output buffer.  If nil, all
170functions will be displayed."
171  :type '(choice integer
172		 (const :tag "Show All" nil))
173  :group 'elp)
174
175(defcustom elp-use-standard-output nil
176  "*Non-nil says to output to `standard-output' instead of a buffer."
177  :type 'boolean
178  :group 'elp)
179
180(defcustom elp-recycle-buffers-p t
181  "*nil says to not recycle the `elp-results-buffer'.
182In other words, a new unique buffer is create every time you run
183\\[elp-results]."
184  :type 'boolean
185  :group 'elp)
186
187
188;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
189;; end of user configuration variables
190
191
192(defvar elp-results-buffer "*ELP Profiling Results*"
193  "Buffer name for outputting profiling results.")
194
195(defconst elp-timer-info-property 'elp-info
196  "ELP information property name.")
197
198(defvar elp-all-instrumented-list nil
199  "List of all functions currently being instrumented.")
200
201(defvar elp-record-p t
202  "Controls whether functions should record times or not.
203This variable is set by the master function.")
204
205(defvar elp-master nil
206  "Master function symbol.")
207
208(defvar elp-not-profilable
209  ;; First, the functions used inside each instrumented function:
210  '(elp-wrapper called-interactively-p
211    ;; Then the functions used by the above functions.  I used
212    ;; (delq nil (mapcar (lambda (x) (and (symbolp x) (fboundp x) x))
213    ;;                   (aref (symbol-function 'elp-wrapper) 2)))
214    ;; to help me find this list.
215    error call-interactively apply current-time)
216  "List of functions that cannot be profiled.
217Those functions are used internally by the profiling code and profiling
218them would thus lead to infinite recursion.")
219
220(defun elp-profilable-p (fun)
221  (and (symbolp fun)
222       (fboundp fun)
223       (not (or (memq fun elp-not-profilable)
224                (keymapp fun)
225                (memq (car-safe (symbol-function fun)) '(autoload macro))
226                (condition-case nil
227                    (when (subrp (indirect-function fun))
228                      (eq 'unevalled
229                          (cdr (subr-arity (indirect-function fun)))))
230                  (error nil))))))
231
232
233;;;###autoload
234(defun elp-instrument-function (funsym)
235  "Instrument FUNSYM for profiling.
236FUNSYM must be a symbol of a defined function."
237  (interactive "aFunction to instrument: ")
238  ;; restore the function.  this is necessary to avoid infinite
239  ;; recursion of already instrumented functions (i.e. elp-wrapper
240  ;; calling elp-wrapper ad infinitum).  it is better to simply
241  ;; restore the function than to throw an error.  this will work
242  ;; properly in the face of eval-defun because if the function was
243  ;; redefined, only the timer info will be nil'd out since
244  ;; elp-restore-function is smart enough not to trash the new
245  ;; definition.
246  (elp-restore-function funsym)
247  (let* ((funguts (symbol-function funsym))
248	 (infovec (vector 0 0 funguts))
249	 (newguts '(lambda (&rest args))))
250    ;; we cannot profile macros
251    (and (eq (car-safe funguts) 'macro)
252	 (error "ELP cannot profile macro: %s" funsym))
253    ;; TBD: at some point it might be better to load the autoloaded
254    ;; function instead of throwing an error.  if we do this, then we
255    ;; probably want elp-instrument-package to be updated with the
256    ;; newly loaded list of functions.  i'm not sure it's smart to do
257    ;; the autoload here, since that could have side effects, and
258    ;; elp-instrument-function is similar (in my mind) to defun-ish
259    ;; type functionality (i.e. it shouldn't execute the function).
260    (and (eq (car-safe funguts) 'autoload)
261	 (error "ELP cannot profile autoloaded function: %s" funsym))
262    ;; We cannot profile functions used internally during profiling.
263    (unless (elp-profilable-p funsym)
264      (error "ELP cannot profile the function: %s" funsym))
265    ;; put rest of newguts together
266    (if (commandp funsym)
267	(setq newguts (append newguts '((interactive)))))
268    (setq newguts (append newguts `((elp-wrapper
269				     (quote ,funsym)
270				     ,(when (commandp funsym)
271					'(called-interactively-p))
272				     args))))
273    ;; to record profiling times, we set the symbol's function
274    ;; definition so that it runs the elp-wrapper function with the
275    ;; function symbol as an argument.  We place the old function
276    ;; definition on the info vector.
277    ;;
278    ;; The info vector data structure is a 3 element vector.  The 0th
279    ;; element is the call-count, i.e. the total number of times this
280    ;; function has been entered.  This value is bumped up on entry to
281    ;; the function so that non-local exists are still recorded. TBD:
282    ;; I haven't tested non-local exits at all, so no guarantees.
283    ;;
284    ;; The 1st element is the total amount of time in usecs that have
285    ;; been spent inside this function.  This number is added to on
286    ;; function exit.
287    ;;
288    ;; The 2nd element is the old function definition list.  This gets
289    ;; funcall'd in between start/end time retrievals. I believe that
290    ;; this lets us profile even byte-compiled functions.
291
292    ;; put the info vector on the property list
293    (put funsym elp-timer-info-property infovec)
294
295    ;; Set the symbol's new profiling function definition to run
296    ;; elp-wrapper.
297    (let ((advice-info (get funsym 'ad-advice-info)))
298      (if advice-info
299	  (progn
300	    ;; If function is advised, don't let Advice change
301	    ;; its definition from under us during the `fset'.
302	    (put funsym 'ad-advice-info nil)
303	    (fset funsym newguts)
304	    (put funsym 'ad-advice-info advice-info))
305	(fset funsym newguts)))
306
307    ;; add this function to the instrumentation list
308    (unless (memq funsym elp-all-instrumented-list)
309      (push funsym elp-all-instrumented-list))))
310
311(defun elp-restore-function (funsym)
312  "Restore an instrumented function to its original definition.
313Argument FUNSYM is the symbol of a defined function."
314  (interactive "aFunction to restore: ")
315  (let ((info (get funsym elp-timer-info-property)))
316    ;; delete the function from the all instrumented list
317    (setq elp-all-instrumented-list
318	  (delq funsym elp-all-instrumented-list))
319
320    ;; if the function was the master, reset the master
321    (if (eq funsym elp-master)
322	(setq elp-master nil
323	      elp-record-p t))
324
325    ;; zap the properties
326    (put funsym elp-timer-info-property nil)
327
328    ;; restore the original function definition, but if the function
329    ;; wasn't instrumented do nothing.  we do this after the above
330    ;; because its possible the function got un-instrumented due to
331    ;; circumstances beyond our control.  Also, check to make sure
332    ;; that the current function symbol points to elp-wrapper.  If
333    ;; not, then the user probably did an eval-defun, or loaded a
334    ;; byte-compiled version, while the function was instrumented and
335    ;; we don't want to destroy the new definition.  can it ever be
336    ;; the case that a lisp function can be compiled instrumented?
337    (and info
338	 (functionp funsym)
339	 (not (byte-code-function-p (symbol-function funsym)))
340	 (assq 'elp-wrapper (symbol-function funsym))
341	 (fset funsym (aref info 2)))))
342
343;;;###autoload
344(defun elp-instrument-list (&optional list)
345  "Instrument for profiling, all functions in `elp-function-list'.
346Use optional LIST if provided instead."
347  (interactive "PList of functions to instrument: ")
348  (let ((list (or list elp-function-list)))
349    (mapcar 'elp-instrument-function list)))
350
351;;;###autoload
352(defun elp-instrument-package (prefix)
353  "Instrument for profiling, all functions which start with PREFIX.
354For example, to instrument all ELP functions, do the following:
355
356    \\[elp-instrument-package] RET elp- RET"
357  (interactive
358   (list (completing-read "Prefix of package to instrument: "
359                          obarray 'elp-profilable-p)))
360  (if (zerop (length prefix))
361      (error "Instrumenting all Emacs functions would render Emacs unusable"))
362  (elp-instrument-list
363   (mapcar
364    'intern
365    (all-completions prefix obarray 'elp-profilable-p))))
366
367(defun elp-restore-list (&optional list)
368  "Restore the original definitions for all functions in `elp-function-list'.
369Use optional LIST if provided instead."
370  (interactive "PList of functions to restore: ")
371  (let ((list (or list elp-function-list)))
372    (mapcar 'elp-restore-function list)))
373
374(defun elp-restore-all ()
375  "Restores the original definitions of all functions being profiled."
376  (interactive)
377  (elp-restore-list elp-all-instrumented-list))
378
379
380(defun elp-reset-function (funsym)
381  "Reset the profiling information for FUNSYM."
382  (interactive "aFunction to reset: ")
383  (let ((info (get funsym elp-timer-info-property)))
384    (or info
385	(error "%s is not instrumented for profiling" funsym))
386    (aset info 0 0)			;reset call counter
387    (aset info 1 0.0)			;reset total time
388    ;; don't muck with aref 2 as that is the old symbol definition
389    ))
390
391(defun elp-reset-list (&optional list)
392  "Reset the profiling information for all functions in `elp-function-list'.
393Use optional LIST if provided instead."
394  (interactive "PList of functions to reset: ")
395  (let ((list (or list elp-function-list)))
396    (mapcar 'elp-reset-function list)))
397
398(defun elp-reset-all ()
399  "Reset the profiling information for all functions being profiled."
400  (interactive)
401  (elp-reset-list elp-all-instrumented-list))
402
403(defun elp-set-master (funsym)
404  "Set the master function for profiling."
405  (interactive "aMaster function: ")
406  ;; when there's a master function, recording is turned off by
407  ;; default
408  (setq elp-master funsym
409	elp-record-p nil)
410  ;; make sure master function is instrumented
411  (or (memq funsym elp-all-instrumented-list)
412      (elp-instrument-function funsym)))
413
414(defun elp-unset-master ()
415  "Unsets the master function."
416  (interactive)
417  ;; when there's no master function, recording is turned on by default.
418  (setq elp-master nil
419	elp-record-p t))
420
421
422(defsubst elp-elapsed-time (start end)
423  (+ (* (- (car end) (car start)) 65536.0)
424     (- (car (cdr end)) (car (cdr start)))
425     (/ (- (car (cdr (cdr end))) (car (cdr (cdr start)))) 1000000.0)))
426
427(defun elp-wrapper (funsym interactive-p args)
428  "This function has been instrumented for profiling by the ELP.
429ELP is the Emacs Lisp Profiler.  To restore the function to its
430original definition, use \\[elp-restore-function] or \\[elp-restore-all]."
431  ;; turn on recording if this is the master function
432  (if (and elp-master
433	   (eq funsym elp-master))
434      (setq elp-record-p t))
435  ;; get info vector and original function symbol
436  (let* ((info (get funsym elp-timer-info-property))
437	 (func (aref info 2))
438	 result)
439    (or func
440	(error "%s is not instrumented for profiling" funsym))
441    (if (not elp-record-p)
442	;; when not recording, just call the original function symbol
443	;; and return the results.
444	(setq result
445	      (if interactive-p
446		  (call-interactively func)
447		(apply func args)))
448      ;; we are recording times
449      (let (enter-time exit-time)
450	;; increment the call-counter
451	(aset info 0 (1+ (aref info 0)))
452	;; now call the old symbol function, checking to see if it
453	;; should be called interactively.  make sure we return the
454	;; correct value
455	(if interactive-p
456	    (setq enter-time (current-time)
457		  result (call-interactively func)
458		  exit-time (current-time))
459	  (setq enter-time (current-time)
460		result (apply func args)
461		exit-time (current-time)))
462	;; calculate total time in function
463	(aset info 1 (+ (aref info 1) (elp-elapsed-time enter-time exit-time)))
464	))
465    ;; turn off recording if this is the master function
466    (if (and elp-master
467	     (eq funsym elp-master))
468	(setq elp-record-p nil))
469    result))
470
471
472;; shut the byte-compiler up
473(defvar elp-field-len nil)
474(defvar elp-cc-len nil)
475(defvar elp-at-len nil)
476(defvar elp-et-len nil)
477
478(defun elp-sort-by-call-count (vec1 vec2)
479  ;; sort by highest call count.  See `sort'.
480  (>= (aref vec1 0) (aref vec2 0)))
481
482(defun elp-sort-by-total-time (vec1 vec2)
483  ;; sort by highest total time spent in function. See `sort'.
484  (>= (aref vec1 1) (aref vec2 1)))
485
486(defun elp-sort-by-average-time (vec1 vec2)
487  ;; sort by highest average time spent in function. See `sort'.
488  (>= (aref vec1 2) (aref vec2 2)))
489
490(defsubst elp-pack-number (number width)
491  ;; pack the NUMBER string into WIDTH characters, watching out for
492  ;; very small or large numbers
493  (if (<= (length number) width)
494      number
495    ;; check for very large or small numbers
496    (if (string-match "^\\(.*\\)\\(e[+-].*\\)$" number)
497	(concat (substring
498		 (match-string 1 number)
499		 0
500		 (- width (match-end 2) (- (match-beginning 2)) 3))
501		"..."
502		(match-string 2 number))
503      (substring number 0 width))))
504
505(defun elp-output-result (resultvec)
506  ;; output the RESULTVEC into the results buffer. RESULTVEC is a 4 or
507  ;; more element vector where aref 0 is the call count, aref 1 is the
508  ;; total time spent in the function, aref 2 is the average time
509  ;; spent in the function, and aref 3 is the symbol's string
510  ;; name. All other elements in the vector are ignored.
511  (let* ((cc (aref resultvec 0))
512	 (tt (aref resultvec 1))
513	 (at (aref resultvec 2))
514	 (symname (aref resultvec 3))
515	 callcnt totaltime avetime)
516    (setq callcnt (number-to-string cc)
517	  totaltime (number-to-string tt)
518	  avetime (number-to-string at))
519    ;; possibly prune the results
520    (if (and elp-report-limit
521	     (numberp elp-report-limit)
522	     (< cc elp-report-limit))
523	nil
524      (elp-output-insert-symname symname)
525      (insert-char 32 (+ elp-field-len (- (length symname)) 2))
526      ;; print stuff out, formatting it nicely
527      (insert callcnt)
528      (insert-char 32 (+ elp-cc-len (- (length callcnt)) 2))
529      (let ((ttstr (elp-pack-number totaltime elp-et-len))
530	    (atstr (elp-pack-number avetime elp-at-len)))
531	(insert ttstr)
532	(insert-char 32 (+ elp-et-len (- (length ttstr)) 2))
533	(insert atstr))
534      (insert "\n"))))
535
536(defvar elp-results-symname-map
537  (let ((map (make-sparse-keymap)))
538    (define-key map [mouse-2] 'elp-results-jump-to-definition)
539    (define-key map "\C-m" 'elp-results-jump-to-definition)
540    map)
541  "Keymap used on the function name column." )
542
543(defun elp-results-jump-to-definition (&optional event)
544  "Jump to the definition of the function under the point."
545  (interactive (list last-nonmenu-event))
546  (if event (posn-set-point (event-end event)))
547  (find-function (get-text-property (point) 'elp-symname)))
548
549(defun elp-output-insert-symname (symname)
550  ;; Insert SYMNAME with text properties.
551  (insert (propertize symname
552		      'elp-symname (intern symname)
553		      'keymap elp-results-symname-map
554		      'mouse-face 'highlight
555		      'help-echo "mouse-2 or RET jumps to definition")))
556
557;;;###autoload
558(defun elp-results ()
559  "Display current profiling results.
560If `elp-reset-after-results' is non-nil, then current profiling
561information for all instrumented functions are reset after results are
562displayed."
563  (interactive)
564  (let ((curbuf (current-buffer))
565	(resultsbuf (if elp-recycle-buffers-p
566			(get-buffer-create elp-results-buffer)
567		      (generate-new-buffer elp-results-buffer))))
568    (set-buffer resultsbuf)
569    (erase-buffer)
570    ;; get the length of the longest function name being profiled
571    (let* ((longest 0)
572	   (title "Function Name")
573	   (titlelen (length title))
574	   (elp-field-len titlelen)
575	   (cc-header "Call Count")
576	   (elp-cc-len    (length cc-header))
577	   (et-header "Elapsed Time")
578	   (elp-et-len    (length et-header))
579	   (at-header "Average Time")
580	   (elp-at-len    (length at-header))
581	   (resvec
582	    (mapcar
583	     (function
584	      (lambda (funsym)
585		(let* ((info (get funsym elp-timer-info-property))
586		       (symname (format "%s" funsym))
587		       (cc (aref info 0))
588		       (tt (aref info 1)))
589		  (if (not info)
590		      (insert "No profiling information found for: "
591			      symname)
592		    (setq longest (max longest (length symname)))
593		    (vector cc tt (if (zerop cc)
594				      0.0 ;avoid arithmetic div-by-zero errors
595				    (/ (float tt) (float cc)))
596			    symname)))))
597	     elp-all-instrumented-list))
598	   )				; end let*
599      (insert title)
600      (if (> longest titlelen)
601	  (progn
602	    (insert-char 32 (- longest titlelen))
603	    (setq elp-field-len longest)))
604      (insert "  " cc-header "  " et-header "  " at-header "\n")
605      (insert-char ?= elp-field-len)
606      (insert "  ")
607      (insert-char ?= elp-cc-len)
608      (insert "  ")
609      (insert-char ?= elp-et-len)
610      (insert "  ")
611      (insert-char ?= elp-at-len)
612      (insert "\n")
613      ;; if sorting is enabled, then sort the results list. in either
614      ;; case, call elp-output-result to output the result in the
615      ;; buffer
616      (if elp-sort-by-function
617	  (setq resvec (sort resvec elp-sort-by-function)))
618      (mapcar 'elp-output-result resvec))
619    ;; now pop up results buffer
620    (set-buffer curbuf)
621    (pop-to-buffer resultsbuf)
622    ;; copy results to standard-output?
623    (if (or elp-use-standard-output noninteractive)
624	(princ (buffer-substring (point-min) (point-max))))
625    ;; reset profiling info if desired
626    (and elp-reset-after-results
627	 (elp-reset-all))))
628
629(defun elp-unload-hook ()
630  (elp-restore-all))
631(add-hook 'elp-unload-hook 'elp-unload-hook)
632
633(provide 'elp)
634
635;; arch-tag: c4eef311-9b3e-4bb2-8a54-3485d41b4eb1
636;;; elp.el ends here
637