From 28cfdf682a031330bb4d48d90c839de2c72e70f8 Mon Sep 17 00:00:00 2001 From: rcmerci Date: Fri, 3 Jan 2025 19:10:22 +0800 Subject: [PATCH] feat: add profiler(dev) rightside-tab (#11668) * perf: add frontend.handler.profiler * perf: add frontend.components.profiler --- src/main/frontend/components/profiler.cljs | 60 ++++++++++++++++++ .../frontend/components/right_sidebar.cljs | 14 ++++- src/main/frontend/handler/profiler.cljs | 62 +++++++++++++++++++ 3 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 src/main/frontend/components/profiler.cljs create mode 100644 src/main/frontend/handler/profiler.cljs diff --git a/src/main/frontend/components/profiler.cljs b/src/main/frontend/components/profiler.cljs new file mode 100644 index 0000000000..f18ebd4f07 --- /dev/null +++ b/src/main/frontend/components/profiler.cljs @@ -0,0 +1,60 @@ +(ns frontend.components.profiler + "Profiler UI" + (:require [fipp.edn :as fipp] + [frontend.handler.profiler :as profiler-handler] + [frontend.util :as util] + [logseq.shui.ui :as shui] + [rum.core :as rum])) + +(rum/defcs profiler < rum/reactive + (rum/local nil ::reports) + (rum/local nil ::register-fn-name) + [state] + (let [profiling-fns (keys (rum/react profiler-handler/*fn-symbol->origin-fn)) + *reports (get state ::reports) + *register-fn-name (get state ::register-fn-name)] + [:div + [:b "Profiling fns:"] + [:div.pb-4 + (for [f-name profiling-fns] + [:div.flex.flex-row.items-center.gap-2 + [:pre.select-text (str f-name)] + [:a.inline.close.flex.transition-opacity.duration-300.ease-in + {:title "Unregister" + :on-pointer-down + (fn [e] + (util/stop e) + (profiler-handler/unregister-fn! f-name))} + (shui/tabler-icon "x")]])] + [:div.flex.flex-row.items-center.gap-2 + (shui/button + {:on-click (fn [] + (when-let [fn-sym (some-> @*register-fn-name symbol)] + (profiler-handler/register-fn! fn-sym)))} + "Register fn") + [:input.form-input.my-2.py-1 + {:on-change (fn [e] (reset! *register-fn-name (util/evalue e))) + :on-focus (fn [e] (let [v (.-value (.-target e))] + (when (= v "input fn name here") + (set! (.-value (.-target e)) "")))) + :placeholder "input fn name here"}]] + [:hr] + [:div.flex.gap-2.flex-wrap.items-center.pb-3 + (shui/button + {:size :sm + :on-click (fn [_] (reset! *reports (profiler-handler/profile-report)))} + (shui/tabler-icon "refresh") "Refresh reports") + (shui/button + {:size :sm + :on-click (fn [_] (profiler-handler/reset-report!) + (reset! *reports (profiler-handler/profile-report)))} + (shui/tabler-icon "x") "Reset reports")] + (let [update-time-sum + (fn [m] (update-vals m (fn [m2] (update-vals m2 #(.toFixed % 6)))))] + [:div.pb-4 + [:pre.select-text + (when @*reports + (-> @*reports + (update :time-sum update-time-sum) + (fipp/pprint {:width 20}) + with-out-str))]])])) diff --git a/src/main/frontend/components/right_sidebar.cljs b/src/main/frontend/components/right_sidebar.cljs index ea2f912102..0b4e18e3b3 100644 --- a/src/main/frontend/components/right_sidebar.cljs +++ b/src/main/frontend/components/right_sidebar.cljs @@ -22,7 +22,8 @@ [frontend.db.rtc.debug-ui :as rtc-debug-ui] [frontend.handler.route :as route-handler] [logseq.db :as ldb] - [frontend.components.icon :as icon])) + [frontend.components.icon :as icon] + [frontend.components.profiler :as profiler])) (rum/defc toggle [] @@ -133,6 +134,10 @@ [[:.flex.items-center (ui/icon "cloud" {:class "text-md mr-2"}) "(Dev) RTC"] (rtc-debug-ui/rtc-debug-ui)] + :profiler + [[:.flex.items-center (ui/icon "cloud" {:class "text-md mr-2"}) "(Dev) Profiler"] + (profiler/profiler)] + ["" [:span]])) (defonce *drag-to @@ -407,7 +412,12 @@ [:div.text-sm [:button.button.cp__right-sidebar-settings-btn {:on-click (fn [_e] (state/sidebar-add-block! repo "rtc" :rtc))} - "(Dev) RTC"]])]] + "(Dev) RTC"]]) + (when (state/sub [:ui/developer-mode?]) + [:div.text-sm + [:button.button.cp__right-sidebar-settings-btn {:on-click (fn [_e] + (state/sidebar-add-block! repo "profiler" :profiler))} + "(Dev) Profiler"]])]] [:.sidebar-item-list.flex-1.scrollbar-spacing.px-2 (if @*anim-finished? diff --git a/src/main/frontend/handler/profiler.cljs b/src/main/frontend/handler/profiler.cljs new file mode 100644 index 0000000000..b4ec8603e7 --- /dev/null +++ b/src/main/frontend/handler/profiler.cljs @@ -0,0 +1,62 @@ +(ns frontend.handler.profiler + "Provides fns for profiling. + TODO: support both main thread and worker thread." + (:require [goog.object :as g])) + +(def ^:private *fn-symbol->key->call-count (volatile! {})) +(def ^:private *fn-symbol->key->time-sum (volatile! {})) + +(def *fn-symbol->origin-fn (atom {})) + +(defn- get-profile-fn + [fn-sym original-fn custom-key-fn] + (fn profile-fn-inner [& args] + (let [start (system-time) + r (apply original-fn args) + elapsed-time (- (system-time) start) + k (when custom-key-fn (custom-key-fn r))] + (vswap! *fn-symbol->key->call-count update-in [fn-sym :total] inc) + (vswap! *fn-symbol->key->time-sum update-in [fn-sym :total] #(+ % elapsed-time)) + (when k + (vswap! *fn-symbol->key->call-count update-in [fn-sym k] inc) + (vswap! *fn-symbol->key->time-sum update-in [fn-sym k] #(+ % elapsed-time))) + r))) + +(defn register-fn! + [fn-sym & {:keys [custom-key-fn] :as _opts}] + (assert (qualified-symbol? fn-sym)) + (let [ns (namespace fn-sym) + s (munge (name fn-sym))] + (if-let [original-fn (find-ns-obj (str ns "." s))] + (let [profiled-fn (get-profile-fn fn-sym original-fn custom-key-fn)] + (swap! *fn-symbol->origin-fn assoc fn-sym original-fn) + (g/set (find-ns-obj ns) s profiled-fn)) + (throw (ex-info (str "fn-sym not found: " fn-sym) {}))))) + +(defn unregister-fn! + [fn-sym] + (let [ns (namespace fn-sym) + s (munge (name fn-sym))] + (vswap! *fn-symbol->key->call-count dissoc fn-sym) + (vswap! *fn-symbol->key->time-sum dissoc fn-sym) + (when-let [origin-fn (get @*fn-symbol->origin-fn fn-sym)] + (some-> (find-ns-obj ns) (g/set s origin-fn)) + (swap! *fn-symbol->origin-fn dissoc fn-sym)))) + +(defn reset-report! + [] + (vreset! *fn-symbol->key->call-count {}) + (vreset! *fn-symbol->key->time-sum {})) + +(defn profile-report + [] + {:call-count @*fn-symbol->key->call-count + :time-sum @*fn-symbol->key->time-sum}) + +(comment + (register-fn! 'datascript.core/entity) + (prn :profiling (keys @*fn-symbol->origin-fn)) + (prn :report) + (pprint/pprint (profile-report)) + (reset-report!) + (unregister-fn! 'datascript.core/entity))