branch: elpa/flamegraph
commit 8021b3aa3a474e6536983e837f8f970f9da2c713
Author: Dmitry Gutov <[email protected]>
Commit: Dmitry Gutov <[email protected]>

    Grade call-site highlights by sample share
    
    Replace the single flamegraph-call-site face with three heat faces
    (-hot/-warm/-cool), chosen per callee's share of the described frame's
    count: hot >= 40%, warm >= 10%, cool below.  The describe-buffer snippet
    now shows at a glance where the time goes, not just which lines call out.
---
 README.md                |  4 +++-
 flamegraph.el            | 47 +++++++++++++++++++++++++++++++++++------------
 test/flamegraph-tests.el | 21 ++++++++++++++-------
 3 files changed, 52 insertions(+), 20 deletions(-)

diff --git a/README.md b/README.md
index 82db81b8d8..9d48b1d61c 100644
--- a/README.md
+++ b/README.md
@@ -156,7 +156,9 @@ canonical [CPU Flame Graphs][cpufg] article.
 
 | Face | Purpose |
 | --- | --- |
-| `flamegraph-call-site` | Highlight for callee occurrences in the 
describe-buffer snippet |
+| `flamegraph-call-site-hot` | Highlight for the hottest callee occurrences in 
the describe-buffer snippet |
+| `flamegraph-call-site-warm` | Highlight for moderately hot callee 
occurrences |
+| `flamegraph-call-site-cool` | Highlight for minor callee occurrences |
 
 ## Development
 
diff --git a/flamegraph.el b/flamegraph.el
index 09522e4a27..c42410e213 100644
--- a/flamegraph.el
+++ b/flamegraph.el
@@ -84,8 +84,26 @@ FILE:LINE location.  If nil, paths are resolved against the 
directory of
 the data file the graph was loaded from."
   :type '(choice (const :tag "Data file's directory" nil) directory))
 
