emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] trunk r114588: * lisp/profiler.el: Create a more coherent


From: Stefan Monnier
Subject: [Emacs-diffs] trunk r114588: * lisp/profiler.el: Create a more coherent calltree from partial backtraces.
Date: Wed, 09 Oct 2013 03:32:40 +0000
User-agent: Bazaar (2.6b2)

------------------------------------------------------------
revno: 114588
revision-id: address@hidden
parent: address@hidden
committer: Stefan Monnier <address@hidden>
branch nick: trunk
timestamp: Tue 2013-10-08 23:32:35 -0400
message:
  * lisp/profiler.el: Create a more coherent calltree from partial backtraces.
  (profiler-format): Hide the tail with `invisible' so that C-s can still
  find the hidden elements.
  (profiler-calltree-depth): Don't recurse so enthusiastically.
  (profiler-function-equal): New hash-table-test.
  (profiler-calltree-build-unified): New function.
  (profiler-calltree-build): Use it.
  (profiler-report-make-name-part): Indent the calltree less.
  (profiler-report-mode): Add visibility specs for profiler-format.
  (profiler-report-expand-entry, profiler-report-toggle-entry):
  Expand the whole subtree when provided with a prefix arg.
  * src/fns.c (hashfn_user_defined): Allow hash functions to return any
  Lisp_Object.
modified:
  lisp/ChangeLog                 changelog-20091113204419-o5vbwnq5f7feedwu-1432
  lisp/profiler.el               profiler.el-20120822062536-8tk8gghazaoi1nyq-1
  src/ChangeLog                  changelog-20091113204419-o5vbwnq5f7feedwu-1438
  src/fns.c                      fns.c-20091113204419-o5vbwnq5f7feedwu-203
=== modified file 'lisp/ChangeLog'
--- a/lisp/ChangeLog    2013-10-09 03:18:01 +0000
+++ b/lisp/ChangeLog    2013-10-09 03:32:35 +0000
@@ -1,3 +1,17 @@
+2013-10-09  Stefan Monnier  <address@hidden>
+
+       * profiler.el: Create a more coherent calltree from partial backtraces.
+       (profiler-format): Hide the tail with `invisible' so that C-s can still
+       find the hidden elements.
+       (profiler-calltree-depth): Don't recurse so enthusiastically.
+       (profiler-function-equal): New hash-table-test.
+       (profiler-calltree-build-unified): New function.
+       (profiler-calltree-build): Use it.
+       (profiler-report-make-name-part): Indent the calltree less.
+       (profiler-report-mode): Add visibility specs for profiler-format.
+       (profiler-report-expand-entry, profiler-report-toggle-entry):
+       Expand the whole subtree when provided with a prefix arg.
+
 2013-10-09  Dmitry Gutov  <address@hidden>
 
        * progmodes/ruby-mode.el (ruby-smie-rules): Indent after hanging

