.emacs.d/profile-dotemacs.el

201 lines
7.0 KiB
EmacsLisp

;;; profile-dotemacs.el --- Profile your Emacs init file
;; Copyright (C) 2010, 2012 David Engster
;; Author: David Engster <dengste@eml.cc>
;; This file is NOT part of GNU Emacs.
;; This program 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
;; of the License, or (at your option) any later version.
;;
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This is to easily profile your Emacs init file (or any other
;; script-like Emacs Lisp file, for that matter).
;; It will go over all sexp's (balanced expressions) in the file and
;; run them through `benchmark-run'. It will then show the file with
;; overlays applied in a way that let you easily find out which sexp's
;; take the most time. Since time is relative, it's not the absolute
;; value that counts but the percentage of the total running time.
;;
;; * All other sexp's with a percentage greater than
;; `profile-dotemacs-low-percentage' will be preceded by a
;; highlighted line, showing the results from `benchmark-run'.
;; Also, the more 'reddish' the background of the sexp, the more
;; time it needs.
;; * All other sexp's will be grayed out to indicate that their
;; running time is miniscule. You can still see the benchmark
;; results in the minibuffer by hovering over the sexp with the
;; mouse.
;; You can only benchmark full sexp's, so if you wrapped large parts
;; of your init file in some conditional clause, you'll have to remove
;; that for getting finer granularity.
;;; Usage:
;; Start emacs as follows:
;;
;; emacs -Q -l <PATH>/profile-dotemacs.el -f profile-dotemacs
;;
;; with <PATH> being the path to where this file resides.
;;; Caveats (thanks to Raffaele Ricciardi for reporting those):
;; - The usual `--debug-init' for debugging your init file won't work
;; with profile-dotemacs, so you'll have to call
;; `toggle-debug-on-error', either on the commandline or at the
;; beginning of your init file.
;; - `load-file-name' is nil when the init file is being loaded
;; by the profiler. This might matter if you perform the
;; bulk of initializations in a different file.
;; - Starting external shells like IELM or eshell in your init file
;; might mess with overlay creation, so this must not be done.
;;; Download:
;; You can always get the latest version from
;; http://randomsample.de/profile-dotemacs.el
;;; Code:
(require 'thingatpt)
(require 'benchmark)
;; User variables
(defvar profile-dotemacs-file "~/.emacs.d/init.el"
"File to be profiled.")
(defvar profile-dotemacs-low-percentage 3
"Percentage which should be considered low.
All sexp's with a running time below this percentage will be
grayed out.")
(defface profile-dotemacs-time-face
'((((background dark)) (:background "OrangeRed1"))
(t (:background "red3")))
"Background color to indicate percentage of total time.")
(defface profile-dotemacs-low-percentage-face
'((((background dark)) (:foreground "gray25"))
(t (:foreground "gray75")))
"Face for sexps below `profile-dotemacs-low-percentage'.")
(defface profile-dotemacs-highlight-face
'((((background dark)) (:background "blue"))
(t (:background "yellow")))
"Highlight face for benchmark results.")
;; Main function
(defun profile-dotemacs ()
"Load `profile-dotemacs-file' and benchmark its sexps."
(interactive)
(with-current-buffer (find-file-noselect profile-dotemacs-file t)
(setq buffer-read-only t) ;; just to be sure
(goto-char (point-min))
(let (start end results)
(while
(< (point)
(setq end (progn
(forward-sexp 1)
(point))))
(forward-sexp -1)
(setq start (point))
(add-to-list
'results
`(,start ,end
,(benchmark-run
(eval (sexp-at-point)))))
(goto-char end))
(profile-dotemacs-show-results results)
(switch-to-buffer (current-buffer)))))
;; Helper functions
(defun profile-dotemacs-show-results (results)
"Show timings from RESULTS in current buffer."
(let ((totaltime (profile-dotemacs-totaltime results))
current percentage ov)
(while results
(let* ((current (pop results))
(ov (make-overlay (car current) (cadr current)))
(current (car (last current)))
(percentage (/ (+ (car current) (nth 2 current))
totaltime))
col benchstr lowface)
(setq col
(profile-dotemacs-percentage-color
percentage
(face-background 'default)
(face-background 'profile-dotemacs-time-face)))
(setq percentage (round (* 100 percentage)))
(setq benchstr (profile-dotemacs-make-benchstr current))
(overlay-put ov 'help-echo benchstr)
(if (and (numberp profile-dotemacs-low-percentage)
(< percentage profile-dotemacs-low-percentage))
(overlay-put ov 'face 'profile-dotemacs-low-percentage-face)
(overlay-put ov 'before-string
(propertize benchstr
'face 'profile-dotemacs-highlight-face))
(overlay-put ov 'face
`(:background ,col)))))
(setq ov (make-overlay (1- (point-max)) (point-max)))
(overlay-put ov 'after-string
(propertize
(format "\n-----------------\nTotal time: %.2fs\n"
totaltime)
'face 'profile-dotemacs-highlight-face))))
(defun profile-dotemacs-totaltime (results)
"Calculate total time of RESULTS."
(let ((totaltime 0))
(mapc (lambda (x)
(let ((cur (car (last x))))
(setq totaltime (+ totaltime (car cur) (nth 2 cur)))))
results)
totaltime))
(defun profile-dotemacs-percentage-color (percent col-begin col-end)
"Calculate color according to PERCENT between COL-BEGIN and COL-END."
(let* ((col1 (color-values col-begin))
(col2 (color-values col-end))
(col
(mapcar (lambda (c)
(round
(+ (* (- 1 percent) (nth c col1))
(* percent (nth c col2)))))
'(0 1 2))))
(format "RGB:%04x/%04x/%04x"
(car col)
(nth 1 col)
(nth 2 col))))
(defun profile-dotemacs-make-benchstr (timings)
"Create descriptive benchmark string from TIMINGS."
(format
(concat
"<Percentage: %d ; "
"Time: %.2f ; "
"Number of GC: %d ; "
"Time for GC: %.2f>\n")
percentage
(car timings) (nth 1 timings) (nth 2 timings)))
;; profile-dotemacs.el ends here