;;; -*- 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 <joaotavora@gmail.com>")
(: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)