=== modified file 'lisp/profiler.el'
--- a/lisp/profiler.el  2013-09-11 01:43:07 +0000
+++ b/lisp/profiler.el  2013-10-09 03:32:35 +0000
@@ -27,6 +27,7 @@
 ;;; Code:
 
 (require 'cl-lib)
+(require 'pcase)
 
 (defgroup profiler nil
   "Emacs profiler."
@@ -86,10 +87,12 @@
                       (profiler-ensure-string arg)))
           for len = (length str)
           if (< width len)
-          collect (substring str 0 width) into frags
+           collect (progn (put-text-property (max 0 (- width 2)) len
+                                             'invisible 'profiler str)
+                          str) into frags
           else
           collect
-          (let ((padding (make-string (- width len) ?\s)))
+           (let ((padding (make-string (max 0 (- width len)) ?\s)))
             (cl-ecase align
               (left (concat str padding))
               (right (concat padding str))))
@@ -248,10 +251,10 @@
   (not (profiler-calltree-count< a b)))
 
 (defun profiler-calltree-depth (tree)
-  (let ((parent (profiler-calltree-parent tree)))
-    (if (null parent)
-       0
-      (1+ (profiler-calltree-depth parent)))))
+  (let ((d 0))
+    (while (setq tree (profiler-calltree-parent tree))
+      (cl-incf d))
+    d))
 
 (defun profiler-calltree-find (tree entry)
   "Return a child tree of ENTRY under TREE."
@@ -269,10 +272,9 @@
     (profiler-calltree-walk child function)))
 
 (defun profiler-calltree-build-1 (tree log &optional reverse)
-  ;; FIXME: Do a better job of reconstructing a complete call-tree
-  ;; when the backtraces have been truncated.  Ideally, we should be
-  ;; able to reduce profiler-max-stack-depth to 3 or 4 and still
-  ;; get a meaningful call-tree.
+  ;; This doesn't try to stitch up partial backtraces together.
+  ;; We still use it for reverse calltrees, but for forward calltrees, we use
+  ;; profiler-calltree-build-unified instead now.
   (maphash
    (lambda (backtrace count)
      (let ((node tree)
@@ -289,6 +291,115 @@
                (setq node child)))))))
    log))
 
