;;; cider-profile.el --- CIDER support for profiling -*- lexical-binding: t; -*- ;; Copyright © 2014-2019 Edwin Watkeys and CIDER contributors ;; Author: Edwin Watkeys ;; Juan E. Maya ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Provides coarse-grained interactive profiling support. ;; Based on earlier work by Edwin Watkeys (https://github.com/thunknyc/nrepl-profile). ;;; Code: (require 'cider-client) (require 'cider-popup) (require 'cider-eval) (defconst cider-profile-buffer "*cider-profile*") (defvar cider-profile-map (let ((map (define-prefix-command 'cider-profile-map))) (define-key map (kbd "t") #'cider-profile-toggle) (define-key map (kbd "c") #'cider-profile-clear) (define-key map (kbd "S") #'cider-profile-summary) (define-key map (kbd "s") #'cider-profile-var-summary) (define-key map (kbd "n") #'cider-profile-ns-toggle) (define-key map (kbd "v") #'cider-profile-var-profiled-p) (define-key map (kbd "+") #'cider-profile-samples) map) "CIDER profiler keymap.") (defconst cider-profile-menu '("Profile" ["Toggle var profiling" cider-profile-toggle] ["Toggle namespace profiling" cider-profile-ns-toggle] "--" ["Display var profiling status" cider-profile-var-profiled-p] ["Display max sample count" cider-profile-samples] ["Display summary" cider-profile-summary] ["Clear data" cider-profile-clear]) "CIDER profiling submenu.") (defun cider-profile--make-response-handler (handler &optional buffer) "Make a response handler using value handler HANDLER for connection BUFFER. Optional argument BUFFER defaults to current buffer." (nrepl-make-response-handler (or buffer (current-buffer)) handler nil nil nil)) ;;;###autoload (defun cider-profile-samples (&optional query) "Displays current max-sample-count. If optional QUERY is specified, set max-sample-count and display new value." (interactive "P") (cider-ensure-op-supported "set-max-samples") (cider-ensure-op-supported "get-max-samples") (if (not (null query)) (cider-nrepl-send-request (let ((max-samples (if (numberp query) query '()))) (message "query: %s" max-samples) `("op" "set-max-samples" "max-samples" ,max-samples)) (cider-profile--make-response-handler (lambda (_buffer value) (let ((value (if (zerop (length value)) "unlimited" value))) (message "max-sample-count is now %s" value))))) (cider-nrepl-send-request '("op" "get-max-samples") (cider-profile--make-response-handler (lambda (_buffer value) (let ((value (if (zerop (length value)) "unlimited" value))) (message "max-sample-count is now %s" value)))))) query) ;;;###autoload (defun cider-profile-var-profiled-p (query) "Displays the profiling status of var under point. Prompts for var if none under point or QUERY is present." (interactive "P") (cider-ensure-op-supported "is-var-profiled") (cider-read-symbol-name "Report profiling status for var: " (lambda (sym) (let ((ns (cider-current-ns))) (cider-nrepl-send-request `("op" "is-var-profiled" "ns" ,ns "sym" ,sym) (cider-profile--make-response-handler (lambda (_buffer value) (pcase value ("profiled" (message "Profiling is currently enabled for %s/%s" ns sym)) ("unprofiled" (message "Profiling is currently disabled for %s/%s" ns sym)) ("unbound" (message "%s/%s is unbound" ns sym))))))))) query) ;;;###autoload (defun cider-profile-ns-toggle (&optional query) "Toggle profiling for the ns associated with optional QUERY. If optional argument QUERY is non-nil, prompt for ns. Otherwise use current ns." (interactive "P") (cider-ensure-op-supported "toggle-profile-ns") (let ((ns (if query (completing-read "Toggle profiling for ns: " (cider-sync-request:ns-list)) (cider-current-ns)))) (cider-nrepl-send-request `("op" "toggle-profile-ns" "ns" ,ns) (cider-profile--make-response-handler (lambda (_buffer value) (pcase value ("profiled" (message "Profiling enabled for %s" ns)) ("unprofiled" (message "Profiling disabled for %s" ns))))))) query) ;;;###autoload (defun cider-profile-toggle (query) "Toggle profiling for the given QUERY. Defaults to the symbol at point. With prefix arg or no symbol at point, prompts for a var." (interactive "P") (cider-ensure-op-supported "toggle-profile") (cider-read-symbol-name "Toggle profiling for var: " (lambda (sym) (let ((ns (cider-current-ns))) (cider-nrepl-send-request `("op" "toggle-profile" "ns" ,ns "sym" ,sym) (cider-profile--make-response-handler (lambda (_buffer value) (pcase value ("profiled" (message "Profiling enabled for %s/%s" ns sym)) ("unprofiled" (message "Profiling disabled for %s/%s" ns sym)) ("unbound" (message "%s/%s is unbound" ns sym))))))))) query) (defun cider-profile-display-stats (stats-response) "Displays the STATS-RESPONSE on `cider-profile-buffer`." (let ((table (nrepl-dict-get stats-response "err"))) (if cider-profile-buffer (let ((buffer (cider-make-popup-buffer cider-profile-buffer))) (with-current-buffer buffer (let ((inhibit-read-only t)) (insert table))) (display-buffer buffer) (let ((window (get-buffer-window buffer))) (set-window-point window 0) (select-window window) (fit-window-to-buffer window))) (cider-emit-interactive-eval-err-output table)))) ;;;###autoload (defun cider-profile-summary () "Display a summary of currently collected profile data." (interactive) (cider-ensure-op-supported "profile-summary") (cider-profile-display-stats (cider-nrepl-send-sync-request '("op" "profile-summary")))) ;;;###autoload (defun cider-profile-var-summary (query) "Display profile data for var under point QUERY. Defaults to the symbol at point. With prefix arg or no symbol at point, prompts for a var." (interactive "P") (cider-ensure-op-supported "profile-var-summary") (cider-read-symbol-name "Profile-summary for var: " (lambda (sym) (cider-profile-display-stats (cider-nrepl-send-sync-request `("op" "profile-var-summary" "ns" ,(cider-current-ns) "sym" ,sym))))) query) ;;;###autoload (defun cider-profile-clear () "Clear any collected profile data." (interactive) (cider-ensure-op-supported "clear-profile") (cider-nrepl-send-request '("op" "clear-profile") (cider-profile--make-response-handler (lambda (_buffer value) (when (equal value "cleared") (message "Cleared profile data")))))) (provide 'cider-profile) ;;; cider-profile.el ends here