04 Mar 2023 -
I’m using a variation of cl-progress-bar library for displaying progress of long-running tasks. Although instead of printing a progress bar, a text with completion percentage, speed, elapsed and estimated remaining time is printed. I think it is very useful for long-running tasks. I’m using it for some database imports at the moment.
Output looks like this:
Mapping table: REL_ORGANIZATION_PERSON_EMPLOYEES_IS_EMPLOYED_BY
Progress: 2% (100 of 4369) at 163.39763/sec. Elapsed: 0 seconds. Remaining: 26 seconds.
Progress: 6% (300 of 4369) at 345.6205/sec. Elapsed: 0 seconds. Remaining: 11 seconds.
Progress: 11% (500 of 4369) at 413.90558/sec. Elapsed: 1 seconds. Remaining: 9 seconds.
Progress: 18% (800 of 4369) at 505.04794/sec. Elapsed: 1 seconds. Remaining: 7 seconds.
Progress: 22% (1000 of 4369) at 502.00577/sec. Elapsed: 1 seconds. Remaining: 6 seconds.
Progress: 25% (1100 of 4369) at 486.7235/sec. Elapsed: 2 seconds. Remaining: 6 seconds.
Implementation:
(defpackage estimated-time-progress
(:local-nicknames
(:pb :cl-progress-bar.progress))
(:use :cl)
(:export :with-estimated-time-progress))
(in-package :estimated-time-progress)
(defconstant +seconds-in-one-hour+ 3600)
(defconstant +seconds-in-one-minute+ 60)
(defun format-time-in-seconds-minutes-hours (stream in-seconds)
(when (>= in-seconds +seconds-in-one-hour+)
(let* ((hours (floor (/ in-seconds +seconds-in-one-hour+))))
(decf in-seconds (* hours +seconds-in-one-hour+))
(format stream " ~a hour~p " hours hours)))
(when (>= in-seconds +seconds-in-one-minute+)
(let* ((minutes (floor (/ in-seconds +seconds-in-one-minute+))))
(decf in-seconds (* minutes +seconds-in-one-minute+))
(format stream "~a minute~p " minutes minutes)))
(unless (zerop in-seconds)
(format stream "~d seconds" (truncate in-seconds))))
;; (format-time-in-seconds-minutes-hours t 160)
(defclass estimated-time-progress (pb::progress-bar)
()
(:documentation "Displays progress estimated time for completion."))
(defmethod pb::update-display ((progress-bar estimated-time-progress))
(incf (pb::progress progress-bar) (pb::pending progress-bar))
(setf (pb::pending progress-bar) 0)
(setf (pb::last-update-time progress-bar) (get-internal-real-time))
(unless (zerop (pb::progress progress-bar))
(let ((current-time (- (pb::last-update-time progress-bar)
(pb::start-time progress-bar))))
(format t "Progress: ~d% (~a of ~a) at ~f/sec. "
(truncate
(* (/ (pb::progress progress-bar)
(pb::total progress-bar)) 100))
(pb::progress progress-bar)
(pb::total progress-bar)
(* (/ (pb::progress progress-bar)
current-time)
1000000))
(write-string "Elapsed: ")
(format-time-in-seconds-minutes-hours t (/ current-time 1000000))
(write-string ". ")
(let ((estimated-seconds (/
(-
(/ (* (pb::total progress-bar)
current-time)
(pb::progress progress-bar))
current-time)
1000000)))
(write-string "Remaining: ")
(format-time-in-seconds-minutes-hours t estimated-seconds)
(write-string "."))
(terpri)
(finish-output))))
(defmacro with-estimated-time-progress ((steps-count description &rest desc-args) &body body)
(let ((!old-bar (gensym)))
`(let* ((,!old-bar cl-progress-bar::*progress-bar*)
(cl-progress-bar::*progress-bar* (or ,!old-bar
(when cl-progress-bar::*progress-bar-enabled*
(make-instance 'estimated-time-progress :total ,steps-count)))))
(unless (eq ,!old-bar cl-progress-bar::*progress-bar*)
(fresh-line)
(format t ,description ,@desc-args)
(cl-progress-bar.progress:start-display cl-progress-bar::*progress-bar*))
(prog1 (progn ,@body)
(unless (eq ,!old-bar cl-progress-bar::*progress-bar*)
(cl-progress-bar.progress:finish-display cl-progress-bar::*progress-bar*))))))
Use:
(defun perform-step () ; Calls to the update can occur anywhere.
(sleep 1.7)
(cl-progress-bar:update 1))
(with-estimated-time-progress (5 "This is just a example. Number of steps is ~a." 5)
(dotimes (i 5) (perform-step)))