-
-
Notifications
You must be signed in to change notification settings - Fork 23
1 Getting started
Add the relevant dependency to your project:
Leiningen: [com.taoensso/tufte "x-y-z"] ; or
deps.edn: com.taoensso/tufte {:mvn/version "x-y-z"}And setup your namespace imports:
(ns my-app
(:require [taoensso.tufte :as tufte]]))Wrap the forms you'd like to (sometimes) profile with p and give them an id (unique keyword):
(defn get-customer-info []
(let [raw-customer-map (tufte/p ::get-raw-customer (fetch-from-db))]
(tufte/p ::enrich-raw-customer
(do-some-work raw-customer-map))))Tufte will record the execution times of these p forms whenever profiling is active.
Whether or not profiling is active, p forms always return their normal body result. So you never need to worry about Tufte messing with your return values.
Activate profiling of p forms with:
| API | Effect |
|---|---|
profiled |
Returns [<body-result> <?pstats>]
|
profile |
Returns <body-result> and sends profiling signal map to registered handlers
|
Since pstats and profiling signals are structured Clojure data, they're trivial to work with. Save to a db, log, put! to a core.async channel, filter, aggregate, use for a realtime analytics dashboard, examine for outliers or unexpected behaviour, feed into your other performance/analytics systems, etc.
- Use
profiledto consume profiling results directly at the call site - Use
profileto consume profiling results later/elsewhere
Between the two, you have great flexibility for a wide range of use cases in production and during development/debugging.
Tufte offers the same rich filtering capabilities available to Telemere, using the exact same API:
(tufte/profile
;; See `profile` docstring for all options:
{:dynamic? true ; Use dynamic (multi-threaded) profiling
:level :debug ; Must be allowed by `set-min-level!`, etc.
:id :id1 ; Must be allowed by `set-id-filter!`, etc.
:sample 0.75 ; Profile 75% of calls
:when (my-pred) ; Must be truthy at runtime to profile
:limit [[1 2000]
[2 60000]] ; Rate limit (1 per 2 secs, 2 per minute)
:limit-by my-user-id ; Rate limit per unique user-id
}
;; Code that has forms wrapped with `tufte/p`:
(my-code))See help:filtering and the profiled / profile docstrings for details.
Once profiled or profile complete, you'll have access to a pstats object.
These can be:
- Merged with one another using
merge-pstats - Derefed to get detailed summary statistics of wrapped
pruntimes - Passed to
format-pstatsto get a string table of summary statistics
An example:
;; See the individual docstrings for options, etc.:
(tufte/add-handler! :my-console-handler
(tufte/handler:console
{:output-fn
(tufte/format-signal-fn
{:format-pstats-opts {:columns [:n :p50 :mean :clock :sum]}})}))
(defnp get-x [] (Thread/sleep 500) "x val")
(defnp get-y [] (Thread/sleep (rand-int 1000)) "y val")
(tufte/profile {:level :info, :id ::my-profiling-id}
(dotimes [_ 5]
(get-x)
(get-y)))
;; The following will be printed to *out*:
;; 2025-04-18T11:23:08.820786Z INFO MyHost readme-examples[15,1]
;; <<< pstats <<<
;; pId nCalls 50% ≤ Mean Clock Total
;; defn_get-y 5 572ms 567ms 2.84s 53%
;; defn_get-x 5 500ms 500ms 2.50s 47%
;;
;; Accounted 5.34s 100%
;; Clock 5.34s 100%
;; >>> pstats >>>