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) 4 (defmacro with-open-file* ((stream filespec &key direction element-type 5 if-exists if-does-not-exist external-format) 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) 11 (,stream (apply #'open ,filespec 14 (list :direction ,direction)) 15 (list :element-type ,(or element-type '(unsigned-byte 8))) 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))))) 24 (defmacro with-output-to-file ((stream-name file-name &rest args 25 &key (direction nil direction-p) 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)) 33 (error "Can't specify :DIRECTION for WITH-OUTPUT-TO-FILE.")) 34 `(with-open-file* (,stream-name ,file-name :direction :output ,@args) 37 (defclass flamegraph-node () 41 sb-di::compiled-debug-fun 47 :accessor get-counter) 51 :documentation "A list of other nodes, called by current one" 52 :accessor get-calls))) 54 (defmethod print-object ((node flamegraph-node) stream) 55 (print-unreadable-object (node stream :type t) 56 (format stream "~A :calls ~A" 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 65 (let* ((children (get-calls node)) 66 (child (find func children 70 (setf child (make-instance 'flamegraph-node :func func)) 71 (push child (get-calls node))) 74 (defgeneric get-name (obj)) 76 (defmethod get-name ((obj flamegraph-node)) 77 (get-name (get-func obj))) 79 (defmethod get-name ((obj string)) 82 (defmethod get-name ((obj sb-di::compiled-debug-fun)) 83 (get-name (slot-value obj 'SB-DI::COMPILER-DEBUG-FUN))) 85 (defmethod get-name ((obj SB-C::COMPILED-DEBUG-FUN)) 86 (get-name (slot-value obj 'SB-C::NAME))) 88 (defmethod get-name ((obj cons)) 89 (let ((*print-pretty* nil)) 90 (format nil "~S" obj))) 92 (defmethod get-name ((obj symbol)) 95 (defmethod get-name ((obj sb-kernel:code-component)) 98 (defun aggregate-raw-data () 99 ;; We need to actually run a report once to make the call graph 101 (sb-sprof:report :stream (make-broadcast-stream))) 105 (let ((root (make-instance 'flamegraph-node))) 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 116 (incf (get-counter node)) 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) 129 do (return (cdr rest))))) 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))) 139 (format stream "~{~A~^;~} ~A~%" 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))) 148 (print-path path count)) 149 (loop for child in children 150 do (print-node child path (1+ depth))))))) 155 (defmacro save-flamegraph ((filename &rest sb-sprof-opts) &body body) 156 (with-gensyms (result-var) 157 `(let ((*frame-where-profiling-was-started* 160 (with-simple-restart (abort "Stop profiling and save graph") 161 (sb-sprof:with-profiling (,@sb-sprof-opts) 165 (with-output-to-file (s ,filename :if-exists :supersede) 166 (print-graph (make-graph) 168 (values-list ,result-var))))