Skip to content

Commit 8a9f62f

Browse files
committed
Add jupyter-org-display-execution-time functionality
* jupyter-base.el (jupyter-format-time) (jupyter-execution-time): New functions. * jupyter-org-client.el (jupyter-org-display-execution-time): New custom variable. (jupyter-generate-request): Clear any previous execution time strings before generating the request for the source block. (jupyter-org--display-execution-time) (jupyter-org-clear-execution-time): New functions. (jupyter-handle-execute-reply): Display execution time on the source block associated with a request.
1 parent 5c013a1 commit 8a9f62f

File tree

2 files changed

+127
-0
lines changed

2 files changed

+127
-0
lines changed

jupyter-base.el

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,32 @@ call the handler methods of those types."
497497
("interrupt_request" "control")
498498
(_ "shell")))
499499

500+
(declare-function jupyter-message-time "jupyter-messages")
501+
(declare-function jupyter-find-message "jupyter-monads")
502+
503+
(defun jupyter-execution-time (req)
504+
"Return an approximate value of the execution time of REQ.
505+
Return a time value in the same form as what is returned by
506+
`current-time' or nil when a value could not be computed,
507+
e.g. when REQ does not represent an execute_request or has not
508+
completed yet.
509+
510+
Note this just returns the time difference between the
511+
execute_input message of the request and the execute_reply
512+
message. The execute_input message is received when the kernel
513+
begins executing the contents of REQ and the execute_reply is
514+
received when the kernel has completed execution of REQ minus how
515+
long it actually takes for the request to generate all of its
516+
potential output. So the returned time, in addition to the true
517+
execution time of the code, contains the time taken in preparing
518+
the request to be executed and other kernel specific tasks."
519+
(when-let* ((msgs (jupyter-request-messages req))
520+
(ex-input (jupyter-find-message "execute_input" msgs))
521+
(ex-reply (jupyter-find-message "execute_reply" msgs)))
522+
(time-subtract
523+
(jupyter-message-time ex-reply)
524+
(jupyter-message-time ex-input))))
525+
500526
;;; Connecting to a kernel's channels
501527

502528
(eval-when-compile (require 'tramp))
@@ -768,6 +794,48 @@ TIME is assumed to have the same form as the return value of
768794
minutes
769795
(if past " ago" ""))))))))
770796

797+
(defun jupyter-format-time (time)
798+
"Return a description string describing TIME.
799+
TIME is a time value as returned by `current-time'. Return
800+
strings like
801+
802+
\"1 day 5 min 10.330 s\", \"10 min 5.000 s\", \"30.200 s\"
803+
804+
depending on the number of seconds contained in TIME. If TIME is
805+
nil, return \"Never\"."
806+
(if (null time) "Never"
807+
(let* ((result "")
808+
(s (float-time time))
809+
(d (floor (/ s 86400.0))))
810+
(unless (zerop d)
811+
(cl-callf concat result
812+
(format "%d day%s" d (if (= d 1) "" "s"))))
813+
(let ((h (floor (- (/ s 3600.0) (* 24 d)))))
814+
(unless (zerop h)
815+
(cl-callf concat result
816+
(format "%s%d hour%s"
817+
(if (zerop (length result)) "" " ")
818+
h
819+
(if (= h 1) "" "s"))))
820+
(let ((m (floor (- (/ s 60.0)
821+
(+ (* 60 h)
822+
(* 1440 d))))))
823+
(unless (zerop m)
824+
(cl-callf concat result
825+
(format "%s%d min%s"
826+
(if (zerop (length result)) "" " ")
827+
m
828+
(if (= m 1) "" "s"))))
829+
(setq s (- s (+ (* 60 m)
830+
(* 3600 h)
831+
(* 86400 d))))
832+
(unless (zerop s)
833+
(cl-callf concat result
834+
(format "%s%.3f s"
835+
(if (zerop (length result)) "" " ")
836+
s)))))
837+
result)))
838+
771839
;;; Simple weak references
772840
;; Thanks to Chris Wellon https://nullprogram.com/blog/2014/01/27/
773841