+
+(define-hash-table-test 'profiler-function-equal #'function-equal
+  (lambda (f) (cond
+          ((byte-code-function-p f) (aref f 1))
+          ((eq (car-safe f) 'closure) (cddr f))
+          (t f))))
+
+(defun profiler-calltree-build-unified (tree log)
+  ;; Let's try to unify all those partial backtraces into a single
+  ;; call tree.  First, we record in fun-map all the functions that appear
+  ;; in `log' and where they appear.
+  (let ((fun-map (make-hash-table :test 'profiler-function-equal))
+        (parent-map (make-hash-table :test 'eq))
+        (leftover-tree (profiler-make-calltree
+                        :entry (intern "...") :parent tree)))
+    (push leftover-tree (profiler-calltree-children tree))
+    (maphash
+     (lambda (backtrace _count)
+       (let ((max (length backtrace)))
+         ;; Don't record the head elements in there, since we want to use this
+         ;; fun-map to find parents of partial backtraces, but parents only
+         ;; make sense if they have something "above".
+         (dotimes (i (1- max))
+           (let ((f (aref backtrace i)))
+             (when f
+               (push (cons i backtrace) (gethash f fun-map)))))))
+     log)
+    ;; Then, for each partial backtrace, try to find a parent backtrace
+    ;; (i.e. a backtrace that describes (part of) the truncated part of
+    ;; the partial backtrace).  For a partial backtrace like "[f3 f2 f1]" (f3
+    ;; is deeper), any backtrace that includes f1 could be a parent; and indeed
+    ;; the counts of this partial backtrace could each come from a different
+    ;; parent backtrace (some of which may not even be in `log').  So we should
+    ;; consider each backtrace that includes f1 and give it some percentage of
+    ;; `count'.  But we can't know for sure what percentage to give to each
+    ;; possible parent.
+    ;; The "right" way might be to give a percentage proportional to the counts
+    ;; already registered for that parent, or some such statistical principle.
+    ;; But instead, we will give all our counts to a single "best
+    ;; matching" parent.  So let's look for the best matching parent, and store
+    ;; the result in parent-map.
+    ;; Using the "best matching parent" is important also to try and avoid
+    ;; stitching together backtraces that can't possibly go together.
+    ;; For example, when the head is `apply' (or `mapcar', ...), we want to
+    ;; make sure we don't just use any parent that calls `apply', since most of
+    ;; them would never, in turn, cause apply to call the subsequent function.
+    (maphash
+     (lambda (backtrace _count)
+       (let* ((max (1- (length backtrace)))
+              (head (aref backtrace max))
+              (best-parent nil)
+              (best-match (1+ max))
+              (parents (gethash head fun-map)))
+         (pcase-dolist (`(,i . ,parent) parents)
+           (when t ;; (<= (- max i) best-match) ;Else, it can't be better.
+             (let ((match max)
+                   (imatch i))
+               (cl-assert (>= match imatch))
+               (cl-assert (function-equal (aref backtrace max)
+                                          (aref parent i)))
+               (while (progn
+                        (cl-decf imatch) (cl-decf match)
+                        (when (> imatch 0)
+                          (function-equal (aref backtrace match)
+                                          (aref parent imatch)))))
+               (when (< match best-match)
+                 (cl-assert (<= (- max i) best-match))
+                 ;; Let's make sure this parent is not already our child: we
+                 ;; don't want cycles here!
+                 (let ((valid t)
+                       (tmp-parent parent))
+                   (while (setq tmp-parent
+                                (if (eq tmp-parent backtrace)
+                                    (setq valid nil)
+                                  (cdr (gethash tmp-parent parent-map)))))
+                   (when valid
+                     (setq best-match match)
+                     (setq best-parent (cons i parent))))))))
+         (puthash backtrace best-parent parent-map)))
+     log)
+    ;; Now we have a single parent per backtrace, so we have a unified tree.
+    ;; Let's build the actual call-tree from it.
+    (maphash
+     (lambda (backtrace count)
+       (let ((node tree)
+             (parents (list (cons -1 backtrace)))
+             (tmp backtrace)
+             (max (length backtrace)))
+         (while (setq tmp (gethash tmp parent-map))
+           (push tmp parents)
+           (setq tmp (cdr tmp)))
+         (when (aref (cdar parents) (1- max))
+           (cl-incf (profiler-calltree-count leftover-tree) count)
+           (setq node leftover-tree))
+         (pcase-dolist (`(,i . ,parent) parents)
+           (let ((j (1- max)))
+             (while (> j i)
+               (let ((f (aref parent j)))
+                 (cl-decf j)
+                 (when f
+                   (let ((child (profiler-calltree-find node f)))
+                     (unless child
+                       (setq child (profiler-make-calltree
+                                    :entry f :parent node))
+                       (push child (profiler-calltree-children node)))
+                     (cl-incf (profiler-calltree-count child) count)
+                     (setq node child)))))))))
+     log)))
+
 (defun profiler-calltree-compute-percentages (tree)
   (let ((total-count 0))
     ;; FIXME: the memory profiler's total wraps around all too easily!
@@ -303,7 +414,9 @@
 
 (cl-defun profiler-calltree-build (log &key reverse)
   (let ((tree (profiler-make-calltree)))
-    (profiler-calltree-build-1 tree log reverse)
+    (if reverse
+        (profiler-calltree-build-1 tree log reverse)
+      (profiler-calltree-build-unified tree log))
     (profiler-calltree-compute-percentages tree)
     tree))
 
@@ -371,7 +484,7 @@
 (defun profiler-report-make-name-part (tree)
   (let* ((entry (profiler-calltree-entry tree))
         (depth (profiler-calltree-depth tree))
-        (indent (make-string (* (1- depth) 2) ?\s))
+        (indent (make-string (* (1- depth) 1) ?\s))
         (mark (if (profiler-calltree-leaf-p tree)
                   profiler-report-leaf-mark
                 profiler-report-closed-mark))
@@ -379,7 +492,7 @@
     (format "%s%s %s" indent mark entry)))
 
 (defun profiler-report-header-line-format (fmt &rest args)
-  (let* ((header (apply 'profiler-format fmt args))
+  (let* ((header (apply #'profiler-format fmt args))
         (escaped (replace-regexp-in-string "%" "%%" header)))
     (concat " " escaped)))
 
@@ -404,7 +517,7 @@
     (insert (propertize (concat line "\n") 'calltree tree))))
 
 (defun profiler-report-insert-calltree-children (tree)
-  (mapc 'profiler-report-insert-calltree
+  (mapc #'profiler-report-insert-calltree
        (profiler-calltree-children tree)))
 
 
@@ -502,6 +615,7 @@
 
 (define-derived-mode profiler-report-mode special-mode "Profiler-Report"
   "Profiler Report Mode."
+  (add-to-invisibility-spec '(profiler . t))
   (setq buffer-read-only t
        buffer-undo-list t
        truncate-lines t))
@@ -531,9 +645,10 @@
   (forward-line -1)
   (profiler-report-move-to-entry))
 
-(defun profiler-report-expand-entry ()
-  "Expand entry at point."
-  (interactive)
+(defun profiler-report-expand-entry (&optional full)
+  "Expand entry at point.
+With a prefix argument, expand the whole subtree."
+  (interactive "P")
   (save-excursion
     (beginning-of-line)
     (when (search-forward (concat profiler-report-closed-mark " ")
@@ -543,7 +658,14 @@
          (let ((inhibit-read-only t))
            (replace-match (concat profiler-report-open-mark " "))
            (forward-line)
-           (profiler-report-insert-calltree-children tree)
+            (let ((first (point))
+                  (last (copy-marker (point) t)))
+              (profiler-report-insert-calltree-children tree)
+              (when full
+                (goto-char first)
+                (while (< (point) last)
+                  (profiler-report-expand-entry)
+                  (forward-line 1))))
            t))))))
 
 (defun profiler-report-collapse-entry ()
@@ -568,11 +690,11 @@
            (delete-region start (line-beginning-position)))))
       t)))
 
-(defun profiler-report-toggle-entry ()
+(defun profiler-report-toggle-entry (&optional arg)
   "Expand entry at point if the tree is collapsed,
 otherwise collapse."
-  (interactive)
-  (or (profiler-report-expand-entry)
+  (interactive "P")
+  (or (profiler-report-expand-entry arg)
       (profiler-report-collapse-entry)))
 
 (defun profiler-report-find-entry (&optional event)

=== modified file 'src/ChangeLog'
--- a/src/ChangeLog     2013-10-08 20:04:40 +0000
+++ b/src/ChangeLog     2013-10-09 03:32:35 +0000
@@ -1,3 +1,8 @@
+2013-10-09  Stefan Monnier  <address@hidden>
+
+       * fns.c (hashfn_user_defined): Allow hash functions to return any
+       Lisp_Object.
+
 2013-10-08  Paul Eggert  <address@hidden>
 
        Fix minor problems found by static checking.

=== modified file 'src/fns.c'
--- a/src/fns.c 2013-09-29 18:50:28 +0000
+++ b/src/fns.c 2013-10-09 03:32:35 +0000
@@ -3571,9 +3571,7 @@
   args[0] = ht->user_hash_function;
   args[1] = key;
   hash = Ffuncall (2, args);
-  if (!INTEGERP (hash))
-    signal_error ("Invalid hash code returned from user-supplied hash 
function", hash);
-  return XUINT (hash);
+  return hashfn_eq (ht, hash);
 }
 
 /* An upper bound on the size of a hash table index.  It must fit in
@@ -4542,9 +4540,9 @@
 
 TEST must be a function taking two arguments and returning non-nil if
 both arguments are the same.  HASH must be a function taking one
-argument and return an integer that is the hash code of the argument.
-Hash code computation should use the whole value range of integers,
-including negative integers.  */)
+argument and returning an object that is the hash code of the argument.
+It should be the case that if (eq (funcall HASH x1) (funcall HASH x2))
+returns nil, then (funcall TEST x1 x2) also returns nil.  */)
   (Lisp_Object name, Lisp_Object test, Lisp_Object hash)
 {
   return Fput (name, Qhash_table_test, list2 (test, hash));


reply via email to

[Prev in Thread] Current Thread [Next in Thread]