1
0
Fork 0
mirror of synced 2024-12-27 00:53:20 -05:00
ultimate-vim/sources_non_forked/slimv/slime/contrib/swank-trace-dialog.lisp
2022-06-05 18:14:25 +08:00

264 lines
8.7 KiB
Common Lisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(defpackage :swank-trace-dialog
(:use :cl)
(:import-from :swank :defslimefun :from-string :to-string)
(:export #:clear-trace-tree
#:dialog-toggle-trace
#:dialog-trace
#:dialog-traced-p
#:dialog-untrace
#:dialog-untrace-all
#:inspect-trace-part
#:report-partial-tree
#:report-specs
#:report-total
#:report-trace-detail
#:report-specs
#:trace-format
#:still-inside
#:exited-non-locally
#:*record-backtrace*
#:*traces-per-report*
#:*dialog-trace-follows-trace*
#:find-trace-part
#:find-trace))
(in-package :swank-trace-dialog)
(defparameter *record-backtrace* nil
"Record a backtrace of the last 20 calls for each trace.
Beware that this may have a drastic performance impact on your
program.")
(defparameter *traces-per-report* 150
"Number of traces to report to emacs in each batch.")
;;;; `trace-entry' model
;;;;
(defvar *traces* (make-array 1000 :fill-pointer 0
:adjustable t))
(defvar *trace-lock* (swank/backend:make-lock :name "swank-trace-dialog lock"))
(defvar *current-trace-by-thread* (make-hash-table))
(defclass trace-entry ()
((id :reader id-of)
(children :accessor children-of :initform nil)
(backtrace :accessor backtrace-of :initform (when *record-backtrace*
(useful-backtrace)))
(spec :initarg :spec :accessor spec-of
:initform (error "must provide a spec"))
(args :initarg :args :accessor args-of
:initform (error "must provide args"))
(parent :initarg :parent :reader parent-of
:initform (error "must provide a parent, even if nil"))
(retlist :initarg :retlist :accessor retlist-of
:initform 'still-inside)))
(defmethod initialize-instance :after ((entry trace-entry) &rest initargs)
(declare (ignore initargs))
(if (parent-of entry)
(nconc (children-of (parent-of entry)) (list entry)))
(swank/backend:call-with-lock-held
*trace-lock*
#'(lambda ()
(setf (slot-value entry 'id) (fill-pointer *traces*))
(vector-push-extend entry *traces*))))
(defmethod print-object ((entry trace-entry) stream)
(print-unreadable-object (entry stream)
(format stream "~a: ~a" (id-of entry) (spec-of entry))))
(defun completed-p (trace) (not (eq (retlist-of trace) 'still-inside)))
(defun find-trace (id)
(when (<= 0 id (1- (length *traces*)))
(aref *traces* id)))
(defun find-trace-part (id part-id type)
(let* ((trace (find-trace id))
(l (and trace
(ecase type
(:arg (args-of trace))
(:retval (swank::ensure-list (retlist-of trace)))))))
(values (nth part-id l)
(< part-id (length l)))))
(defun useful-backtrace ()
(swank/backend:call-with-debugging-environment
#'(lambda ()
(loop for i from 0
for frame in (swank/backend:compute-backtrace 0 20)
collect (list i (swank::frame-to-string frame))))))
(defun current-trace ()
(gethash (swank/backend:current-thread) *current-trace-by-thread*))
(defun (setf current-trace) (trace)
(setf (gethash (swank/backend:current-thread) *current-trace-by-thread*)
trace))
;;;; Control of traced specs
;;;
(defvar *traced-specs* '())
(defslimefun dialog-trace (spec)
(flet ((before-hook (args)
(setf (current-trace) (make-instance 'trace-entry
:spec spec
:args args
:parent (current-trace))))
(after-hook (retlist)
(let ((trace (current-trace)))
(when trace
;; the current trace might have been wiped away if the
;; user cleared the tree in the meantime. no biggie,
;; don't do anything.
;;
(setf (retlist-of trace) retlist
(current-trace) (parent-of trace))))))
(when (dialog-traced-p spec)
(warn "~a is apparently already traced! Untracing and retracing." spec)
(dialog-untrace spec))
(swank/backend:wrap spec 'trace-dialog
:before #'before-hook
:after #'after-hook)
(pushnew spec *traced-specs*)
(format nil "~a is now traced for trace dialog" spec)))
(defslimefun dialog-untrace (spec)
(swank/backend:unwrap spec 'trace-dialog)
(setq *traced-specs* (remove spec *traced-specs* :test #'equal))
(format nil "~a is now untraced for trace dialog" spec))
(defslimefun dialog-toggle-trace (spec)
(if (dialog-traced-p spec)
(dialog-untrace spec)
(dialog-trace spec)))
(defslimefun dialog-traced-p (spec)
(find spec *traced-specs* :test #'equal))
(defslimefun dialog-untrace-all ()
(untrace)
(mapcar #'dialog-untrace *traced-specs*))
(defparameter *dialog-trace-follows-trace* nil)
(setq swank:*after-toggle-trace-hook*
#'(lambda (spec traced-p)
(when *dialog-trace-follows-trace*
(cond (traced-p
(dialog-trace spec)
"traced for trace dialog as well")
(t
(dialog-untrace spec)
"untraced for the trace dialog as well")))))
;;;; A special kind of trace call
;;;
(defun trace-format (format-spec &rest format-args)
"Make a string from FORMAT-SPEC and FORMAT-ARGS and as a trace."
(let* ((line (apply #'format nil format-spec format-args)))
(make-instance 'trace-entry :spec line
:args format-args
:parent (current-trace)
:retlist nil)))
;;;; Reporting to emacs
;;;
(defparameter *visitor-idx* 0)
(defparameter *visitor-key* nil)
(defvar *unfinished-traces* '())
(defun describe-trace-for-emacs (trace)
`(,(id-of trace)
,(and (parent-of trace) (id-of (parent-of trace)))
,(spec-of trace)
,(loop for arg in (args-of trace)
for i from 0
collect (list i (swank::to-line arg)))
,(loop for retval in (swank::ensure-list (retlist-of trace))
for i from 0
collect (list i (swank::to-line retval)))))
(defslimefun report-partial-tree (key)
(unless (equal key *visitor-key*)
(setq *visitor-idx* 0
*visitor-key* key))
(let* ((recently-finished
(loop with i = 0
for trace in *unfinished-traces*
while (< i *traces-per-report*)
when (completed-p trace)
collect trace
and do
(incf i)
(setq *unfinished-traces*
(remove trace *unfinished-traces*))))
(new (loop for i
from (length recently-finished)
below *traces-per-report*
while (< *visitor-idx* (length *traces*))
for trace = (aref *traces* *visitor-idx*)
collect trace
unless (completed-p trace)
do (push trace *unfinished-traces*)
do (incf *visitor-idx*))))
(list
(mapcar #'describe-trace-for-emacs
(append recently-finished new))
(- (length *traces*) *visitor-idx*)
key)))
(defslimefun report-trace-detail (trace-id)
(swank::call-with-bindings
swank::*inspector-printer-bindings*
#'(lambda ()
(let ((trace (find-trace trace-id)))
(when trace
(append
(describe-trace-for-emacs trace)
(list (backtrace-of trace)
(swank::to-line trace))))))))
(defslimefun report-specs ()
(sort (copy-list *traced-specs*)
#'string<
:key #'princ-to-string))
(defslimefun report-total ()
(length *traces*))
(defslimefun clear-trace-tree ()
(setf *current-trace-by-thread* (clrhash *current-trace-by-thread*)
*visitor-key* nil
*unfinished-traces* nil)
(swank/backend:call-with-lock-held
*trace-lock*
#'(lambda () (setf (fill-pointer *traces*) 0)))
nil)
;; HACK: `swank::*inspector-history*' is unbound by default and needs
;; a reset in that case so that it won't error `swank::inspect-object'
;; before any other object is inspected in the slime session.
;;
(unless (boundp 'swank::*inspector-history*)
(swank::reset-inspector))
(defslimefun inspect-trace-part (trace-id part-id type)
(multiple-value-bind (obj found)
(find-trace-part trace-id part-id type)
(if found
(swank::inspect-object obj)
(error "No object found with ~a, ~a and ~a" trace-id part-id type))))
(provide :swank-trace-dialog)