re-frame.performance¶
Measure how long events, subscriptions, effects and view renders take in a production build. With timing on, re-frame2 records each one as a User Timing measure, so browser DevTools, an APM or any PerformanceObserver can read it. This is separate from the trace stream, which exists only in development builds: in development, Xray and the trace stream already time each run and also say why it ran, so reach for this when you need timings from the build you ship.
Application code never calls this namespace. Timing is controlled by two compile-time flags, named fully qualified in your build's :closure-defines. enabled? turns it on:
;; shadow-cljs.edn
{:builds {:app {:compiler-options
{:closure-defines {re-frame.performance/enabled? true}}}}}
- Each measure is named
rf:<kind>:<id>:rf:event:<event-id>for an event handler,rf:sub:<query-id>for a subscription recompute (a cached read records nothing),rf:fx:<fx-id>for an effect handler, orrf:render:<view-id>for a render of a view registered withreg-viewor defined with Fresco'sdefview. A keyword id is written without its leading colon, as inrf:event:todo/add. Noperformance.markentries are created. - CLJS only. On the JVM both flags are constant
falseand timing does nothing, because the Performance API exists only in the browser.
To send the rf: measures to an APM, forward them from a PerformanceObserver:
(def timing-observer
(js/PerformanceObserver.
(fn [entries _]
(doseq [e (.getEntries entries)
:when (.startsWith (.-name e) "rf:")]
(js/console.log (.-name e) (.-duration e)))))) ;; or send to your APM
(.observe timing-observer #js {:type "measure"})
;; At teardown, after collecting the measurements you need:
;; (.disconnect timing-observer)
Find and fix a slow view § Only slow in production shows the measures in the DevTools Performance panel and in an APM.
Compile-time flags¶
enabled?¶
- Kind: var (compile-time
goog-defineboolean) - Signature: set with
:closure-defines {re-frame.performance/enabled? true}(CLJS). On the JVM it is^:const false. - Description: Turns on the timing measures for event handling, subscription recomputes, fx handlers and view renders. Default
false.- Each measure is emitted as
performance.measure(name, {start, end}), with numericperformance.now()timestamps. - The measure is emitted in a
try/finally, so it is recorded even when the measured code throws; the exception still propagates. - Unless
retain-entries?is on, the entry is cleared by name (performance.clearMeasures) straight after it is emitted. An attachedPerformanceObserverhas its own queue and receives the entry asynchronously; clearing the timeline buffer does not clear that queue. - Attach the observer before the work you want to measure. With the default retention setting, attaching later with
buffered: truecannot recover entries already cleared from the timeline. - It is read at compile time only: it is not a
rf/configure!option, and changing it at runtime has no effect. With the default, every measure site is removed under:advanced.
- Each measure is emitted as
retain-entries?¶
- Kind: var (compile-time
goog-defineboolean) - Signature: set with
:closure-defines {re-frame.performance/retain-entries? true}(CLJS). On the JVM it is a constantfalse. - Description: Keeps measure entries in the browser's User Timing buffer instead of clearing each one after it is emitted. Default
false.- Turn it on for one-shot reads with
performance.getEntriesByType("measure")in the console. A DevTools Performance recording does not need it. - Leave it off for long-running sessions such as real-user monitoring. The browser's measure buffer has no size limit, so retained entries accumulate for the life of the page. With it off, each entry is delivered to any live
PerformanceObserverand cleared, so the buffer does not grow andgetEntriesByTypereturns norf:*entries. - It has no effect unless
enabled?is also on. - Like
enabled?, it is read at compile time only; changing it at runtime has no effect.
- Turn it on for one-shot reads with
- Example:
Framework integration¶
Not for application code. A view substrate uses these so that the name it publishes for a view is the name that view's measure carries.
entry-id¶
- Kind: function
- Signature:
(entry-id id) → string - Description: The
<id>half of a measure name: a keyword without its leading colon, anything else as written. A substrate stamps it as the component name it publishes, such as React'sdisplayName, so the name DevTools shows matches therf:render:measure.
build-name¶
- Kind: function
- Signature:
(build-name kind id) → string - Description: The whole measure name,
rf:<kind>:<id>, whose<id>is(entry-id id).
mark-and-measure¶
- Kind: macro
- Signature:
(mark-and-measure kind id body+) → the last body value - Description: With
enabled?on, recordsbodyas onerf:<kind>:<id>measure, even when it throws; withenabled?off, an:advancedbuild keeps onlybody.
See also¶
- Configure dev and prod — how the timing flag combines with
goog.DEBUGacross build profiles. - Observability — how production timing sits alongside the trace and error surfaces.