mirror of
https://github.com/penpot/penpot.git
synced 2025-07-24 05:47:19 +02:00
159 lines
4.2 KiB
Clojure
159 lines
4.2 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns app.util.perf
|
|
"Performance profiling for react components."
|
|
(:require-macros [app.util.perf])
|
|
(:require
|
|
[app.common.math :as mth]
|
|
[rumext.v2 :as mf]
|
|
[goog.functions :as f]
|
|
["react" :as react]
|
|
["tdigest" :as td]))
|
|
|
|
;; For use it, just wrap the component you want to profile with
|
|
;; `perf/profiler` component and pass a label for debug purpose.
|
|
;;
|
|
;; Example:
|
|
;;
|
|
;; [:& perf/profiler {:label "viewport"}
|
|
;; [:section
|
|
;; [:& some-component]]]
|
|
;;
|
|
;; This will catch all renders and print to the console the
|
|
;; percentiles of render time measures. The log function is
|
|
;; automatically debounced to avoid excessive spam to the console.
|
|
|
|
(defn tdigest
|
|
[]
|
|
(specify! (td/TDigest.)
|
|
ITransientCollection
|
|
(-conj! [this n]
|
|
(.push this n)
|
|
this)
|
|
|
|
(-persistent! [this]
|
|
this)))
|
|
|
|
(defn tdigest-summary
|
|
[^js td]
|
|
(str "samples=" (unchecked-get td "n") "\n"
|
|
"Q50=" (.percentile td 0.50) "\n"
|
|
"Q75=" (.percentile td 0.75) "\n"
|
|
"Q95=" (.percentile td 0.90) "\n"
|
|
"MAX=" (.percentile td 1)))
|
|
|
|
(defn timestamp
|
|
[]
|
|
(js/performance.now))
|
|
|
|
(def registry (js/Map.))
|
|
|
|
(def register-measure
|
|
(let [insert!
|
|
(fn [name measure]
|
|
(let [td (.get registry name)]
|
|
(if td
|
|
(conj! td measure)
|
|
(.set registry name (conj! (tdigest) measure)))))
|
|
|
|
print-single-summary!
|
|
(fn [name td]
|
|
(js/console.log (str "[measure: " name "] " (tdigest-summary td))))
|
|
|
|
print-summary!
|
|
(f/debounce
|
|
#(.forEach registry (fn [td name] (print-single-summary! name td)))
|
|
500)]
|
|
(fn [name measure]
|
|
(insert! name measure)
|
|
(print-summary!))))
|
|
|
|
(defn measurable
|
|
[name f]
|
|
(fn [& args]
|
|
(app.util.perf/with-measure name
|
|
(apply f args))))
|
|
|
|
(defn on-render-factory
|
|
[label]
|
|
(let [td (tdigest)
|
|
log (f/debounce
|
|
(fn [phase td]
|
|
(js/console.log (str "[profile: " label " (" phase ")] "
|
|
(tdigest-summary td))))
|
|
300)]
|
|
(fn [id phase adur, bdur, st, ct, itx]
|
|
(conj! td adur)
|
|
(log phase td))))
|
|
|
|
(mf/defc profiler
|
|
{::mf/wrap-props false}
|
|
[props]
|
|
(let [children (unchecked-get props "children")
|
|
label (unchecked-get props "label")
|
|
enabled? (unchecked-get props "enabled")
|
|
enabled? (if (nil? enabled?) true enabled?)
|
|
on-render (mf/use-memo
|
|
(mf/deps label)
|
|
#(on-render-factory label))]
|
|
(if enabled?
|
|
[:> react/Profiler #js {:id label
|
|
:onRender on-render}
|
|
children]
|
|
children)))
|
|
|
|
(defn benchmark
|
|
[& {:keys [run-fn chk-fn iterations name gc]
|
|
:or {iterations 10000}}]
|
|
(let [end-mark (str name ":end")
|
|
blackhole (volatile! nil)]
|
|
(println "=> benchmarking:" name)
|
|
(when gc
|
|
(println "-> force gc: true"))
|
|
|
|
(println "--> warming up: " (* iterations 2))
|
|
(when (fn? gc) (gc))
|
|
(loop [i (* iterations 2)]
|
|
(when (pos? i)
|
|
(vreset! blackhole (run-fn))
|
|
(recur (dec i))))
|
|
(println "--> benchmarking:" iterations)
|
|
(when (fn? gc) (gc))
|
|
(js/performance.mark name)
|
|
(loop [i iterations]
|
|
(when (pos? i)
|
|
(vreset! blackhole (run-fn))
|
|
(recur (dec i))))
|
|
(js/performance.measure end-mark name)
|
|
|
|
(when (fn? chk-fn)
|
|
(when-not (chk-fn @blackhole)
|
|
(println "--> EE: failed chk-fn")))
|
|
|
|
|
|
(let [[result] (js/performance.getEntriesByName end-mark)
|
|
duration (mth/precision (.-duration ^js result) 4)
|
|
avg (mth/precision (/ duration iterations) 4)]
|
|
(println "--> TOTAL:" (str duration " ms"))
|
|
(println "--> AVG :" (str avg " ms"))
|
|
(println "")
|
|
(js/performance.clearMarks name)
|
|
(js/performance.clearMeasures end-mark)
|
|
#js {:duration duration
|
|
:avg avg})))
|
|
|
|
(defn now
|
|
[]
|
|
(js/performance.now))
|
|
|
|
(defn tpoint
|
|
"Create a measurement checkpoint for time measurement of potentially
|
|
asynchronous flow."
|
|
[]
|
|
(let [p1 (now)]
|
|
#(js/Math.floor (- (now) p1))))
|
|
|