changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/lib/rt/flamegraph.lisp

changeset 698: 96958d3eb5b0
parent: a37b1d3371fc
author: Richard Westhaver <ellis@rwest.io>
date: Fri, 04 Oct 2024 22:04:59 -0400
permissions: -rw-r--r--
description: fixes
1 (in-package :rt/flamegraph)
2 (defparameter *frame-where-profiling-was-started* nil)
3 
4 (defmacro with-open-file* ((stream filespec &key direction element-type
5  if-exists if-does-not-exist external-format)
6  &body body)
7  "Just like WITH-OPEN-FILE, but NIL values in the keyword arguments
8 mean to use the default value specified for OPEN."
9  (once-only (direction element-type if-exists if-does-not-exist external-format)
10  `(with-open-stream
11  (,stream (apply #'open ,filespec
12  (append
13  (when ,direction
14  (list :direction ,direction))
15  (list :element-type ,(or element-type '(unsigned-byte 8)))
16  (when ,if-exists
17  (list :if-exists ,if-exists))
18  (when ,if-does-not-exist
19  (list :if-does-not-exist ,if-does-not-exist))
20  (when ,external-format
21  (list :external-format ,external-format)))))
22  ,@body)))
23 
24 (defmacro with-output-to-file ((stream-name file-name &rest args
25  &key (direction nil direction-p)
26  &allow-other-keys)
27  &body body)
28  "Evaluate BODY with STREAM-NAME to an output stream on the file
29 FILE-NAME. ARGS is sent as is to the call to OPEN except EXTERNAL-FORMAT,
30 which is only sent to WITH-OPEN-FILE when it's not NIL."
31  (declare (ignore direction))
32  (when direction-p
33  (error "Can't specify :DIRECTION for WITH-OUTPUT-TO-FILE."))
34  `(with-open-file* (,stream-name ,file-name :direction :output ,@args)
35  ,@body))
36 
37 (defclass flamegraph-node ()
38  ((func :initarg :func
39  :initform nil
40  :type (or string
41  sb-di::compiled-debug-fun
42  null)
43  :accessor get-func)
44  (counter :initform 0
45  :type fixnum
46  :initarg :counter
47  :accessor get-counter)
48  (calls :initform nil
49  :type list
50  :initarg :calls
51  :documentation "A list of other nodes, called by current one"
52  :accessor get-calls)))
53 
54 (defmethod print-object ((node flamegraph-node) stream)
55  (print-unreadable-object (node stream :type t)
56  (format stream "~A :calls ~A"
57  (or (get-func node)
58  "<root>")
59  (get-counter node))))
60 
61 (defun search-or-add-child (node func)
62  ;; Not all frames contain an info for some reason.
63  ;; We only want to show meaningfull nodes
64  (when func
65  (let* ((children (get-calls node))
66  (child (find func children
67  :test #'equal
68  :key #'get-func)))
69  (unless child
70  (setf child (make-instance 'flamegraph-node :func func))
71  (push child (get-calls node)))
72  child)))
73 
74 (defgeneric get-name (obj))
75 
76 (defmethod get-name ((obj flamegraph-node))
77  (get-name (get-func obj)))
78 
79 (defmethod get-name ((obj string))
80  obj)
81 
82 (defmethod get-name ((obj sb-di::compiled-debug-fun))
83  (get-name (slot-value obj 'SB-DI::COMPILER-DEBUG-FUN)))
84 
85 (defmethod get-name ((obj SB-C::COMPILED-DEBUG-FUN))
86  (get-name (slot-value obj 'SB-C::NAME)))
87 
88 (defmethod get-name ((obj cons))
89  (let ((*print-pretty* nil))
90  (format nil "~S" obj)))
91 
92 (defmethod get-name ((obj symbol))
93  (symbol-name obj))
94 
95 (defmethod get-name ((obj sb-kernel:code-component))
96  "Some binary code")
97 
98 (defun aggregate-raw-data ()
99  ;; We need to actually run a report once to make the call graph
100  ;; available to map.
101  (sb-sprof:report :stream (make-broadcast-stream)))
102 
103 (defun make-graph ()
104  (aggregate-raw-data)
105  (let ((root (make-instance 'flamegraph-node)))
106  (sb-sprof:map-traces
107  (lambda (thread trace)
108  (declare (ignorable thread))
109  (let ((current-node root))
110  (sb-sprof::map-trace-pc-locs
111  (lambda (info pc-or-offset)
112  (declare (ignorable pc-or-offset))
113  (let ((node (search-or-add-child current-node
114  info)))
115  (when node
116  (incf (get-counter node))
117  (setf current-node
118  node))))
119  trace)))
120  sb-sprof::*samples*)
121  root))
122 
123 (defun remove-nodes-up-to-frame (nodes frame)
124  (let ((func (slot-value frame 'sb-di::debug-fun)))
125  (loop for rest on nodes
126  for node = (car rest)
127  when (eql (get-func node)
128  func)
129  do (return (cdr rest)))))
130 
131 (defun print-graph (root &key (stream t) (max-depth most-positive-fixnum))
132  (let* ((roots (get-calls root)))
133  (labels ((print-path (path count)
134  (let* ((nodes (reverse path))
135  (rest-nodes (remove-nodes-up-to-frame nodes
136  *frame-where-profiling-was-started*))
137  (names (mapcar #'get-name rest-nodes)))
138  (when names
139  (format stream "~{~A~^;~} ~A~%"
140  names
141  count))))
142  (print-node (node &optional path (depth 0))
143  (when (< depth max-depth)
144  (let* ((count (get-counter node))
145  (path (list* node path))
146  (children (get-calls node)))
147  (when (> count 0)
148  (print-path path count))
149  (loop for child in children
150  do (print-node child path (1+ depth)))))))
151  (mapcar #'print-node
152  roots)
153  (values))))
154 
155 (defmacro save-flamegraph ((filename &rest sb-sprof-opts) &body body)
156  (with-gensyms (result-var)
157  `(let ((*frame-where-profiling-was-started*
158  (sb-di:top-frame))
159  (,result-var nil))
160  (with-simple-restart (abort "Stop profiling and save graph")
161  (sb-sprof:with-profiling (,@sb-sprof-opts)
162  (setf ,result-var
163  (multiple-value-list
164  (progn ,@body)))))
165  (with-output-to-file (s ,filename :if-exists :supersede)
166  (print-graph (make-graph)
167  :stream s))
168  (values-list ,result-var))))