Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/project_docs/startup-profiling-option/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# startup-profiling-option Plan

## Goal

`ceeker` の起動時に、明示オプション指定時だけ
セットアップ区間の計測ログを stderr へ出せるようにする。

## Scope

- CLI に `--startup-profile` を追加
- `start-tui!` の初期化処理を計測
- `create-terminal`
- `create-watcher`
- `start-pane-checker`
- 合計時間
- 通常起動時の挙動は変えない

## Tests

- `test/ceeker/core_test.clj`
- 新オプションが parse できること
- `test/ceeker/tui/app_test.clj`
- オプション有効時だけ計測ログが出ること
14 changes: 11 additions & 3 deletions src/ceeker/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
:parse-fn keyword
:validate [#{:auto :table :card}
"Must be one of: auto, table, card"]]
[nil "--exit-on-jump" "Exit after a successful jump"]])
[nil "--exit-on-jump" "Exit after a successful jump"]
[nil "--startup-profile"
"Log startup profiling to stderr"]])

(defn- usage
"Returns usage string."
Expand Down Expand Up @@ -98,6 +100,13 @@
(doseq [e errors] (println e)))
(System/exit 1))

(defn- tui-opts
"Builds TUI opts from parsed CLI options."
[options]
{:exit-on-jump (:exit-on-jump options)
:startup-profile (:startup-profile options)
:initial-display-mode (:view options)})

;; musl? is evaluated at AOT compile time (macro expansion time).
;; For musl static builds, set CEEKER_STATIC=true CEEKER_MUSL=true
;; when running `clojure -T:build uber` to produce a musl-compatible binary.
Expand Down Expand Up @@ -137,6 +146,5 @@
:else
(do (tui/start-tui!
nil
{:exit-on-jump (:exit-on-jump options)
:initial-display-mode (:view options)})
(tui-opts options))
(System/exit 0))))))
63 changes: 57 additions & 6 deletions src/ceeker/tui/app.clj
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,56 @@
(let [w (.getWidth terminal)]
(if (pos? w) w 120)))

(defn- elapsed-ms
"Returns elapsed milliseconds since started-at."
[started-at]
(/ (- (System/nanoTime) started-at) 1000000.0))

(defn- timed-step
"Runs f and returns its result plus elapsed time."
[f]
(let [started-at (System/nanoTime)
result (f)]
{:result result
:elapsed-ms (elapsed-ms started-at)}))

(defn- log-startup-profile!
"Logs startup profiling summary to stderr."
[{:keys [create-terminal create-watcher
start-pane-checker total]}]
(binding [*out* *err*]
(println
(str "ceeker: startup-profile "
"create-terminal="
(format "%.2fms" create-terminal)
" create-watcher="
(format "%.2fms" create-watcher)
" start-pane-checker="
(format "%.2fms" start-pane-checker)
" total="
(format "%.2fms" total)))))

(declare create-watcher-for)

(defn- setup-runtime
"Creates startup resources and optionally logs timings."
[state-dir startup-profile?]
(let [started-at (System/nanoTime)
terminal-step (timed-step input/create-terminal)
watcher-step (timed-step
#(create-watcher-for state-dir))
checker-step (timed-step
#(start-pane-checker! state-dir))]
(when startup-profile?
(log-startup-profile!
{:create-terminal (:elapsed-ms terminal-step)
:create-watcher (:elapsed-ms watcher-step)
:start-pane-checker (:elapsed-ms checker-step)
:total (elapsed-ms started-at)}))
{:terminal (:result terminal-step)
:watcher (:result watcher-step)
:stop-ch (:result checker-step)}))

(defn- get-terminal-height
"Gets the current terminal height from a JLine terminal."
[^org.jline.terminal.Terminal terminal]
Expand Down Expand Up @@ -321,23 +371,24 @@
(defn start-tui!
"Runs the TUI application loop.
opts may include :exit-on-jump to quit after a
successful jump and :initial-display-mode to
successful jump, :startup-profile to log
startup timings, and :initial-display-mode to
set the startup view."
([] (start-tui! nil))
([state-dir] (start-tui! state-dir {}))
([state-dir opts]
(let [terminal (input/create-terminal)
w (create-watcher-for state-dir)
stop-ch (start-pane-checker! state-dir)
(let [{:keys [terminal watcher stop-ch]}
(setup-runtime state-dir
(:startup-profile opts))
exit-on-jump? (:exit-on-jump opts)
initial-display-mode (:initial-display-mode
opts :auto)]
(try
(tui-loop terminal w state-dir exit-on-jump?
(tui-loop terminal watcher state-dir exit-on-jump?
initial-display-mode)
(finally
(async/close! stop-ch)
(print "\033[2J\033[H")
(flush)
(watcher/close-watcher w)
(watcher/close-watcher watcher)
(input/close-terminal terminal))))))
9 changes: 9 additions & 0 deletions test/ceeker/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@
(is (str/includes? (view/render [] 0 120 :table) "View:Table"))
(is (str/includes? (view/render [] 0 120 :card) "View:Card"))))

(deftest cli-options-include-startup-profile
(testing "--startup-profile is parsed as a boolean option"
(let [{:keys [options errors]}
(cli/parse-opts ["--startup-profile"]
core/cli-options
:in-order true)]
(is (nil? errors))
(is (true? (:startup-profile options))))))

(deftest cli-accepts-view-option
(testing "--view accepts supported startup views"
(doseq [mode ["auto" "table" "card"]]
Expand Down
48 changes: 48 additions & 0 deletions test/ceeker/tui/app_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[ceeker.tui.input]
[ceeker.tui.watcher]
[clojure.core.async :as async]
[clojure.string :as str]
[clojure.test :refer [deftest is testing]]))

(deftest test-handle-search-key-backspace-delete
Expand Down Expand Up @@ -333,6 +334,53 @@
(is (= count-at-stop @call-count)
"no more checks after stop")))))))

(deftest test-start-tui-logs-startup-profile-when-enabled
(testing "start-tui! logs setup timings to stderr"
(let [err (java.io.StringWriter.)]
(binding [*err* err]
(with-redefs [ceeker.tui.app/create-watcher-for
(fn [_] :watcher)
ceeker.tui.app/start-pane-checker!
(fn [_] :stop-ch)
ceeker.tui.app/tui-loop
(fn [& _])
ceeker.tui.input/create-terminal
(fn [] :terminal)
ceeker.tui.input/close-terminal
(fn [_])
ceeker.tui.watcher/close-watcher
(fn [_])
async/close!
(fn [_])]
(app/start-tui! nil {:startup-profile true})))
(let [output (str err)]
(is (str/includes? output "ceeker: startup-profile"))
(is (str/includes? output "create-terminal="))
(is (str/includes? output "create-watcher="))
(is (str/includes? output "start-pane-checker="))
(is (str/includes? output "total="))))))

(deftest test-start-tui-skips-startup-profile-when-disabled
(testing "start-tui! does not log timings without option"
(let [err (java.io.StringWriter.)]
(binding [*err* err]
(with-redefs [ceeker.tui.app/create-watcher-for
(fn [_] :watcher)
ceeker.tui.app/start-pane-checker!
(fn [_] :stop-ch)
ceeker.tui.app/tui-loop
(fn [& _])
ceeker.tui.input/create-terminal
(fn [] :terminal)
ceeker.tui.input/close-terminal
(fn [_])
ceeker.tui.watcher/close-watcher
(fn [_])
async/close!
(fn [_])]
(app/start-tui! nil {})))
(is (= "" (str err))))))

;; --- exit-on-jump tests ---

(deftest test-handle-enter-key-returns-jumped-true
Expand Down
Loading