-(defface flamegraph-call-site '((t :inherit highlight))
-  "Face for callee occurrences highlighted in a description buffer's source.")
+(defface flamegraph-call-site-hot
+  '((((background light)) :background "#ff9b3d")
+    (((background dark))  :background "#9c4a1c"))
+  "Face for the hottest callee occurrences in a description buffer's source.")
+
+(defface flamegraph-call-site-warm
+  '((((background light)) :background "#ffd24d")
+    (((background dark))  :background "#7a5817"))
+  "Face for moderately hot callee occurrences in a description buffer's 
source.")
+
+(defface flamegraph-call-site-cool
+  '((((background light)) :background "#fff0c0")
+    (((background dark))  :background "#4a3a14"))
+  "Face for minor callee occurrences in a description buffer's source.")
+
+(defun flamegraph--call-site-face (weight)
+  "Return the call-site heat face for fractional WEIGHT in [0,1]."
+  (cond ((>= weight 0.40) 'flamegraph-call-site-hot)
+        ((>= weight 0.10) 'flamegraph-call-site-warm)
+        (t                'flamegraph-call-site-cool)))
 
 ;;; Layout: call tree -> flat list of frames
 
@@ -548,9 +566,10 @@ the enclosing structural lines up to the definition are 
kept — each line
 that is less indented than the one below it — so the nesting that leads
 to the sampled line stays visible.  FIND-REGIONS, if non-nil, is called
 with the enclosing defun's start and end buffer positions; it returns
-a list of (BEG . END) ranges within the loaded buffer to highlight with
-`flamegraph-call-site'.  Their lines are kept as well.  Skipped runs
-are elided with `⋯'.  CONTEXT defaults to 4."
+a list of (BEG END WEIGHT) ranges within the loaded buffer to highlight
+with a heat face chosen from WEIGHT (see `flamegraph--call-site-face').
+Their lines are kept as well.  Skipped runs are elided with `⋯'.
+CONTEXT defaults to 4."
   (setq context (or context 4))
   (when (and (file-readable-p path) (> line 0))
     (with-temp-buffer
@@ -614,9 +633,10 @@ are elided with `⋯'.  CONTEXT defaults to 4."
                 (let ((bol (line-beginning-position))
                       (eol (line-end-position)))
                   (dolist (r regions)
-                    (when (and (<= bol (car r)) (>= eol (cdr r)))
-                      (add-face-text-property (car r) (cdr r)
-                                              'flamegraph-call-site))))
+                    (pcase-let ((`(,rb ,re ,w) r))
+                      (when (and (<= bol rb) (>= eol re))
+                        (add-face-text-property
+                         rb re (flamegraph--call-site-face w))))))
                 (push (flamegraph--snippet-line n (= n line)) out)
                 (setq prev n))
               (mapconcat #'identity (nreverse out) "\n"))))))))
@@ -663,14 +683,17 @@ in completely unrelated forms (a literal `(if …)' 
elsewhere), or
 may not exist at all (`dolist' → `while'/`let').  Only real function
 matches narrow, which is what preserves structural attribution.
 
-Returns a list of (POS-BEG . POS-END) regions to highlight."
-  (let (regions)
+Each region is (POS-BEG POS-END WEIGHT), WEIGHT being the callee's
+share of NODE's count, for heat styling."
+  (let ((total (max 1 (profiler-calltree-count node)))
+        regions)
     (cl-labels
         ((walk (n region)
            (dolist (k (profiler-calltree-children n))
              (let* ((entry (profiler-calltree-entry k))
                     (name (flamegraph--frame-display-name entry))
-                    (skip (flamegraph--skip-through-p entry)))
+                    (skip (flamegraph--skip-through-p entry))
+                    (weight (/ (float (profiler-calltree-count k)) total)))
                (cond
                 (skip
                  ;; Special form — transparent descend, no narrowing.
@@ -685,7 +708,7 @@ Returns a list of (POS-BEG . POS-END) regions to highlight."
                      (while (re-search-forward re (cdr region) t)
                        (let ((mb (match-beginning 0))
                              (me (match-end 0)))
-                         (push (cons mb me) regions)
+                         (push (list mb me weight) regions)
                          (when-let* ((sub (flamegraph--enclosing-form-region
                                            mb)))
                            (walk k sub))))))))))))
diff --git a/test/flamegraph-tests.el b/test/flamegraph-tests.el
index 94f5644cdc..8efadf6f7f 100644
--- a/test/flamegraph-tests.el
+++ b/test/flamegraph-tests.el
@@ -108,8 +108,8 @@
      ,@body))
 
 (defun flamegraph-test--region-texts (regions)
-  "Texts spanned by REGIONS (a list of (BEG . END)) in current buffer."
-  (mapcar (lambda (r) (buffer-substring-no-properties (car r) (cdr r)))
+  "Texts spanned by REGIONS (a list of (BEG END WEIGHT)) in current buffer."
+  (mapcar (lambda (r) (buffer-substring-no-properties (car r) (cadr r)))
           regions))
 
 (ert-deftest flamegraph-test-walker-strict-nesting ()
@@ -225,22 +225,29 @@ callees outside it must still be found."
       (should s)
       (should (string-match-p "^ ▸ +2 " s)))))
 
+(defun flamegraph-test--has-face (string face)
+  "Non-nil if FACE is applied anywhere in STRING (symbol or in a list)."
+  (cl-loop for i below (length string)
+           for f = (get-text-property i 'face string)
+           thereis (or (eq f face) (and (listp f) (memq face f)))))
+
 (ert-deftest flamegraph-test-source-snippet-applies-highlight-face ()
-  "Callee matches in the snippet carry the `flamegraph-call-site' face."
+  "Callee matches in the snippet carry a heat face scaled by share.
+foo at 100% of outer is hot; bar at 10% is warm."
   (flamegraph-test--with-temp-source path
       "(defun outer ()\n  (foo (bar)))\n"
     (let* ((bar (profiler-make-calltree :entry 'bar :count 1))
-           (foo (profiler-make-calltree :entry 'foo :count 1
+           (foo (profiler-make-calltree :entry 'foo :count 10
                                         :children (list bar)))
-           (outer (profiler-make-calltree :entry 'outer :count 1
+           (outer (profiler-make-calltree :entry 'outer :count 10
                                           :children (list foo)))
            (s (flamegraph--source-snippet
                path 1
                (lambda (b e)
                  (flamegraph--collect-nested-call-sites outer b e)))))
       (should s)
-      (should (text-property-any 0 (length s)
-                                 'face 'flamegraph-call-site s)))))
+      (should (flamegraph-test--has-face s 'flamegraph-call-site-hot))
+      (should (flamegraph-test--has-face s 'flamegraph-call-site-warm)))))
 
 ;;; Calls-tree rendering — `flamegraph--insert-call-tree'
 

Reply via email to