bug-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

bug#1343: bug#27397: [PATCH] New commands for bulk tracing of elisp func


From: Lars Ingebrigtsen
Subject: bug#1343: bug#27397: [PATCH] New commands for bulk tracing of elisp functions
Date: Sun, 11 Sep 2022 13:49:51 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/29.0.50 (gnu/linux)

Phil Sainty <psainty@orcon.net.nz> writes:

> After a brief hiatus, I've resumed (maybe completed) my work on this.
>
> The branch scratch/bulk-tracing contains the updated code for Emacs 29
> (rebased over master).

For reference, I've included the diff between master and the branch
below.

I have not tried the patch myself -- does anybody have any comments
here?  It seems like useful functionality to me.

diff --git a/doc/lispref/debugging.texi b/doc/lispref/debugging.texi
index 058c931954..6bdcf33a95 100644
--- a/doc/lispref/debugging.texi
+++ b/doc/lispref/debugging.texi
@@ -20,14 +20,10 @@ Debugging
 You can use Edebug, a source-level debugger for Emacs Lisp.
 
 @item
-@cindex tracing Lisp programs
-You can trace the execution of functions involved in the problem using
-the tracing facilities provided by the @file{trace.el} package.  This
-package provides the functions @code{trace-function-foreground} and
-@code{trace-function-background} for tracing function calls, and
-@code{trace-values} for adding values of select variables to the
-trace.  For the details, see the documentation of these facilities in
-@file{trace.el}.
+You can trace the execution of functions involved in the problem
+(logging function calls, their arguments and return values, and other
+context values) using the tracing facilities provided by the
+@file{trace.el} package.
 
 @item
 If a syntactic problem is preventing Lisp from even reading the
@@ -59,6 +55,7 @@ Debugging
 * Syntax Errors::       How to find syntax errors.
 * Test Coverage::       Ensuring you have tested all branches in your code.
 * Profiling::           Measuring the resources that your code uses.
+* Tracing::             Log function calls, arguments, and return values.
 @end menu
 
 @node Debugger
@@ -1072,3 +1069,327 @@ Profiling
 debugging Emacs.  It actually stops the Lisp-level @kbd{M-x
 profiler-@dots{}} commands described above from working.
 @end ifnottex
