branch: elpa/flamegraph
commit 9cdbb0fbecf3493cdd96a7b543c493aea5826ebb
Author: Dmitry Gutov <[email protected]>
Commit: Dmitry Gutov <[email protected]>
Match rbspy native Ruby frames in source
---
flamegraph.el | 38 +++++++++++++++++++++++++++-----------
test/flamegraph-tests.el | 45 +++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 70 insertions(+), 13 deletions(-)
diff --git a/flamegraph.el b/flamegraph.el
index ec05fa1e1b..1fb1ec9c9f 100644
--- a/flamegraph.el
+++ b/flamegraph.el
@@ -555,11 +555,29 @@ necessarily its definition."
(flamegraph--entry-name entry))))))))
(defun flamegraph--frame-display-name (entry)
- "Return ENTRY's name with any embedded FILE:LINE location stripped."
- (if (and (stringp entry)
- (string-match "\\([^ ():]*\\.[^ ():]+\\):\\([0-9]+\\)" entry))
- (string-trim-right (substring entry 0 (match-beginning 0)) "[ (:-]+")
- (flamegraph--entry-name entry)))
+ "Return ENTRY's name with embedded source location text stripped."
+ (cond
+ ((and (stringp entry)
+ (string-match "\\([^ ():]*\\.[^ ():]+\\):\\([0-9]+\\)" entry))
+ (string-trim-right (substring entry 0 (match-beginning 0)) "[ (:-]+"))
+ ((and (stringp entry)
+ (string-match "\\`\\(.+\\) - (unknown)\\'" entry))
+ (match-string 1 entry))
+ (t (flamegraph--entry-name entry))))
+
+(defun flamegraph--frame-search-name (entry)
+ "Return ENTRY's name for matching against source text.
+This can differ from `flamegraph--frame-display-name' when a profiler
+adds metadata around the source spelling of the frame."
+ (cond
+ ;; profiler.el: anonymous function objects are written as lambda forms.
+ ((flamegraph--el-anonymous-function-p entry) "lambda")
+ ;; rbspy: Ruby native frames include implementation/source metadata.
+ ((and (stringp entry)
+ (string-match
+ "\\`\\(.+?\\) \\[c function\\] - (unknown)\\'" entry))
+ (match-string 1 entry))
+ (t (flamegraph--frame-display-name entry))))
(defvar-keymap flamegraph--snippet-line-map
:doc "Keymap active on source snippet lines."
@@ -717,8 +735,8 @@ symbol boundaries in the target buffer's syntax."
(and (stringp name) (not (string-empty-p name))
(not (string-match-p "[][[:space:]();,]" name))))
-(defun flamegraph--anonymous-function-p (entry)
- "Non-nil if ENTRY is a function object with no source-searchable name."
+(defun flamegraph--el-anonymous-function-p (entry)
+ "Non-nil if ENTRY is an anonymous Elisp function object."
(and (not (symbolp entry)) (functionp entry)))
(defun flamegraph--collect-nested-call-sites (node beg end &optional shown)
@@ -759,10 +777,8 @@ callees the snippet omits."
(let ((any nil))
(dolist (k (profiler-calltree-children n))
(let* ((entry (profiler-calltree-entry k))
- (anonymous (flamegraph--anonymous-function-p entry))
- (name (if anonymous
- "lambda"
- (flamegraph--frame-display-name entry)))
+ (anonymous (flamegraph--el-anonymous-function-p entry))
+ (name (flamegraph--frame-search-name entry))
(weight (/ (float (profiler-calltree-count k)) total)))
(cond
((flamegraph--skip-through-p entry)
diff --git a/test/flamegraph-tests.el b/test/flamegraph-tests.el
index c187a81bba..b89c7eb1ae 100644
--- a/test/flamegraph-tests.el
+++ b/test/flamegraph-tests.el
@@ -45,7 +45,7 @@
(should (null (flamegraph--entry-location "[unknown]")))
(should (null (flamegraph--entry-location ""))))
-;;; Display name strips the embedded location
+;;; Display/search names strip profiler metadata for different uses
(ert-deftest flamegraph-test-frame-display-name-strips-location ()
(should (equal (flamegraph--frame-display-name
@@ -54,12 +54,33 @@
(should (equal (flamegraph--frame-display-name "work (app/main.py:20)")
"work"))
(should (equal (flamegraph--frame-display-name "block in foo -
lib/foo.rb:12")
- "block in foo")))
+ "block in foo"))
+ (should (equal (flamegraph--frame-display-name
+ "select [c function] - (unknown)")
+ "select [c function]")))
(ert-deftest flamegraph-test-frame-display-name-pass-through ()
(should (equal (flamegraph--frame-display-name "emacs") "emacs"))
(should (equal (flamegraph--frame-display-name "[unknown]") "[unknown]")))
+(ert-deftest flamegraph-test-frame-search-name-rbspy-native ()
+ "Ruby native frames from rbspy search by method name."
+ (should (equal (flamegraph--frame-search-name
+ "gsub! [c function] - (unknown)")
+ "gsub!"))
+ (should (equal (flamegraph--frame-search-name
+ "any? [c function] - (unknown)")
+ "any?"))
+ (should (equal (flamegraph--frame-search-name
+ "block in foo - lib/foo.rb:12")
+ "block in foo")))
+
+(ert-deftest flamegraph-test-frame-search-name-el-anonymous ()
+ "Anonymous Elisp function objects search as lambda."
+ (should (equal (flamegraph--frame-search-name
+ (make-byte-code 257 "\300\207" [] 2))
+ "lambda")))
+
;;; skip-through-p / callee-name-acceptable-p
(ert-deftest flamegraph-test-skip-through-p ()
@@ -127,6 +148,26 @@
(mapcar (lambda (r) (buffer-substring-no-properties (car r) (cadr r)))
regions))
+(ert-deftest flamegraph-test-walker-rbspy-native-frame-search-name ()
+ "Ruby native frames are matched by method name in source."
+ (with-temp-buffer
+ (ruby-mode)
+ (insert "def outer
+ name.gsub!(/x/, y)
+ items.any? { |item| item.ok? }
+end")
+ (let* ((gsub (profiler-make-calltree
+ :entry "gsub! [c function] - (unknown)" :count 10))
+ (any (profiler-make-calltree
+ :entry "any? [c function] - (unknown)" :count 5))
+ (outer (profiler-make-calltree
+ :entry "outer - lib/foo.rb:1" :count 15
+ :children (list gsub any)))
+ (regions (flamegraph--collect-nested-call-sites
+ outer (point-min) (point-max))))
+ (should (equal (flamegraph-test--region-texts regions)
+ '("gsub!" "any?"))))))
+
(ert-deftest flamegraph-test-walker-strict-nesting ()
"Real-function chains narrow strictly: a stray sibling is not matched.
For `(foo (bar (baz)))' with a separate `(bar 'unrelated)' elsewhere,