|
|
- (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)
|