+
+
+@node Tracing
+@section Tracing
+@cindex tracing
+@cindex trace
+@cindex trace functions
+@cindex tracing Lisp programs
+
+You can trace the execution of functions using the tracing facilities
+provided by the @file{trace.el} library.  Many functions can be traced
+at the same time.  The commands @code{trace-function-foreground} and
+@code{trace-function-background} add a new trace to a single specified
+function.  The commands @code{trace-package}, @code{trace-regexp}, and
+@code{trace-library} enable traces to be added to functions en masse.
+Traces can also be added to autoloaded functions -- the associated
+function will be traced if and when it is defined.
+
+@vindex trace-buffer
+Calls to traced functions, including the values of their arguments and
+their return values, are logged to the @file{*trace-output*} buffer
+(or another buffer as specified -- either by the @code{trace-buffer}
+user option, or as the @var{buffer} argument to a tracing command).
+
+@anchor{trace context}
+@cindex @code{context} in trace functions
+Optional @var{context} expressions are also evaluated, both when the
+associated function is called and again when it returns, with the
+value logged within square brackets alongside the call-time arguments
+or return value respectively.  This could be used to track the current
+buffer or position of point, for instance.  If @var{context} is a
+function, it will be called (with no arguments) to obtain the value to
+be inserted into the trace output buffer.
+
+Finally, you may add explicit calls to @code{trace-values} to your
+code, to log arbitrary values to the trace buffer at any time.
+
+@anchor{background and foreground tracing}
+@cindex foreground tracing
+@cindex background tracing
+When using ``foreground'' tracing, the output buffer will be displayed
+whenever a traced function is called.  When using ``background''
+tracing the output buffer is not forcibly displayed.  Because
+foreground tracing affects the window configuration, it should not be
+used to trace functions that switch buffers, or have other
+display-oriented behaviour.  To avoid such problems, all bulk tracing
+commands use background tracing -- @code{trace-function-foreground} is
+the only command providing foreground tracing.
+
+@menu
+* Commands for Tracing::  Commands and variables.
+* Restrictions on Tracing:: Limitations on what can be traced.
+* Examples of Tracing::   Usage examples.
+@end menu
+
+@node Commands for Tracing
+@subsection Commands and variables for tracing functions
+
+@defopt trace-buffer
+This variable defines the buffer where trace output will be logged to
+by default.  Trace commands can be passed a @var{buffer} argument to
+specify a non-default output buffer.
+@end defopt
+
+@defvar inhibit-trace
+If this variable is non-@code{nil}, all tracing is temporarily
+inhibited (including any calls to @code{trace-values}).
+@end defvar
+
+@deffn Command trace-function-background function &optional buffer context
+This function adds a background trace (@pxref{background and
+foreground tracing}) to @var{function}.  When called interactively, it
+prompts for @var{function} in the minibuffer.  With a prefix argument,
+it also prompts for the trace output @var{buffer} (defaulting to the
+value of @code{trace-buffer}), and a Lisp expression @var{context}
+(@pxref{trace context}).
+
+If @var{function} is an autoload, the associated function will be
+traced if and when it is defined.
+
+Calling @code{trace-function-background} for an already-traced
+@var{function} will update the optional argument behaviours to respect
+the new values (and change to background tracing, if foreground
+tracing was previously used).
+@end deffn
+
+@deffn Command trace-function-foreground function &optional buffer context
+This function adds a foreground trace (@pxref{background and
+foreground tracing}) to @var{function}.  When called interactively, it
+prompts for @var{function} in the minibuffer.  With a prefix argument,
+it also prompts for the trace output @var{buffer} (defaulting to the
+value of @code{trace-buffer}), and a Lisp expression @var{context}
+(@pxref{trace context}).
+
+If @var{function} is an autoload, the associated function will be
+traced if and when it is defined.
+
+Calling @code{trace-function-foreground} for an already-traced
+@var{function} will update the optional argument behaviours to respect
+the new values (and change to foreground tracing, if background
+tracing was previously used).
+@end deffn
+
+@deffn Command trace-package prefix &optional buffer context after-load
+This function calls @code{trace-function-background} for all functions
+with names starting with @var{prefix}.
+
+For any autoload declarations matching @var{prefix}, the associated
+function will be traced if and when it is defined.
+
+With a prefix argument, also prompt for the trace output @var{buffer}
+(defaulting to the value of @code{trace-buffer}); a Lisp expression
+@var{context} (@pxref{trace context}); and boolean query
+@var{after-load}.  If @var{after-load} is non-@code{nil} then
+re-process @var{prefix} after loading any file.
+
+Calling @code{trace-package} again for the same @var{prefix} will
+update the optional argument behaviours to respect the new values.
+@end deffn
+
+@deffn Command trace-regexp regexp &optional buffer context after-load
+This function calls @code{trace-function-background} for all functions
+matching in @var{regexp}.
+
+Background tracing is used.  Switch to the trace output buffer to view
+the results.  For any autoload declarations matching @var{regexp}, the
+associated function will be traced if and when it is defined.
+
+With a prefix argument, also prompt for the trace output @var{buffer}
+(defaulting to the value of @code{trace-buffer}); a Lisp expression
+@var{context} (@pxref{trace context}); and boolean query
+@var{after-load}.  If @var{after-load} is non-@code{nil} then
+re-process @var{regexp} after loading any file.
+
+Calling @code{trace-regexp} again for the same @var{regexp} will
+update the optional argument behaviours to respect the new values.
+
+@strong{Warning:} Do not attempt to trace all functions.  Tracing too
+many functions at one time will render Emacs unusable.
+@end deffn
+
+@deffn Command trace-library library &optional buffer context after-load
+This function calls @code{trace-function-background} for all functions
+currently defined in @var{library} according to @var{load-history}.
+
+For any autoload declarations with a file name matching @var{library},
+the associated function will be traced if and when it is defined.
+(Autoload file names will not match if @var{library} specifies a
+longer, more specific path.)
+
+With a prefix argument, also prompt for the trace output @var{buffer}
+(defaulting to the value of @code{trace-buffer}); a Lisp expression
+@var{context} (@pxref{trace context}); and boolean query
+@var{after-load}.  If @var{after-load} is non-@code{nil} then
+re-process @var{library} after loading it, (ensuring that all of its
+functions will be traced).
+
+Calling @code{trace-library} again for the same @var{library} will
+update the optional argument behaviours to respect the new values.
+@end deffn
+
+@deffn Command trace-currently-traced &optional display-message
+This function returns the list of currently traced function symbols.
+When called interactively, or if @var{display-message} is
+non-@code{nil}, it displays the list as a message.
+@end deffn
+
+@deffn Command untrace-function function
+This function removes the trace on @var{function}.  This has no effect
+if @var{function} was not being traced.  When called interactively, it
+prompts for @var{function} in the minibuffer.
+@end deffn
+
+@deffn Command untrace-package prefix
+This function calls @code{untrace-function} for all functions with
+names starting with @var{prefix}.  When called interactively, it
+prompts for @var{prefix} in the minibuffer.
+@end deffn
+
+@deffn Command untrace-regexp regexp
+This function calls @code{untrace-function} for all functions matching
+@var{regexp}.  When called interactively, it prompts for @var{regexp}
+in the minibuffer.
+@end deffn
+
+@deffn Command untrace-library library
+This function calls @code{untrace-function} for all functions defined
+in @var{library}.  When called interactively, it prompts for
+@var{library} in the minibuffer.
+@end deffn
+
+@deffn Command untrace-all
+This function calls @code{untrace-function} for all functions.
+@end deffn
+
+@deffn Function trace-values &rest values
+This function inserts a message showing @var{values} into the trace
+buffer.  You can add explicit calls to @code{trace-values} into your
+functions in order to provide additional tracing information.
+@end deffn
+
+
+@node Restrictions on Tracing
+@subsection Limitations on what can be traced
+
+@itemize @bullet
+@item
+Only functions/macros/subrs that are called via their function cell
+will generate trace output; hence, you won't get trace output for:
+
+@itemize @bullet
+@item
+Macros that were expanded during compilation.
+
+@item
+Subrs called directly from other subrs/C-code.
+
+@item
+Byte-compiled calls to subrs that have special byte-codes associated
+with them:
+
+@example
+(sort (cl-loop for sym being the symbols
+               if (and (subrp (symbol-function sym))
+                       (plist-get (symbol-plist sym)
+                                  'byte-opcode))
+               collect sym)
+      (lambda (s1 s2)
+        (string< (symbol-name s1) (symbol-name s2))))
+@end example
+@end itemize
+
+@item
+Tracing too many functions at one time will render Emacs unusable.  Do
+not attempt to trace all functions, and take care with the arguments
+passed to the bulk tracing commands @code{trace-package} and
+@code{trace-regexp}.
+
+@item
+Foreground tracing should not be used to trace functions that switch
+buffers, or have other display-oriented behaviour.
+
+@item
+Each function can only be subject to a single trace.  When a function
+which is already being traced is targeted by any tracing command, the
+new trace criteria (including optional argument values) will replace
+the previous trace criteria for that function.
+
+Note that this also means there is no need to un-trace a function in
+order to re-trace it with different arguments.
+
+@item
+All the restrictions that apply to @file{nadvice.el} also apply to
+tracing (as tracing is implemented using advice).  @xref{Advising
+Functions}.
+@end itemize
+
+@node Examples of Tracing
+@subsection Usage examples for function tracing
+
+The following is example trace output, including a context list
+expression, for a function which also makes a call to
+@code{trace-values}.  The left hand column indicates the evaluation
+depth of the function call.
+
+@example
+@group
+1 -> (funcname arg1 arg2) [(context1 context2)]
+1 -> (trace-values value1 value2)
+1 <- funcname: return [(context1 context2)]
+@end group
+@end example
+
+The trace output display of recursion/nesting levels can be
+demonstrated by tracing a recursive function, such as a simplistic
+factorial implementation:
+
+@example
+@group
+(defun fact (n)
+  "Calculate factorial of N."
+  (if (eql n 0) 1
+    (* n (fact (1- n)))))
+     @result{} fact
+
+(trace-function 'fact)
+     @result{} fact
+
+Now, evaluating this...
+
+(fact 4)
+     @result{} 24
+
+...will generate the following in *trace-buffer*:
+
+1 -> fact: n=4
+| 2 -> fact: n=3
+| | 3 -> fact: n=2
+| | | 4 -> fact: n=1
+| | | | 5 -> fact: n=0
+| | | | 5 <- fact: 1
+| | | 4 <- fact: 1
+| | 3 <- fact: 2
+| 2 <- fact: 6
+1 <- fact: 24
+@end group
+
+Try the following for some more interesting trace output:
+
+@group
+(defun ack (x y z)
+  (if (= x 0)
+      (+ y z)
+    (if (and (<= x 2) (= z 0))
+        (1- x)
+      (if (and (> x 2) (= z 0))
+          y
+        (ack (1- x) y (ack x y (1- z)))))))
+
+(trace-function 'ack)
+
+(ack 3 3 1)
+@end group
+@end example
diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi
index a3d1d80408..64f31cdf3d 100644
--- a/doc/lispref/elisp.texi
+++ b/doc/lispref/elisp.texi
@@ -669,6 +669,7 @@ Top
 * Syntax Errors::           How to find syntax errors.
 * Test Coverage::           Ensuring you have tested all branches in your code.
 * Profiling::               Measuring the resources that your code uses.
+* Tracing::                 Log function calls, arguments, and return values.
 
 The Lisp Debugger
 
diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index e94093318f..ad2b175afb 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -2507,15 +2507,13 @@ %-Constructs
 @item %%
 The character @samp{%}---this is how to include a literal @samp{%} in a
 string in which @code{%}-constructs are allowed.
-@end table
-
-The following @code{%}-construct is still supported, but it is
-obsolete, since you can get the same result using the variable
-@code{mode-name}.
 
-@table @code
 @item %m
-The value of @code{mode-name}.
+Obsolete; use the @code{mode-name} variable instead.  The @code{%m}
+construct is still supported, but it is inadequate, as it produces an
+empty string if the value of the @code{mode-name} variable is a
+non-string mode-line construct (for example, in
+@code{emacs-lisp-mode}).
 @end table
 
 @node Properties in Mode
diff --git a/etc/NEWS b/etc/NEWS
index 57845df979..9728edc303 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1918,6 +1918,16 @@ The newly created buffer will be displayed via 
'display-buffer', which
 can be customized through the usual mechanism of 'display-buffer-alist'
 and friends.
 
+** Trace
+
++++
+*** New commands 'trace-package', 'trace-regexp', and 'trace-library'
+(and their counterparts 'untrace-package', 'untrace-regexp', and
+'untrace-library') allow for the bulk tracing of calls to functions
+with names matching a specified prefix or regexp, or functions defined
+by a specified file.  New command 'trace-currently-traced' lists the
+traced function symbols.
+
 ** Tramp
 
 ---
diff --git a/lisp/emacs-lisp/trace.el b/lisp/emacs-lisp/trace.el
index c2f6c16226..a4fcfd50b0 100644
--- a/lisp/emacs-lisp/trace.el
+++ b/lisp/emacs-lisp/trace.el
@@ -40,8 +40,6 @@
 
 ;; Restrictions:
 ;; =============
-;; - Traced subrs when called interactively will always show nil as the
-;;   value of their arguments.
 ;; - Only functions/macros/subrs that are called via their function cell will
 ;;   generate trace output, hence, you won't get trace output for:
 ;;   + Subrs called directly from other subrs/C-code
@@ -52,14 +50,28 @@
 
 ;; Usage:
 ;; ======
-;; - To trace a function say `M-x trace-function', which will ask you for the
+;; - To trace a function use `M-x trace-function', which will ask you for the
 ;;   name of the function/subr/macro to trace.
 ;; - If you want to trace a function that switches buffers or does other
 ;;   display oriented stuff use `M-x trace-function-background', which will
 ;;   generate the trace output silently in the background without popping
 ;;   up windows and doing other irritating stuff.
-;; - To untrace a function say `M-x untrace-function'.
-;; - To untrace all currently traced functions say `M-x untrace-all'.
+;; - `M-x trace-package' will ask you for a function name prefix, and trace
+;;   (in the background) all matching functions.
+;; - `M-x trace-regexp' will ask you for a function name pattern (regexp),
+;;   and trace (in the background) all matching functions.
+;; - `M-x trace-library' will ask you for a library name, and trace (in the
+;;   background) all functions defined by that file.
+;; - Interactively in all cases, a prefix argument can be used to prompt
+;;   for the output buffer and context arguments and, for bulk tracing
+;;   commands, whether or not the traces should be automatically updated
+;;   after loading lisp files.
+;; - To untrace a function use `M-x untrace-function'.
+;; - To untrace multiple functions by prefix use `M-x untrace-package'.
+;; - To untrace multiple functions by regexp use `M-x untrace-regexp'.
+;; - To untrace multiple functions by file use `M-x untrace-library'.
+;; - To untrace all currently traced functions use `M-x untrace-all'.
+;; - To list all currently traced functions use `M-x trace-currently-traced'.
 
 ;; Examples:
 ;; =========
@@ -120,6 +132,23 @@
 
 ;;; Change Log:
 
+;; 2017-06-17  Phil Sainty
+;;     * New commands `trace-package', `untrace-package', `trace-regexp',
+;;       `untrace-regexp', `trace-library', `untrace-library'.
+;;      * Documentation added to the elisp reference manual.
+;;
+;; 2012-2014  Stefan Monnier, Glenn Morris
+;;     * Adapted for nadvice.el
+;;     * New `context' argument and display in trace buffer
+;;     * `trace-function' renamed to (and now an alias of)
+;;       `trace-function-foreground'
+;;
+;; 2005-02-27  Stefan Monnier
+;;     * New `inhibit-trace' variable
+;;
+;; 1998-04-05  Stephen Eglen
+;;     * New customize group `trace'
+;;
 ;; Revision 2.0 1993/05/18 00:41:16 hans
 ;;     * Adapted for advice.el 2.0; it now also works
 ;;       for GNU Emacs-19 and Lemacs
@@ -134,6 +163,8 @@
 
 ;;; Code:
 
+(eval-when-compile (require 'cl-macs))
+
 (defgroup trace nil
   "Tracing facility for Emacs Lisp functions."
   :prefix "trace-"
@@ -181,7 +212,7 @@ trace-entry-message
             ;; FIXME: Make it so we can click the function name to jump to its
             ;; definition and/or untrace it.
             (cons function args)
-            context)))
+            (if context (format " [%s]" context) ""))))
 
 (defun trace-exit-message (function level value context)
   "Generate a string that describes that FUNCTION has exited.
@@ -197,7 +228,7 @@ trace-exit-message
             function
             ;; Do this so we'll see strings:
             value
-            context)))
+            (if context (format " [%s]" context) ""))))
 
 (defvar trace--timer nil)
 
@@ -218,8 +249,14 @@ trace-make-advice
 FUNCTION is the name of the traced function.
 BUFFER is the buffer where the trace should be printed.
 BACKGROUND if nil means to display BUFFER.
-CONTEXT if non-nil should be a function that returns extra info that should
-be printed along with the arguments in the trace."
+CONTEXT, if non-nil, should be either a function or an expression
+that returns extra info, which will be printed after the
+arguments or return value in the trace."
+  (setq context (if context
+                    (if (functionp context)
+                        context
+                      (trace-make-context context))
+                  (lambda () "")))
   (lambda (body &rest args)
     (let ((trace-level (1+ trace-level))
           (trace-buffer (get-buffer-create buffer))
@@ -227,6 +264,7 @@ trace-make-advice
           (ctx (funcall context)))
       (unless inhibit-trace
         (with-current-buffer trace-buffer
+          (setq-local page-delimiter (format "^%s" (regexp-quote 
trace-separator)))
           (setq-local window-point-insertion-type t)
           (unless background (trace--display-buffer trace-buffer))
           (goto-char (point-max))
@@ -255,41 +293,70 @@ trace-function-internal
   "Add trace advice for FUNCTION."
   (advice-add
    function :around
-   (trace-make-advice function (or buffer trace-buffer) background
-                      (or context (lambda () "")))
+   (trace-make-advice function (or buffer trace-buffer) background context)
    `((name . ,trace-advice-name) (depth . -100))))
 
-(defun trace-is-traced (function)
+(defun trace-is-traceable-p (sym)
+  "Whether the given symbol is a traceable function.
+Autoloaded functions are traceable."
+  (or (functionp sym) (macrop sym)))
+
+(defun trace-is-traced-p (function)
+  "Whether FUNCTION is currently traced."
   (advice-member-p trace-advice-name function))
 
-(defun trace--read-args (prompt)
-  "Read a function name, prompting with string PROMPT.
-If `current-prefix-arg' is non-nil, also read a buffer and a \"context\"
-\(Lisp expression).  Return (FUNCTION BUFFER FUNCTION-CONTEXT)."
-  (cons
-   (let ((default (function-called-at-point)))
-     (intern (completing-read (format-prompt prompt default)
-                              obarray 'fboundp t nil nil
-                              (if default (symbol-name default)))))
-   (when current-prefix-arg
-     (list
-      (read-buffer "Output to buffer" trace-buffer)
-      (let ((exp
-             (read-from-minibuffer "Context expression: "
-                                   nil read-expression-map t
-                                   'read-expression-history)))
-        (lambda ()
-          (let ((print-circle t)
-                (print-escape-newlines t))
-            (concat " [" (prin1-to-string (eval exp t)) "]"))))))))
+(define-obsolete-function-alias 'trace-is-traced 'trace-is-traced-p "29.1")
+
+(defun trace-currently-traced (&optional display-message)
+  "Return the list of currently traced function symbols.
+Interactively, display the list as a message."
+  (interactive "p")
+  (let ((tracelist (cl-loop for sym being the symbols
+                            if (trace-is-traced-p sym)
+                            collect sym)))
+    (when display-message
+      (message "%S" tracelist))
+    tracelist))
+
+(defun trace--read-function (prompt)
+  "Read a function name, prompting with string PROMPT."
+  (let ((default (function-called-at-point)))
+    (intern (completing-read (format-prompt prompt default)
+                             obarray 'trace-is-traceable-p t nil nil
+                             (if default (symbol-name default))))))
+
+(defun trace--read-library (&optional prompt)
+  "Read a library name, prompting with string PROMPT."
+  (completing-read
+   (or prompt "Library: ")
+   (apply-partially 'locate-file-completion-table
+                    load-path (get-load-suffixes))))
+
+(defun trace--read-extra-args ()
+  "Read a buffer and a \"context\" (Lisp expression).
+Return (BUFFER CONTEXT)."
+  (list
+   (read-buffer "Output to buffer" trace-buffer)
+   (when-let ((exp (read-from-minibuffer
+                    "Context expression: "
+                    nil read-expression-map t
+                    'read-expression-history "nil")))
+     (trace-make-context exp))))
+
+(defun trace-make-context (exp)
+  "Return a context function for expression EXP."
+  (lambda ()
+    (let ((print-circle t)
+          (print-escape-newlines t))
+      (prin1-to-string (eval exp t)))))
 
 ;;;###autoload
 (defun trace-function-foreground (function &optional buffer context)
   "Trace calls to function FUNCTION.
-With a prefix argument, also prompt for the trace buffer (default
-`trace-buffer'), and a Lisp expression CONTEXT.  When called from
-Lisp, CONTEXT should be a function of no arguments which returns
-a value to insert into BUFFER during the trace.
+With a prefix argument, also prompt for the trace output BUFFER
+\(default `trace-buffer'), and a Lisp expression CONTEXT.
+When called from Lisp, CONTEXT should be a function of no arguments
+which returns a value to insert into BUFFER during the trace.
 
 Tracing a function causes every call to that function to insert
 into BUFFER Lisp-style trace messages that display the function's
@@ -302,8 +369,14 @@ trace-function-foreground
 functions that switch buffers, or do any other display-oriented
 stuff - use `trace-function-background' instead.
 
+Calling `trace-function-foreground' again for the same FUNCTION
+will update the optional argument behaviours to respect the new
+values.
+
 To stop tracing a function, use `untrace-function' or `untrace-all'."
-  (interactive (trace--read-args "Trace function"))
+  (interactive
+   (cons (trace--read-function "Trace function")
+         (and current-prefix-arg (trace--read-extra-args))))
   (trace-function-internal function buffer nil context))
 
 ;;;###autoload
@@ -311,26 +384,290 @@ trace-function-background
   "Trace calls to function FUNCTION, quietly.
 This is like `trace-function-foreground', but without popping up
 the output buffer or changing the window configuration."
-  (interactive (trace--read-args "Trace function in background"))
+  (interactive
+   (cons (trace--read-function "Trace function in background")
+         (and current-prefix-arg (trace--read-extra-args))))
   (trace-function-internal function buffer t context))
 
 ;;;###autoload
 (defalias 'trace-function 'trace-function-foreground)
 
 (defun untrace-function (function)
-  "Untraces FUNCTION and possibly activates all remaining advice.
-Activation is performed with `ad-update', hence remaining advice will get
-activated only if the advice of FUNCTION is currently active.  If FUNCTION
-was not traced this is a noop."
+  "Remove trace from FUNCTION.  If FUNCTION was not traced this is a noop."
   (interactive
    (list (intern (completing-read "Untrace function: "
-                                  obarray #'trace-is-traced t))))
+                                  obarray #'trace-is-traced-p t))))
   (advice-remove function trace-advice-name))
 
+;;;###autoload
+(defun trace-package (prefix &optional buffer context after-load)
+  "Trace all functions with names starting with PREFIX.
+For example, to trace all diff functions, do the following:
+
+\\[trace-package] RET diff- RET
+
+Background tracing is used.  Switch to the trace output buffer to
+view the results.  For any autoload declarations matching PREFIX,
+the associated function will be traced if and when it is defined.
+
+With a prefix argument, also prompt for the optional arguments.
+If AFTER-LOAD is non-nil then re-process PREFIX after loading any
+file.  See `trace-function-foreground' for details of BUFFER and
+CONTEXT, and of foreground vs background tracing.
+
+Calling `trace-package' again for the same PREFIX will update the
+optional argument behaviours to respect the new values.
+
+See also `untrace-package'."
+  ;; Derived in part from `elp-instrument-package'.
+  (interactive
+   (cons (completing-read "Prefix of package to trace: "
+                          obarray #'trace-is-traceable-p)
+         (and current-prefix-arg
+              (nconc (trace--read-extra-args)
+                     (list (y-or-n-p "Update traces after loading files?"))))))
+  (when (zerop (length prefix))
+    (error "Tracing all Emacs functions would render Emacs unusable"))
+  (mapc (lambda (name)
+          (trace-function-background (intern name) buffer context))
+        (all-completions prefix obarray #'trace-is-traceable-p))
+  (message
+   "Tracing to %s.  Use %s to untrace a package, or %s to remove all traces."
+   (or buffer trace-buffer)
+   (substitute-command-keys "\\[untrace-package]")
+   (substitute-command-keys "\\[untrace-all]"))
+  ;; Handle `after-load' argument.
+  (when after-load
+    (trace--after-load 'prefix prefix buffer context)))
+
+(defun untrace-package (prefix)
+  "Remove all traces from functions with names starting with PREFIX.
+
+See also `trace-package'."
+  (interactive
+   (list (completing-read "Prefix of package to untrace: "
+                          obarray #'trace-is-traced-p)))
+  (if (and (zerop (length prefix))
+           (y-or-n-p "Remove all function traces?"))
+      (untrace-all)
+    (mapc (lambda (name)
+            (untrace-function (intern name)))
+          (all-completions prefix obarray #'trace-is-traced-p)))
+  ;; Remove any `after-load' behaviour.
+  (trace--remove-after-load 'prefix prefix))
+
+;;;###autoload
+(defun trace-regexp (regexp &optional buffer context after-load)
+  "Trace all functions with names matching REGEXP.
+For example, to trace indentation-related functions, you could try:
+
+\\[trace-regexp] RET indent\\|offset RET
+
+Warning: Do not attempt to trace all functions.  Tracing too many
+functions at one time will render Emacs unusable.
+
+Background tracing is used.  Switch to the trace output buffer to
+view the results.  For any autoload declarations matching REGEXP,
+the associated function will be traced if and when it is defined.
+
+With a prefix argument, also prompt for the optional arguments.
+If AFTER-LOAD is non-nil then re-process REGEXP after loading any
+file.  See `trace-function-foreground' for details of BUFFER and
+CONTEXT, and of foreground vs background tracing.
+
+Calling `trace-regexp' again for the same REGEXP will update the
+optional argument behaviours to respect the new values.
+
+See also `untrace-regexp'."
+  (interactive
+   (cons (read-regexp "Regexp matching functions to trace: ")
+         (and current-prefix-arg
+              (nconc (trace--read-extra-args)
+                     (list (y-or-n-p "Update traces after loading files?"))))))
+  (when (member regexp '("" "." ".+" ".*"))
+    ;; Not comprehensive, but it catches the most likely attempts.
+    (error "Tracing all Emacs functions would render Emacs unusable"))
+  (mapatoms
+   (lambda (sym)
+     (and (trace-is-traceable-p sym)
+          (string-match-p regexp (symbol-name sym))
+          (trace-function-background sym buffer context))))
+  (message
+   "Tracing to %s.  Use %s to untrace by regexp, or %s to remove all traces."
+   (or buffer trace-buffer)
+   (substitute-command-keys "\\[untrace-regexp]")
+   (substitute-command-keys "\\[untrace-all]"))
+  ;; Handle `after-load' argument.
+  (when after-load
+    (trace--after-load 'regexp regexp buffer context)))
+
+(defun untrace-regexp (regexp)
+  "Remove all traces from functions with names matching REGEXP.
+
+See also `trace-regexp'."
+  (interactive
+   (list (read-regexp "Regexp matching functions to untrace: ")))
+  (if (and (zerop (length regexp))
+           (y-or-n-p "Remove all function traces?"))
+      (untrace-all)
+    (mapatoms
+     (lambda (sym)
+       (and (trace-is-traced-p sym)
+            (string-match-p regexp (symbol-name sym))
+            (untrace-function sym)))))
+  ;; Remove any `after-load' behaviour.
+  (trace--remove-after-load 'regexp regexp))
+
+;;;###autoload
+(defun trace-library (library &optional buffer context after-load)
+  "Trace functions defined by LIBRARY.
+For example, to trace tramp.el functions, you could use:
+
+\\[trace-library] RET tramp RET
+
+Background tracing is used.  Switch to the trace output buffer to
+view the results.  For any autoload declarations with a file name
+matching LIBRARY, the associated function will be traced if and
+when it is defined.  (Autoload file names will not match if LIBRARY
+specifies a longer, more specific path.)
+
+With a prefix argument, also prompt for the optional arguments.
+If AFTER-LOAD is non-nil then re-process LIBRARY after loading it
+\(ensuring that all of its functions will be traced).  See
+`trace-function-foreground' for details of BUFFER and CONTEXT,
+and of foreground vs background tracing.
+
+Calling `trace-library' again for the same LIBRARY will update the
+optional argument behaviours to respect the new values.
+
+See also `untrace-library'."
+  (interactive
+   (cons (trace--read-library)
+         (and current-prefix-arg
+              (nconc (trace--read-extra-args)
+                     (list (y-or-n-p "Update traces after loading this 
library?"))))))
+  ;; Build list of library functions and autoloads.
+  (let ((defs (nconc (trace--library-defuns library)
+                     (trace--library-autoloads library))))
+    ;; Trace each of those definitions.
+    (mapc (lambda (func)
+            (trace-function-background func buffer context))
+          defs))
+  ;; Handle `after-load' argument.
+  (when after-load
+    (trace--after-load 'library library buffer context)))
+
+(defun trace--library-defuns (library)
+  "Returns a list of loaded function definitions associated with LIBRARY."
+  (delq nil (mapcar (lambda (x)
+                      (and (consp x)
+                           (eq (car x) 'defun)
+                           (cdr x)))
+                    (cdr (load-history-filename-element
+                          (load-history-regexp library))))))
+
+(defun trace--library-autoloads (library)
+  "Returns a list of all current autoloads associated with LIBRARY.
+
+Autoload file names will not match if LIBRARY specifies a longer,
+more specific path than that of the autoload declaration itself."
+  (let* ((functions nil)
+         (filepattern (load-history-regexp library))
+         (predicate (apply-partially 'trace--library-provides-autoload-p
+                                     filepattern)))
+    (mapatoms (lambda (sym)
+                (when (funcall predicate sym)
+                  (push sym functions))))
+    functions))
+
+(defun trace--library-provides-autoload-p (filepattern sym)
+  "Whether symbol SYM is an autoload associated with FILEPATTERN.
+
+FILEPATTERN should be the result of calling `load-history-regexp'."
+  (when (fboundp sym)
+    (let ((f (symbol-function sym)))
+      (and (autoloadp f)
+           (string-match filepattern (cadr f))))))
+
+(defun untrace-library (library)
+  "Remove all traces from functions defined by LIBRARY.
+
+See also `trace-library'."
+  (interactive (list (trace--read-library)))
+  ;; Remove traces from known LIBRARY defuns.
+  ;; (Also process autoloads, in case LIBRARY is unloaded.)
+  (let ((defs (nconc (trace--library-defuns library)
+                     (trace--library-autoloads library))))
+    (mapc (lambda (func)
+            (when (trace-is-traced-p func)
+              (untrace-function func)))
+          defs))
+  ;; Remove any `after-load' behaviour.
+  (trace--remove-after-load 'library library))
+
+(defvar trace--after-load-alist nil
+  "List of trace types to update after loading.
+
+Each list item has the form ((TYPE . VALUE) BUFFER CONTEXT),
+where TYPE is one of the symbols `prefix', `regexp', or `library';
+and VALUE is the respective first argument to `trace-package',
+`trace-regexp', or `trace-library'; with BUFFER and CONTEXT being
+the values of those arguments as they were passed to the same
+function.")
+
+(defun trace--after-load (type value &optional buffer context)
+  "Arrange to update traces after libraries are loaded.
+
+TYPE is one of the symbols `prefix', `regexp', or `library';
+VALUE is the respective first argument to `trace-package',
+`trace-regexp', or `trace-library'; and BUFFER and CONTEXT are
+the values of those arguments as they were passed to the same
+function.
+
+Adds `trace--after-load-function' to `after-load-functions'."
+  ;; Remove any existing spec for this (TYPE VALUE) key.
+  (trace--remove-after-load type value)
+  ;; Add the new spec.
+  (push (list (cons type value) buffer context)
+        trace--after-load-alist)
+  ;; Arrange to call `trace--after-load-function'.
+  (add-hook 'after-load-functions #'trace--after-load-function))
+
+(defun trace--after-load-function (file)
+  "React to FILE being loaded.  Callback for `after-load-functions'.
+
+See also `trace--after-load'."
+  (dolist (spec trace--after-load-alist)
+    (cl-destructuring-bind ((type . value) buffer context)
+        spec
+      (cl-case type
+        (prefix (trace-package value nil buffer context))
+        (regexp (trace-regexp value nil buffer context))
+        (library (when (string-match (load-history-regexp value) file)
+                   (trace-library value nil buffer context)))))))
+
+(defun trace--remove-after-load (type value)
+  "Remove any (TYPE . VALUE) entry from `trace--after-load-alist'.
+
+Remove `trace--after-load-function' from `after-load-functions'
+if it is no longer needed."
+  (setq trace--after-load-alist
+        (cl-delete (cons type value) trace--after-load-alist
+                   :key #'car :test #'equal))
+  (unless trace--after-load-alist
+    (remove-hook 'after-load-functions #'trace--after-load-function)))
+
+(defun trace--remove-after-load-all ()
+  "Reset `trace--after-load-alist'.
+Remove `trace--after-load-function' from `after-load-functions'"
+  (setq trace--after-load-alist nil)
+  (remove-hook 'after-load-functions #'trace--after-load-function))
+
 (defun untrace-all ()
-  "Untraces all currently traced functions."
+  "Remove traces from all currently traced functions."
   (interactive)
-  (mapatoms #'untrace-function))
+  (mapatoms #'untrace-function)
+  (trace--remove-after-load-all))
 
 (provide 'trace)
 





reply via email to

[Prev in Thread] Current Thread [Next in Thread]