1 ;;; profiler.el --- UI and helper functions for Emacs's native profiler -*- lexical-binding: t -*-
3 ;; Copyright (C) 2012 Free Software Foundation, Inc.
5 ;; Author: Tomohiro Matsuyama <tomo@cx4a.org>
8 ;; This program is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation, either version 3 of the License, or
11 ;; (at your option) any later version.
13 ;; This program is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
30 (defgroup profiler nil
39 (defun profiler-ensure-string (object)
42 (format "%s" object
)))
44 (defun profiler-format (fmt &rest args
)
45 (cl-loop for
(width align subfmt
) in fmt
47 for str
= (cl-typecase subfmt
48 (cons (apply 'profiler-format subfmt arg
))
49 (string (format subfmt arg
))
50 (t (profiler-ensure-string arg
)))
51 for len
= (length str
)
53 collect
(substring str
0 width
) into frags
56 (let ((padding (make-string (- width len
) ?\s
)))
58 (left (concat str padding
))
59 (right (concat padding str
))))
61 finally return
(apply #'concat frags
)))
65 ;;; Slot data structure
67 (cl-defstruct (profiler-slot (:type list
)
68 (:constructor profiler-make-slot
))
69 backtrace count elapsed
)
73 ;;; Log data structure
75 (cl-defstruct (profiler-log (:type list
)
76 (:constructor profiler-make-log
))
77 type diff-p timestamp slots
)
79 (defun profiler-log-diff (log1 log2
)
81 (unless (eq (profiler-log-type log1
)
82 (profiler-log-type log2
))
83 (error "Can't compare different type of logs"))
84 (let ((slots (profiler-log-slots log2
)))
85 (dolist (slot (profiler-log-slots log1
))
86 (push (profiler-make-slot :backtrace
(profiler-slot-backtrace slot
)
87 :count
(- (profiler-slot-count slot
))
88 :elapsed
(- (profiler-slot-elapsed slot
)))
90 (profiler-make-log :type
(profiler-log-type log1
)
92 :timestamp
(current-time)
95 (defun profiler-log-fixup (log)
96 "Fixup LOG so that the log could be serialized into file."
101 (or (eq (car entry
) 'lambda
)
102 (eq (car entry
) 'closure
)))
103 (format "#<closure 0x%x>" (sxhash entry
)))
104 ((eq (type-of entry
) 'compiled-function
)
105 (format "#<compiled 0x%x>" (sxhash entry
)))
111 (format "#<unknown 0x%x>" (sxhash entry
)))))))
112 (dolist (slot (profiler-log-slots log
))
113 (setf (profiler-slot-backtrace slot
)
114 (mapcar fixup-entry
(profiler-slot-backtrace slot
))))))
118 ;;; Calltree data structure
120 (cl-defstruct (profiler-calltree (:constructor profiler-make-calltree
))
122 (count 0) count-percent
123 (elapsed 0) elapsed-percent
126 (defun profiler-calltree-leaf-p (tree)
127 (null (profiler-calltree-children tree
)))
129 (defun profiler-calltree-count< (a b
)
130 (cond ((eq (profiler-calltree-entry a
) t
) t
)
131 ((eq (profiler-calltree-entry b
) t
) nil
)
132 (t (< (profiler-calltree-count a
)
133 (profiler-calltree-count b
)))))
135 (defun profiler-calltree-count> (a b
)
136 (not (profiler-calltree-count< a b
)))
138 (defun profiler-calltree-elapsed< (a b
)
139 (cond ((eq (profiler-calltree-entry a
) t
) t
)
140 ((eq (profiler-calltree-entry b
) t
) nil
)
141 (t (< (profiler-calltree-elapsed a
)
142 (profiler-calltree-elapsed b
)))))
144 (defun profiler-calltree-elapsed> (a b
)
145 (not (profiler-calltree-elapsed< a b
)))
147 (defun profiler-calltree-depth (tree)
148 (let ((parent (profiler-calltree-parent tree
)))
151 (1+ (profiler-calltree-depth parent
)))))
153 (defun profiler-calltree-find (tree entry
)
154 (cl-dolist (child (profiler-calltree-children tree
))
155 (when (equal (profiler-calltree-entry child
) entry
)
158 (defun profiler-calltree-walk (calltree function
)
159 (funcall function calltree
)
160 (dolist (child (profiler-calltree-children calltree
))
161 (profiler-calltree-walk child function
)))
163 (defun profiler-calltree-build-1 (tree log
&optional reverse
)
164 (dolist (slot (profiler-log-slots log
))
165 (let ((backtrace (profiler-slot-backtrace slot
))
166 (count (profiler-slot-count slot
))
167 (elapsed (profiler-slot-elapsed slot
))
169 (dolist (entry (if reverse backtrace
(reverse backtrace
)))
170 (let ((child (profiler-calltree-find node entry
)))
172 (setq child
(profiler-make-calltree :entry entry
:parent node
))
173 (push child
(profiler-calltree-children node
)))
174 (cl-incf (profiler-calltree-count child
) count
)
175 (cl-incf (profiler-calltree-elapsed child
) elapsed
)
176 (setq node child
))))))
178 (defun profiler-calltree-compute-percentages (tree)
179 (let ((total-count 0)
181 (dolist (child (profiler-calltree-children tree
))
182 (cl-incf total-count
(profiler-calltree-count child
))
183 (cl-incf total-elapsed
(profiler-calltree-elapsed child
)))
184 (profiler-calltree-walk
186 (unless (zerop total-count
)
187 (setf (profiler-calltree-count-percent node
)
189 (/ (* (profiler-calltree-count node
) 100)
191 (unless (zerop total-elapsed
)
192 (setf (profiler-calltree-elapsed-percent node
)
194 (/ (* (profiler-calltree-elapsed node
) 100)
195 total-elapsed
))))))))
197 (cl-defun profiler-calltree-build (log &key reverse
)
198 (let ((tree (profiler-make-calltree)))
199 (profiler-calltree-build-1 tree log reverse
)
200 (profiler-calltree-compute-percentages tree
)
203 (defun profiler-calltree-sort (tree predicate
)
204 (let ((children (profiler-calltree-children tree
)))
205 (setf (profiler-calltree-children tree
) (sort children predicate
))
206 (dolist (child (profiler-calltree-children tree
))
207 (profiler-calltree-sort child predicate
))))
213 (defcustom profiler-report-closed-mark
"+"
214 "An indicator of closed calltrees."
218 (defcustom profiler-report-open-mark
"-"
219 "An indicator of open calltrees."
223 (defcustom profiler-report-leaf-mark
" "
224 "An indicator of calltree leaves."
228 (defvar profiler-report-sample-line-format
233 (defvar profiler-report-memory-line-format
238 (defvar profiler-report-log nil
)
239 (defvar profiler-report-reversed nil
)
240 (defvar profiler-report-order nil
)
242 (defun profiler-report-make-entry-part (entry)
247 ((and (symbolp entry
)
249 (propertize (symbol-name entry
)
251 'mouse-face
'highlight
252 'help-echo
"mouse-2 or RET jumps to definition"))
254 (profiler-ensure-string entry
)))))
255 (propertize string
'entry entry
)))
257 (defun profiler-report-make-name-part (tree)
258 (let* ((entry (profiler-calltree-entry tree
))
259 (depth (profiler-calltree-depth tree
))
260 (indent (make-string (* (1- depth
) 2) ?\s
))
261 (mark (if (profiler-calltree-leaf-p tree
)
262 profiler-report-leaf-mark
263 profiler-report-closed-mark
))
264 (entry (profiler-report-make-entry-part entry
)))
265 (format "%s%s %s" indent mark entry
)))
267 (defun profiler-report-header-line-format (fmt &rest args
)
268 (let* ((header (apply 'profiler-format fmt args
))
269 (escaped (replace-regexp-in-string "%" "%%" header
)))
270 (concat " " escaped
)))
272 (defun profiler-report-line-format (tree)
273 (let ((diff-p (profiler-log-diff-p profiler-report-log
))
274 (name-part (profiler-report-make-name-part tree
))
275 (elapsed (profiler-calltree-elapsed tree
))
276 (elapsed-percent (profiler-calltree-elapsed-percent tree
))
277 (count (profiler-calltree-count tree
))
278 (count-percent (profiler-calltree-count-percent tree
)))
279 (cl-ecase (profiler-log-type profiler-report-log
)
282 (profiler-format profiler-report-sample-line-format
284 (list (if (> elapsed
0)
285 (format "+%s" elapsed
)
288 (profiler-format profiler-report-sample-line-format
289 name-part
(list elapsed elapsed-percent
))))
292 (profiler-format profiler-report-memory-line-format
294 (list (if (> count
0)
298 (profiler-format profiler-report-memory-line-format
299 name-part
(list count count-percent
)))))))
301 (defun profiler-report-insert-calltree (tree)
302 (let ((line (profiler-report-line-format tree
)))
303 (insert (propertize (concat line
"\n") 'calltree tree
))))
305 (defun profiler-report-insert-calltree-children (tree)
306 (mapc 'profiler-report-insert-calltree
307 (profiler-calltree-children tree
)))
313 (defvar profiler-report-mode-map
314 (let ((map (make-sparse-keymap)))
315 (define-key map
"n" 'profiler-report-next-entry
)
316 (define-key map
"p" 'profiler-report-previous-entry
)
317 (define-key map
[down] 'profiler-report-next-entry)
318 (define-key map [up] 'profiler-report-previous-entry)
319 (define-key map "\r" 'profiler-report-toggle-entry)
320 (define-key map "\t" 'profiler-report-toggle-entry)
321 (define-key map "i" 'profiler-report-toggle-entry)
322 (define-key map "f" 'profiler-report-find-entry)
323 (define-key map "j" 'profiler-report-find-entry)
324 (define-key map [mouse-2] 'profiler-report-find-entry)
325 (define-key map "d" 'profiler-report-describe-entry)
326 (define-key map "C" 'profiler-report-render-calltree)
327 (define-key map "B" 'profiler-report-render-reversed-calltree)
328 (define-key map "A" 'profiler-report-ascending-sort)
329 (define-key map "D" 'profiler-report-descending-sort)
330 (define-key map "=" 'profiler-report-compare-log)
331 (define-key map (kbd "C-x C-w") 'profiler-report-write-log)
332 (define-key map "q" 'quit-window)
335 (defun profiler-report-make-buffer-name (log)
336 (let ((time (format-time-string "%Y-%m-%d %T" (profiler-log-timestamp log))))
337 (cl-ecase (profiler-log-type log)
338 (sample (format "*CPU-Profiler-Report %s*" time))
339 (memory (format "*Memory-Profiler-Report %s*" time)))))
341 (defun profiler-report-setup-buffer (log)
342 (let* ((buf-name (profiler-report-make-buffer-name log))
343 (buffer (get-buffer-create buf-name)))
344 (with-current-buffer buffer
345 (profiler-report-mode)
346 (setq profiler-report-log log
347 profiler-report-reversed nil
348 profiler-report-order 'descending))
351 (define-derived-mode profiler-report-mode special-mode "Profiler-Report"
352 "Profiler Report Mode."
353 (make-local-variable 'profiler-report-log)
354 (make-local-variable 'profiler-report-reversed)
355 (make-local-variable 'profiler-report-order)
356 (use-local-map profiler-report-mode-map)
357 (setq buffer-read-only t
365 (defun profiler-report-calltree-at-point ()
366 (get-text-property (point) 'calltree))
368 (defun profiler-report-move-to-entry ()
369 (let ((point (next-single-property-change (line-beginning-position) 'entry)))
372 (back-to-indentation))))
374 (defun profiler-report-next-entry ()
375 "Move cursor to next profile entry."
378 (profiler-report-move-to-entry))
380 (defun profiler-report-previous-entry ()
381 "Move cursor to previous profile entry."
384 (profiler-report-move-to-entry))
386 (defun profiler-report-expand-entry ()
387 "Expand profile entry at point."
391 (when (search-forward (concat profiler-report-closed-mark " ")
392 (line-end-position) t)
393 (let ((tree (profiler-report-calltree-at-point)))
395 (let ((buffer-read-only nil))
396 (replace-match (concat profiler-report-open-mark " "))
398 (profiler-report-insert-calltree-children tree)
401 (defun profiler-report-collapse-entry ()
402 "Collpase profile entry at point."
406 (when (search-forward (concat profiler-report-open-mark " ")
407 (line-end-position) t)
408 (let* ((tree (profiler-report-calltree-at-point))
409 (depth (profiler-calltree-depth tree))
410 (start (line-beginning-position 2))
413 (let ((buffer-read-only nil))
414 (replace-match (concat profiler-report-closed-mark " "))
415 (while (and (eq (forward-line) 0)
416 (let ((child (get-text-property (point) 'calltree)))
418 (numberp (setq d (profiler-calltree-depth child)))))
420 (delete-region start (line-beginning-position)))))
423 (defun profiler-report-toggle-entry ()
424 "Expand profile entry at point if the tree is collapsed,
425 otherwise collapse the entry."
427 (or (profiler-report-expand-entry)
428 (profiler-report-collapse-entry)))
430 (defun profiler-report-find-entry (&optional event)
431 "Find profile entry at point."
432 (interactive (list last-nonmenu-event))
433 (if event (posn-set-point (event-end event)))
434 (let ((tree (profiler-report-calltree-at-point)))
436 (let ((entry (profiler-calltree-entry tree)))
437 (find-function entry)))))
439 (defun profiler-report-describe-entry ()
440 "Describe profile entry at point."
442 (let ((tree (profiler-report-calltree-at-point)))
444 (let ((entry (profiler-calltree-entry tree)))
446 (describe-function entry)))))
448 (cl-defun profiler-report-render-calltree-1 (log &key reverse (order 'descending))
449 (let ((calltree (profiler-calltree-build profiler-report-log
451 (cl-ecase (profiler-log-type log)
453 (setq header-line-format
454 (profiler-report-header-line-format
455 profiler-report-sample-line-format
456 "Function" (list "Time (ms)" "%")))
457 (let ((predicate (cl-ecase order
458 (ascending 'profiler-calltree-elapsed<)
459 (descending 'profiler-calltree-elapsed>))))
460 (profiler-calltree-sort calltree predicate)))
462 (setq header-line-format
463 (profiler-report-header-line-format
464 profiler-report-memory-line-format
465 "Function" (list "Alloc" "%")))
466 (let ((predicate (cl-ecase order
467 (ascending 'profiler-calltree-count<)
468 (descending 'profiler-calltree-count>))))
469 (profiler-calltree-sort calltree predicate))))
470 (let ((buffer-read-only nil))
472 (profiler-report-insert-calltree-children calltree)
473 (goto-char (point-min))
474 (profiler-report-move-to-entry))))
476 (defun profiler-report-rerender-calltree ()
477 (profiler-report-render-calltree-1 profiler-report-log
478 :reverse profiler-report-reversed
479 :order profiler-report-order))
481 (defun profiler-report-render-calltree ()
482 "Render calltree view of the current profile."
484 (setq profiler-report-reversed nil)
485 (profiler-report-rerender-calltree))
487 (defun profiler-report-render-reversed-calltree ()
488 "Render reversed calltree view of the current profile."
490 (setq profiler-report-reversed t)
491 (profiler-report-rerender-calltree))
493 (defun profiler-report-ascending-sort ()
494 "Sort calltree view in ascending order."
496 (setq profiler-report-order 'ascending)
497 (profiler-report-rerender-calltree))
499 (defun profiler-report-descending-sort ()
500 "Sort calltree view in descending order."
502 (setq profiler-report-order 'descending)
503 (profiler-report-rerender-calltree))
505 (defun profiler-report-log (log)
506 (let ((buffer (profiler-report-setup-buffer log)))
507 (with-current-buffer buffer
508 (profiler-report-render-calltree))
509 (pop-to-buffer buffer)))
511 (defun profiler-report-compare-log (buffer)
512 "Compare current profiler log with another profiler log."
513 (interactive (list (read-buffer "Compare to: ")))
514 (let ((log1 (with-current-buffer buffer profiler-report-log))
515 (log2 profiler-report-log))
516 (profiler-report-log (profiler-log-diff log1 log2))))
518 (defun profiler-report-write-log (filename &optional confirm)
519 "Write current profiler log into FILENAME."
521 (list (read-file-name "Write log: " default-directory)
522 (not current-prefix-arg)))
523 (let ((log profiler-report-log))
525 (let (print-level print-length)
526 (print log (current-buffer)))
527 (write-file filename confirm))))
531 ;;; Profiler commands
533 (defcustom profiler-sample-interval 10
534 "Default sample interval in millisecond."
539 (defun profiler-start (mode)
541 (list (intern (completing-read "Mode: " '("cpu" "memory" "cpu&memory")
542 nil t nil nil "cpu"))))
545 (sample-profiler-start profiler-sample-interval)
546 (message "CPU profiler started"))
548 (memory-profiler-start)
549 (message "Memory profiler started"))
551 (sample-profiler-start profiler-sample-interval)
552 (memory-profiler-start)
553 (message "CPU and memory profiler started"))))
555 (defun profiler-stop ()
558 ((and (sample-profiler-running-p)
559 (memory-profiler-running-p))
560 (sample-profiler-stop)
561 (memory-profiler-stop)
562 (message "CPU and memory profiler stopped"))
563 ((sample-profiler-running-p)
564 (sample-profiler-stop)
565 (message "CPU profiler stopped"))
566 ((memory-profiler-running-p)
567 (memory-profiler-stop)
568 (message "Memory profiler stopped"))
570 (error "No profilers started"))))
572 (defun profiler-reset ()
574 (sample-profiler-reset)
575 (memory-profiler-reset)
578 (defun profiler-report ()
580 (let ((sample-log (sample-profiler-log)))
582 (profiler-log-fixup sample-log)
583 (profiler-report-log sample-log)))
584 (let ((memory-log (memory-profiler-log)))
586 (profiler-log-fixup memory-log)
587 (profiler-report-log memory-log))))
590 (defun profiler-find-log (filename)
592 (list (read-file-name "Find log: " default-directory)))
594 (insert-file-contents filename)
595 (goto-char (point-min))
596 (let ((log (read (current-buffer))))
597 (profiler-report-log log))))
600 ;;; profiler.el ends here