Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

264 lines
8.7 KiB

4 years ago
  1. (defpackage :swank-trace-dialog
  2. (:use :cl)
  3. (:import-from :swank :defslimefun :from-string :to-string)
  4. (:export #:clear-trace-tree
  5. #:dialog-toggle-trace
  6. #:dialog-trace
  7. #:dialog-traced-p
  8. #:dialog-untrace
  9. #:dialog-untrace-all
  10. #:inspect-trace-part
  11. #:report-partial-tree
  12. #:report-specs
  13. #:report-total
  14. #:report-trace-detail
  15. #:report-specs
  16. #:trace-format
  17. #:still-inside
  18. #:exited-non-locally
  19. #:*record-backtrace*
  20. #:*traces-per-report*
  21. #:*dialog-trace-follows-trace*
  22. #:find-trace-part
  23. #:find-trace))
  24. (in-package :swank-trace-dialog)
  25. (defparameter *record-backtrace* nil
  26. "Record a backtrace of the last 20 calls for each trace.
  27. Beware that this may have a drastic performance impact on your
  28. program.")
  29. (defparameter *traces-per-report* 150
  30. "Number of traces to report to emacs in each batch.")
  31. ;;;; `trace-entry' model
  32. ;;;;
  33. (defvar *traces* (make-array 1000 :fill-pointer 0
  34. :adjustable t))
  35. (defvar *trace-lock* (swank/backend:make-lock :name "swank-trace-dialog lock"))
  36. (defvar *current-trace-by-thread* (make-hash-table))
  37. (defclass trace-entry ()
  38. ((id :reader id-of)
  39. (children :accessor children-of :initform nil)
  40. (backtrace :accessor backtrace-of :initform (when *record-backtrace*
  41. (useful-backtrace)))
  42. (spec :initarg :spec :accessor spec-of
  43. :initform (error "must provide a spec"))
  44. (args :initarg :args :accessor args-of
  45. :initform (error "must provide args"))
  46. (parent :initarg :parent :reader parent-of
  47. :initform (error "must provide a parent, even if nil"))
  48. (retlist :initarg :retlist :accessor retlist-of
  49. :initform 'still-inside)))
  50. (defmethod initialize-instance :after ((entry trace-entry) &rest initargs)
  51. (declare (ignore initargs))
  52. (if (parent-of entry)
  53. (nconc (children-of (parent-of entry)) (list entry)))
  54. (swank/backend:call-with-lock-held
  55. *trace-lock*
  56. #'(lambda ()
  57. (setf (slot-value entry 'id) (fill-pointer *traces*))
  58. (vector-push-extend entry *traces*))))
  59. (defmethod print-object ((entry trace-entry) stream)
  60. (print-unreadable-object (entry stream)
  61. (format stream "~a: ~a" (id-of entry) (spec-of entry))))
  62. (defun completed-p (trace) (not (eq (retlist-of trace) 'still-inside)))
  63. (defun find-trace (id)
  64. (when (<= 0 id (1- (length *traces*)))
  65. (aref *traces* id)))
  66. (defun find-trace-part (id part-id type)
  67. (let* ((trace (find-trace id))
  68. (l (and trace
  69. (ecase type
  70. (:arg (args-of trace))
  71. (:retval (swank::ensure-list (retlist-of trace)))))))
  72. (values (nth part-id l)
  73. (< part-id (length l)))))
  74. (defun useful-backtrace ()
  75. (swank/backend:call-with-debugging-environment
  76. #'(lambda ()
  77. (loop for i from 0
  78. for frame in (swank/backend:compute-backtrace 0 20)
  79. collect (list i (swank::frame-to-string frame))))))
  80. (defun current-trace ()
  81. (gethash (swank/backend:current-thread) *current-trace-by-thread*))
  82. (defun (setf current-trace) (trace)
  83. (setf (gethash (swank/backend:current-thread) *current-trace-by-thread*)
  84. trace))
  85. ;;;; Control of traced specs
  86. ;;;
  87. (defvar *traced-specs* '())
  88. (defslimefun dialog-trace (spec)
  89. (flet ((before-hook (args)
  90. (setf (current-trace) (make-instance 'trace-entry
  91. :spec spec
  92. :args args
  93. :parent (current-trace))))
  94. (after-hook (retlist)
  95. (let ((trace (current-trace)))
  96. (when trace
  97. ;; the current trace might have been wiped away if the
  98. ;; user cleared the tree in the meantime. no biggie,
  99. ;; don't do anything.
  100. ;;
  101. (setf (retlist-of trace) retlist
  102. (current-trace) (parent-of trace))))))
  103. (when (dialog-traced-p spec)
  104. (warn "~a is apparently already traced! Untracing and retracing." spec)
  105. (dialog-untrace spec))
  106. (swank/backend:wrap spec 'trace-dialog
  107. :before #'before-hook
  108. :after #'after-hook)
  109. (pushnew spec *traced-specs*)
  110. (format nil "~a is now traced for trace dialog" spec)))
  111. (defslimefun dialog-untrace (spec)
  112. (swank/backend:unwrap spec 'trace-dialog)
  113. (setq *traced-specs* (remove spec *traced-specs* :test #'equal))
  114. (format nil "~a is now untraced for trace dialog" spec))
  115. (defslimefun dialog-toggle-trace (spec)
  116. (if (dialog-traced-p spec)
  117. (dialog-untrace spec)
  118. (dialog-trace spec)))
  119. (defslimefun dialog-traced-p (spec)
  120. (find spec *traced-specs* :test #'equal))
  121. (defslimefun dialog-untrace-all ()
  122. (untrace)
  123. (mapcar #'dialog-untrace *traced-specs*))
  124. (defparameter *dialog-trace-follows-trace* nil)
  125. (setq swank:*after-toggle-trace-hook*
  126. #'(lambda (spec traced-p)
  127. (when *dialog-trace-follows-trace*
  128. (cond (traced-p
  129. (dialog-trace spec)
  130. "traced for trace dialog as well")
  131. (t
  132. (dialog-untrace spec)
  133. "untraced for the trace dialog as well")))))
  134. ;;;; A special kind of trace call
  135. ;;;
  136. (defun trace-format (format-spec &rest format-args)
  137. "Make a string from FORMAT-SPEC and FORMAT-ARGS and as a trace."
  138. (let* ((line (apply #'format nil format-spec format-args)))
  139. (make-instance 'trace-entry :spec line
  140. :args format-args
  141. :parent (current-trace)
  142. :retlist nil)))
  143. ;;;; Reporting to emacs
  144. ;;;
  145. (defparameter *visitor-idx* 0)
  146. (defparameter *visitor-key* nil)
  147. (defvar *unfinished-traces* '())
  148. (defun describe-trace-for-emacs (trace)
  149. `(,(id-of trace)
  150. ,(and (parent-of trace) (id-of (parent-of trace)))
  151. ,(spec-of trace)
  152. ,(loop for arg in (args-of trace)
  153. for i from 0
  154. collect (list i (swank::to-line arg)))
  155. ,(loop for retval in (swank::ensure-list (retlist-of trace))
  156. for i from 0
  157. collect (list i (swank::to-line retval)))))
  158. (defslimefun report-partial-tree (key)
  159. (unless (equal key *visitor-key*)
  160. (setq *visitor-idx* 0
  161. *visitor-key* key))
  162. (let* ((recently-finished
  163. (loop with i = 0
  164. for trace in *unfinished-traces*
  165. while (< i *traces-per-report*)
  166. when (completed-p trace)
  167. collect trace
  168. and do
  169. (incf i)
  170. (setq *unfinished-traces*
  171. (remove trace *unfinished-traces*))))
  172. (new (loop for i
  173. from (length recently-finished)
  174. below *traces-per-report*
  175. while (< *visitor-idx* (length *traces*))
  176. for trace = (aref *traces* *visitor-idx*)
  177. collect trace
  178. unless (completed-p trace)
  179. do (push trace *unfinished-traces*)
  180. do (incf *visitor-idx*))))
  181. (list
  182. (mapcar #'describe-trace-for-emacs
  183. (append recently-finished new))
  184. (- (length *traces*) *visitor-idx*)
  185. key)))
  186. (defslimefun report-trace-detail (trace-id)
  187. (swank::call-with-bindings
  188. swank::*inspector-printer-bindings*
  189. #'(lambda ()
  190. (let ((trace (find-trace trace-id)))
  191. (when trace
  192. (append
  193. (describe-trace-for-emacs trace)
  194. (list (backtrace-of trace)
  195. (swank::to-line trace))))))))
  196. (defslimefun report-specs ()
  197. (sort (copy-list *traced-specs*)
  198. #'string<
  199. :key #'princ-to-string))
  200. (defslimefun report-total ()
  201. (length *traces*))
  202. (defslimefun clear-trace-tree ()
  203. (setf *current-trace-by-thread* (clrhash *current-trace-by-thread*)
  204. *visitor-key* nil
  205. *unfinished-traces* nil)
  206. (swank/backend:call-with-lock-held
  207. *trace-lock*
  208. #'(lambda () (setf (fill-pointer *traces*) 0)))
  209. nil)
  210. ;; HACK: `swank::*inspector-history*' is unbound by default and needs
  211. ;; a reset in that case so that it won't error `swank::inspect-object'
  212. ;; before any other object is inspected in the slime session.
  213. ;;
  214. (unless (boundp 'swank::*inspector-history*)
  215. (swank::reset-inspector))
  216. (defslimefun inspect-trace-part (trace-id part-id type)
  217. (multiple-value-bind (obj found)
  218. (find-trace-part trace-id part-id type)
  219. (if found
  220. (swank::inspect-object obj)
  221. (error "No object found with ~a, ~a and ~a" trace-id part-id type))))
  222. (provide :swank-trace-dialog)