;;; -*- coding: utf-8; lexical-binding: t -*- ;;; ;;; sly-profiler.el -- a navigable dialog of inspectable timing entries ;;; (eval-and-compile (require 'sly) (require 'sly-parse "lib/sly-parse")) (define-sly-contrib sly-profiler "Provide an interfactive timing dialog buffer for managing and inspecting details of timing functions. Invoke this dialog with C-c Y." (:authors "João Távora ") (:license "GPL") (:slynk-dependencies slynk/profiler) (:on-load (add-hook 'sly-mode-hook 'sly-profiler-enable)) (:on-unload (remove-hook 'sly-mode-hook 'sly-profiler-enable))) ;;;; Modes and mode maps ;;; (defvar sly-profiler-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "G") 'sly-profiler-fetch-timings) (define-key map (kbd "C-k") 'sly-profiler-clear-fetched-timings) (define-key map (kbd "g") 'sly-profiler-fetch-status) (define-key map (kbd "q") 'quit-window) map)) (define-derived-mode sly-profiler-mode fundamental-mode "SLY Timing Dialog" "Mode for controlling SLY's Timing Dialog" (set-syntax-table lisp-mode-syntax-table) (read-only-mode 1)) (defvar sly-profiler-shortcut-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-c Y") 'sly-profiler) (define-key map (kbd "C-c C-y") 'sly-profiler-toggle-timing) map)) (define-minor-mode sly-profiler-shortcut-mode "Add keybindings for accessing SLY's Profiler.") (defun sly-profiler-enable () (sly-profiler-shortcut-mode 1)) ;;;; Helpers ;;; (defun sly-profiler--get-buffer () (let* ((name (format "*profiler for %s*" (sly-connection-name sly-default-connection))) (existing (get-buffer name))) (cond ((and existing (buffer-live-p existing) (with-current-buffer existing (memq sly-buffer-connection sly-net-processes))) existing) (t (if existing (kill-buffer existing)) (with-current-buffer (get-buffer-create name) (sly-profiler-mode) (setq sly-buffer-connection sly-default-connection) (pop-to-buffer (current-buffer))))))) (defun sly-profiler--clear-local-tree () (erase-buffer) (insert "Cleared timings!")) (defun sly-profiler--render-timings (timing-specs) (let ((inhibit-read-only t)) (erase-buffer) (let ((standard-output (current-buffer))) (cl-loop for spec in timing-specs do (princ spec) (terpri))))) ;;;; Interactive functions ;;; ;; (defun sly-profiler-fetch-specs () ;; "Refresh just list of timing specs." ;; (interactive) ;; (sly-eval-async `(slynk-profiler:report-specs) ;; #'sly-profiler--open-specs)) (defun sly-profiler-clear-fetched-timings (&optional interactive) "Clear local and remote timings collected so far" (interactive "p") (when (or (not interactive) (y-or-n-p "Clear all collected and fetched timings?")) (sly-eval-async '(slynk-profiler:clear-timing-tree) #'sly-profiler--clear-local-tree))) (defun sly-profiler-fetch-timings () (interactive) (sly-eval-async `(slynk-profiler:report-latest-timings) #'sly-profiler--render-timings)) (defun sly-profiler-fetch-status () (interactive) (sly-profiler-fetch-timings)) (defun sly-profiler-toggle-timing (&optional using-context-p) "Toggle the dialog-timing of the spec at point. When USING-CONTEXT-P, attempt to decipher lambdas. methods and other complicated function specs." (interactive "P") ;; Notice the use of "spec strings" here as opposed to the ;; proper cons specs we use on the slynk side. ;; ;; Notice the conditional use of `sly-trace-query' found in ;; slynk-fancy-trace.el ;; (let* ((spec-string (if using-context-p (sly-extract-context) (sly-symbol-at-point))) (spec-string (read-from-minibuffer "(Un)time: " (format "%s" spec-string)))) (message "%s" (sly-eval `(slynk-profiler:toggle-timing (slynk::from-string ,spec-string)))))) (defun sly-profiler (&optional refresh) "Show timing dialog and refresh timing collection status. With optional CLEAR-AND-FETCH prefix arg, clear the current tree and fetch a first batch of timings." (interactive "P") (sly-with-popup-buffer ((sly-buffer-name :profiler :connection sly-default-connection) :mode 'sly-profiler-mode :select t) (when refresh (sly-profiler-fetch-timings)))) ;;;; Menu ;;; (easy-menu-define sly-profiler--shortcut-menu nil "Menu setting traces from anywhere in SLY." (let* ((in-dialog '(eq major-mode 'sly-profiler-mode)) (_dialog-live `(and ,in-dialog (memq sly-buffer-connection sly-net-processes))) (connected '(sly-connected-p))) `("Profiling" ["(Un)Profile definition" sly-profiler-toggle-timing ,connected] ["Open Profiler Dialog" sly-profiler (and ,connected (not ,in-dialog))]))) (easy-menu-add-item sly-menu nil sly-profiler--shortcut-menu "Documentation") (defvar sly-profiler--easy-menu (let ((condition '(memq sly-buffer-connection sly-net-processes))) `("Timing" [ "Clear fetched timings" sly-profiler-clear-fetched-timings ,condition] [ "Fetch timings" sly-profiler-fetch-timings ,condition]))) (easy-menu-define my-menu sly-profiler-mode-map "Timing" sly-profiler--easy-menu) (provide 'sly-profiler)