jupyter-org-client.el

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,20 @@ after the failed one are not executed."
9191
:group 'ob-jupyter
9292
:type 'boolean)
9393

94+
(defcustom jupyter-org-display-execution-time nil
95+
"Whether or not to display the execution time of a source block.
96+
If this variable is nil, the execution times are not displayed.
97+
When it is t, display the execution times regardless of how long
98+
it took to execute. When it is a number, display the execution
99+
time when it is longer than that many seconds.
100+
101+
To clear the execution time information from the source block
102+
simply edit it or call `jupyter-org-clear-execution-time'."
103+
:group 'ob-jupyter
104+
:type '(choice (const :tag "Never display" nil)
105+
(const :tag "Always display" t)
106+
(number :tag "Display when above this threshold (in seconds)")))
107+
94108
(defcustom jupyter-org-resource-directory "./.ob-jupyter/"
95109
"Directory used to store automatically generated image files.
96110
See `jupyter-org-image-file-name'."
@@ -216,6 +230,7 @@ nothing and return nil."
216230
;; started due to sending a completion request.
217231
(save-excursion
218232
(goto-char org-babel-current-src-block-location)
233+
(jupyter-org-clear-execution-time)
219234
(let* ((context (org-element-context))
220235
(block-params org-babel-jupyter-current-src-block-params)
221236
(result-params (alist-get :result-params block-params))
@@ -422,7 +437,51 @@ to."
422437
(forward-line)
423438
(insert (org-element-normalize-string (plist-get pl :text))))))
424439

440+
(defun jupyter-org--display-execution-time (req)
441+
"In the Org buffer of REQ, show the REQ's execution time."
442+
(pcase jupyter-org-display-execution-time
443+
((and (or (and `t
444+
(let time (jupyter-execution-time req)))
445+
(and (pred numberp) secs
446+
(let time (jupyter-execution-time req))
447+
(guard (> time secs))))
448+
(let (cl-struct jupyter-org-request inline-block-p) req)
449+
(guard (not inline-block-p)))
450+
(jupyter-org-with-point-at req
451+
(let* ((src-block (org-element-at-point))
452+
(ov (make-overlay
453+
(org-element-property :begin src-block)
454+
;; Exclude the newline to make it look like
455+
;;
456+
;; #+end_src Execution time ...
457+
(1- (jupyter-org-element-end-before-blanks src-block)))))
458+
(let ((delete
459+
(list (lambda (&rest _)
460+
(delete-overlay ov)))))
461+
(overlay-put ov 'evaporate t)
462+
(overlay-put ov 'jupyter-execution-time time)
463+
(overlay-put ov 'modification-hooks delete)
464+
(overlay-put ov 'insert-in-front-hooks delete)
465+
(overlay-put ov 'insert-behind-hooks delete))
466+
(overlay-put
467+
ov 'after-string
468+
(concat " " (propertize
469+
(format "Execution time: %s"
470+
(jupyter-format-time time))
471+
'face 'bold-italic))))))))
472+
473+
(defun jupyter-org-clear-execution-time ()
474+
"Clear the execution time overlay for the source block at point."
475+
(interactive)
476+
(let ((el (org-element-at-point)))
477+
(pcase (org-element-type el)
478+
((or `src-block `babel-call)
479+
(dolist (ov (overlays-at (org-element-property :begin el)))
480+
(when (overlay-get ov 'jupyter-execution-time)
481+
(delete-overlay ov)))))))
482+
425483
(cl-defmethod jupyter-handle-execute-reply ((_client jupyter-org-client) (req jupyter-org-request) msg)
484+
(jupyter-org--display-execution-time req)
426485
(jupyter-with-message-content msg (status payload)
427486
(when payload
428487
(jupyter-org-with-point-at req

0 commit comments

Comments
 (0)