Subject: bug#27397: [PATCH] New commands for bulk tracing of elisp functions

From: Phil Sainty <[email-masked]>
Date: Sat, 17 Jun 2017 01:32:47 +1200

trace.el currently only has commands for adding a trace to a single
function, and for untracing either a single function or ALL traced
functions.

This patch adds commands for tracing and untracing functions in bulk,
either by function name prefix (`trace-package' and `untrace-package')
or by regexp (`trace-regexp' and `untrace-regexp').

The use of the term "package" for the prefix-based commands was chosen
for consistency with `elp-instrument-package', from which this code is
partly derived, and from which I have also maintained a warning and
basic protection against attempts to trace too many functions at once
(which can indeed render Emacs unusable).  It it still possible to do
this, but I feel the checks which are currently in place are probably
sufficient to protect against accidents.

`trace-is-traceable-p' is using Stefan's suggestion in his comment on
my initial implementation at https://stackoverflow.com/a/44241058 and
I have changed the completing-read filter predicate in
`trace--read-args' to also use this (instead of `fboundp'), on the
assumption that this makes more sense.

(I note that the `trace-is-traced' function does not follow the usual
naming convention for predicates.  Should this be renamed to
`trace-is-traced-p' ?)

This patch also removes some outdated docstring comments for
`untrace-function' which were clearly remnants of the earlier
implementation using advice.el (whereas the current library is based
on nadvice.el).

-Phil
>From c7262056db33722bc84dd94fbadaa71ed8fbf5fc Mon Sep 17 00:00:00 2001
From: Phil Sainty <[email-masked]>
Date: Sun, 11 Jun 2017 17:29:53 +1200
Subject: [PATCH] New commands for bulk tracing of elisp functions

* lisp/emacs-lisp/trace.el (trace-package, untrace-package)
(trace-regexp, untrace-regexp, trace-is-traceable-p): New functions.
(trace--read-args): Use new trace-is-traceable-p predicate.
(trace-is-traced, untrace-function, untrace-all): Doc updates/fixes.
---
 lisp/emacs-lisp/trace.el | 98 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 92 insertions(+), 6 deletions(-)

diff --git a/lisp/emacs-lisp/trace.el b/lisp/emacs-lisp/trace.el
index 1c57d73..5dba151 100644
--- a/lisp/emacs-lisp/trace.el
+++ b/lisp/emacs-lisp/trace.el
@@ -257,7 +257,12 @@ trace-function-internal
                       (or context (lambda () "")))
    `((name . ,trace-advice-name) (depth . -100))))
 
+(defun trace-is-traceable-p (sym)
+  "Whether the given symbol is a traceable function."
+  (or (functionp sym) (macrop sym)))
+
 (defun trace-is-traced (function)
+  "Whether FUNCTION is currently traced."
   (advice-member-p trace-advice-name function))
 
 (defun trace--read-args (prompt)
@@ -274,7 +279,7 @@ trace--read-args
                                    default
                                    (if beg (substring prompt beg) ": "))
                                 prompt)
-                              obarray 'fboundp t nil nil
+                              obarray 'trace-is-traceable-p t nil nil
                               (if default (symbol-name default)))))
    (when current-prefix-arg
      (list
@@ -321,17 +326,98 @@ trace-function-background
 (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))))
   (advice-remove function trace-advice-name))
 
+;;;###autoload
+(defun trace-package (prefix)
+  "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.
+
+See also `untrace-package'."
+  (interactive ;; derived from `elp-instrument-package'.
+   (list (completing-read "Prefix of package to trace: "
+                          obarray #'trace-is-traceable-p)))
+  (when (zerop (length prefix))
+    (error "Tracing all Emacs functions would render Emacs unusable"))
+  (mapc (lambda (name)
+          (trace-function-background (intern name)))
+        (all-completions prefix obarray #'trace-is-traceable-p))
+  (message
+   "Tracing to %s.  Use %s to untrace a package, or %s to remove all traces."
+   trace-buffer
+   (substitute-command-keys "\\[untrace-package]")
+   (substitute-command-keys "\\[untrace-all]")))
+
+(defun untrace-package (prefix)
+  "Remove traces from all functions with names starting with PREFIX.
+
+See also `trace-package'."
+  (interactive
+   (list (completing-read "Prefix of package to untrace: "
+                          obarray #'trace-is-traced)))
+  (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-traceable-p))))
+
+;;;###autoload
+(defun trace-regexp (regexp)
+  "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.
+
+See also `untrace-regexp'."
+  (interactive
+   (list (read-regexp "Regexp matching functions to trace: ")))
+  (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))))
+  (message
+   "Tracing to %s.  Use %s to untrace by regexp, or %s to remove all traces."
+   trace-buffer
+   (substitute-command-keys "\\[untrace-regexp]")
+   (substitute-command-keys "\\[untrace-all]")))
+
+(defun untrace-regexp (regexp)
+  "Remove traces from all 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-traceable-p sym)
+            (string-match-p regexp (symbol-name sym))
+            (untrace-function sym))))))
+
 (defun untrace-all ()
-  "Untraces all currently traced functions."
+  "Remove traces from all currently traced functions."
   (interactive)
   (mapatoms #'untrace-function))
 
-- 
2.8.3