FlowStorm is a tracing debugger for Clojure and ClojureScript.

It can instrument any Clojure code and provides many tools to explore and analyze your programs executions.

1. Quick start

Before you start check FlowStorm minimum requirements.

Important
Minimum requirements
  • jdk >= 17 (if you still need to run it with jdk11 take a look at here)

  • Clojure >= 1.10.0

1.1. Clojure

There are two ways of using FlowStorm for Clojure :

  • With ClojureStorm (recommended) : Swap your dev Clojure compiler by ClojureStorm and get everything instrumented automatically

  • Vanilla FlowStorm : Just add FlowStorm to your dev classpath and instrument by tagging and re-evaluating forms

ClojureStorm is a fork of the official Clojure compiler that adds automatic instrumentation so you don’t need to think about it (you can still disable it when you don’t need it).

You use it by swapping the official Clojure compiler by ClojureStorm at dev time, using dev aliases or profiles.

ClojureStorm

This is the newest and simplest way of using FlowStorm. It requires you to swap your official Clojure compiler by ClojureStorm only at dev time.

Swapping compilers sounds like a lot, but don’t worry, ClojureStorm is just a patch applied over the official compiler with some extra stuff for automatic instrumentation, you shouldn’t encounter any differences, it is only for dev, and you can swap it back and forth by starting your repl with a different alias or lein profile.

The easiest way to run and learn FlowStorm with ClojureStorm is by running the repl tutorial, like this :

;; on linux and mac-os
clj -Sforce -Sdeps '{:deps {} :aliases {:dev {:classpath-overrides {org.clojure/clojure nil} :extra-deps {com.github.flow-storm/clojure {:mvn/version "RELEASE"} com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}} :jvm-opts ["-Dclojure.storm.instrumentEnable=true" "-Dclojure.storm.instrumentOnlyPrefixes=user"]}}}' -A:dev

;; on windows
clj -Sforce -Sdeps '{:deps {} :aliases {:dev {:classpath-overrides {org.clojure/clojure nil} :extra-deps {com.github.flow-storm/clojure {:mvn/version """RELEASE"""} com.github.flow-storm/flow-storm-dbg {:mvn/version """RELEASE"""}} :jvm-opts ["""-Dclojure.storm.instrumentEnable=true""" """-Dclojure.storm.instrumentOnlyPrefixes=user"""]}}}' -A:dev
Important
On using maven RELEASE as version

Be careful when using RELEASE as maven versions, since the first time is going to fetch the latest one but it will be cached unless you run with -Sforce. It is much safer to just write the latest versions. Check out the github page for the latest stable releases.

Note
On disabling/enabling instrumentation

It is possible to disable/enable instrumentation without restarting the repl, which is necessary for things like measuring performance.

Pasting that command on your terminal will bring up a repl with FlowStorm and the compiler swapped by ClojureStorm. When the repl comes up just evaluate the :tut/basics keyword on it for a tour of the basics.

After the tutorial you would want to add and configure it for your projects, so here are the basics :

If your project is using deps.edn, your deps.edn file should look like this :

{:paths ["src"]
 :deps {}
 :aliases {:dev {:classpath-overrides {org.clojure/clojure nil} ;; for disabling the official compiler
                 :extra-deps {com.github.flow-storm/clojure {:mvn/version "RELEASE"}
                              com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}
                 :jvm-opts ["-Dclojure.storm.instrumentEnable=true"
                            "-Dclojure.storm.instrumentOnlyPrefixes=YOUR_INSTRUMENTATION_STRING"]}}}

where YOUR_INSTRUMENTATION_STRING should be a comma separated list of namespaces prefixes like :

my-project.,lib1.,lib2.core

which means only instrument my-project.* (which includes all sub namespaces), all lib1.* and only everything under lib2.core

This is probably what most people want since you don’t want to instrument things like nrepl, cider or any of your tooling stuff although you can still do it if you need it. There are also other options you can check with :help.

If your project uses lein, your project.clj file should look something like this :

(defproject my.project "1.0.0"
  :profiles {:dev {:dependencies [[com.github.flow-storm/clojure "RELEASE"]
                                  [com.github.flow-storm/flow-storm-dbg "RELEASE"] ]
                   :exclusions [org.clojure/clojure] ;; for disabling the official compiler
                   :jvm-opts ["-Dclojure.storm.instrumentEnable=true"
                              "-Dclojure.storm.instrumentOnlyPrefixes=YOUR_INSTRUMENTATION_STRING"]}}
  :main foo.core)

with YOUR_INSTRUMENTATION_STRING as described above.

Note
lein dependencies

If you are usig lein < 2.11.0 make sure your global :dependencies don’t include the official org.clojure/clojure dependency. Moving to lein latest version should work ok even if your global :dependencies contains the Clojure dep.

Note
#rtrace and #trace

When using FlowStorm with ClojureStorm it is a error to use #trace or #rtrace. They aren’t needed since everything you declared in clojure.storm.instrumentOnlyPrefixes will be automatically instrumented.

Vanilla FlowStorm

If you use clojure cli you can start a repl with the FlowStorm dependency loaded like this :

;; on linux and mac-os
clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}}'

;; on windows
clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version """RELEASE"""}}}'
Important
On using maven RELEASE as version

Be careful when using RELEASE as maven versions, since the first time is going to fetch the latest one but it will be cached unless you run with -Sforce. It is much safer to just write the latest versions. Check out the github page for the latest stable releases.

If you are a lein user add the dependency to your project.clj :dependencies and run lein repl.

Then require the api namespace and start the debugger :

user> (require '[flow-storm.api :as fs-api]) ;; the only namespace you need to require

user> (fs-api/local-connect) ;; will run the debugger GUI and get everything ready

You should now see a empty debugger window.

Let’s debug something :

user> #rtrace (reduce + (map inc (range 10))) ;; #rtrace will instrument and run some code
quick start

After running it, you should get the return value of the expression (as if #rtrace wasn’t there). The debugger thread list (the one on the left) shows all the threads it has recordings for. Double clicking on it should open the "thread exploring tools" for that thread in a new tab.

This guide will cover all the tools in more detail but if you are interested in code stepping for example you will find it in the code stepping tool at the bottom left corner of the thread tab, the one that has the () icon. Clicking on it will show you something like the picture above.

Go ahead and use the stepping controls to step over the code.

Now that everything seems to be working move on and explore the many features FlowStorm provides. There are many ways of instrumenting your code, and many ways to explore its executions.

1.2. ClojureScript

Debugging ClojureScript is a case of remote debugging in FlowStorm. This means the debugger will run in a separate process and connect to the debuggee (your browser or nodejs runtime) via a websocket and optionally an nrepl server.

There are two ways of using FlowStorm with ClojureScript :

  • With ClojureScriptStorm (recommended) : Swap your ClojureScript compiler by ClojureScriptStorm at dev and get everything instrumented automatically

  • Vanilla FlowStorm : Just add FlowStorm to your dev classpath and instrument by tagging and re-evaluating forms

ClojureScriptStorm is a fork of the official ClojureScript compiler that adds automatic instrumentation so you don’t need to think about it (you can still disable it when you don’t need it).

You use it by swapping the official ClojureScript compiler by ClojureScriptStorm at dev time, using dev aliases or profiles.

Note
Repl connection

For enabling every debugger feature, FlowStorm needs to connect to a cljs repl. Currently only shadow-cljs repl over nrepl is supported.

ClojureScriptStorm with shadow-cljs
Important
Minimum requirements
  • Shadow Cljs >= 2.25.4

  • FlowStorm >= 3.7.4

For setting up FlowStorm with shadow-cljs you need to modify two files, your shadow-cljs.edn and your deps.edn. This is setup once and forget, so once you have configured FlowStorm you can do everything from the UI, without any other sources modifications.

If you want a shadow-cljs template to play with, take a look at this repo.

Note
shadow-cljs

Currently you can only use ClojureScriptStorm with shadow-cljs if you are resolving your dependencies with deps.edn. This means having :deps true or similar in your shadow-cljs.edn. If you have your dependecies directly in your shadow-cljs.edn you will have to use Vanilla FlowStorm for now. This is because there is currently no way to swap the ClojureScript compiler in shadow-cljs.edn.

First, make your shadow-cljs.edn looks something like this :

{:deps {:aliases [:dev]}
 :nrepl {:port 9000}
 ...
 :builds
 {:my-app {...
           :devtools {:preloads [flow-storm.storm-preload]
                      :http-port 8021}}}}

So, the important parts are: you need to tell shadow to apply your deps.edn dev alias, set up a nrepl port, and also add flow-storm.storm-preload to your preloads. If you have other preloads make sure flow-storm.storm-preload is the first one.

Then, modify your deps.edn dev profile to look like this :

{...
 :aliases
 {:dev {:classpath-overrides {org.clojure/clojurescript nil} ;; disable the official compiler
        :extra-deps {thheller/shadow-cljs {:mvn/version "2.25.6" ;; >= 2.25.4
                                           :exclusions [org.clojure/clojurescript]}
                     ;; bring ClojureScriptStorm
                     com.github.flow-storm/clojurescript {:mvn/version "RELEASE"}
                     ;; add FlowStorm runtime dep
                     com.github.flow-storm/flow-storm-inst {:mvn/version "RELEASE"}}
       :jvm-opts ["-Dcljs.storm.instrumentOnlyPrefixes=your-app-base-ns"
                   "-Dcljs.storm.instrumentEnable=true"
                   "-Dflowstorm.startRecording=false"]}}}

There are lots of things going on there, but the main ones are: disabling the official compiler, adding ClojureScriptStorm and FlowStorm dependencies, and then configuring what you want ClojureScriptStorm to automatically instrument.

It is important to configure what namespaces you want to instrument, and you do this by setting the cljs.storm.instrumentOnlyPrefixes jvm property.

This is a comma separated list of namespaces prefixes, you normally want your app namespaces plus some libraries, like : cljs.storm.instrumentOnlyPrefixes=org.my-app,org.my-lib,hiccup

And this is it. Once you have it configured, run your shadow watch as you normally do, load your app on the browser (or nodejs).

Whenever your need the debugger, on a terminal run the ui with your shadow-cljs.edn data :

clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}}' -X flow-storm.debugger.main/start-debugger :port 9000 :repl-type :shadow :build-id :my-app

and then reload you page so it connects to it.

Since we started the app with flowstorm.startRecording=false you will have to click on the record button once to start recording. Whenever recording is enable and something executes under an instrumented namespace you should see the recordings appear in the debugger under the main thread.

Note
recording expressions typed on the repl

If you type at the repl something like (defn foo [a b] (+ a b)) under an instrumented ns, the foo funciton will get instrumented automatically and you will able to explore the recordings after the function is called. On the other side, typing a simple expression like (+ 1 2) will not show anything, this is currently a limitation but you can still make that work by wrapping the expression on a fn and immediately calling it, like fn [] (+ 1 2)

ClojureScriptStorm with cljs.main

You can use FlowStorm and ClojureScriptStorm with cljs.main.

To compile instrumented files :

clj -J-Dcljs.storm.instrumentOnlyPrefixes=org.foo -J-Dcljs.storm.instrumentEnable=true -Sdeps '{:paths ["src"] :deps {com.github.flow-storm/clojurescript {:mvn/version "RELEASE"} com.github.flow-storm/flow-storm-inst {:mvn/version "RELEASE"}}}' -M -m cljs.main -co '{:preloads [flow-storm.storm-preload] :main org.foo.core}' --compile

To run a repl that instrument everything under org.foo :

clj -J-Dcljs.storm.instrumentOnlyPrefixes=org.foo -J-Dcljs.storm.instrumentEnable=true -Sdeps '{:paths ["src"] :deps {com.github.flow-storm/clojurescript {:mvn/version "RELEASE"} com.github.flow-storm/flow-storm-inst {:mvn/version "RELEASE"}}}' -M -m cljs.main -co '{:preloads [flow-storm.storm-preload] :main org.foo.core}' --repl

Then run the FlowStorm UI :

clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}}' -X flow-storm.debugger.main/start-debugger

And now refresh your browser page so your browser app connects to the UI.

ClojureScript vanilla FlowStorm

Let’s say you are using shadow-cljs to start a ClojureScript repl.

First you need to add FlowStorm dependency to your project dependencies, like this :

$ cat shadow-cljs.edn

{...
 :dependencies [... [com.github.flow-storm/flow-storm-inst "RELEASE"]]

 ;; the next two lines aren't needed but pretty convenient
 :nrepl {:port 9000}
 :my-build-id {:devtools {:preloads [flow-storm.preload]}}
 ...}
Important
On using maven RELEASE as version

Be careful when using RELEASE as maven versions, since the first time is going to fetch the latest one but it will be cached unless you run with -Sforce. It is much safer to just write the latest versions. Check out the github page for the latest stable releases.

Then let’s say you start your repl like :

npx shadow-cljs watch :my-build-id

shadow-cljs - config: /home/jmonetta/demo/shadow-cljs.edn
shadow-cljs - server version: 2.19.0 running at http://localhost:9630
shadow-cljs - nREPL server started on port 9000
shadow-cljs - watching build :my-build-id
[:my-build-id] Configuring build.
[:my-build-id] Compiling ...
[:my-build-id] Build completed. (127 files, 0 compiled, 0 warnings, 6.19s)

cljs.user=>

As you can see from the output log shadow-cljs started a nrepl server on port 9000, this is the port FlowStorm needs to connect to, so to start the debugger and connect to it you run :

;; on linux and mac-os
clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}}' -X flow-storm.debugger.main/start-debugger :port 9000 :repl-type :shadow :build-id :my-build-id

;; on windows
clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version """RELEASE"""}}}' -X flow-storm.debugger.main/start-debugger :port 9000 :repl-type :shadow :build-id :my-build-id

And that is all you need, the debugger GUI will pop up and everything will be ready.

Try tracing some code from the repl :

cljs.user> #rtrace (reduce + (map inc (range 10))) ;; #rtrace will instrument and run some code

After running it, you should get the return value of the expression (as if #rtrace wasn’t there).

The debugger thread list (the one on the left) shows all the threads it has recordings for. Because we are in javascript land there will always be just one thread, called main. Double clicking it should open the "thread exploring tools" for that thread in a new tab.

This guide will cover all the tools in more detail but if you are interested in code stepping for example you will find it in the code stepping tool at the bottom left corner of the thread tab, the one that has the () icon.

Click on it and use the stepping controls to step over the code.

Now that everything seems to be working move on and explore the many features FlowStorm provides. There are many ways of instrumenting your code, and many ways to explore its executions.

If you are not using a repl or the repl you are using isn’t supported by FlowStorm yet you can still use the debugger but not all features will be supported (mainly the browser features).

For this you can start the debugger like before but without any parameters, like this :

clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}}' -X flow-storm.debugger.main/start-debugger

And then go to your app code and call (flow-storm.runtime.debuggers-api/remote-connect) maybe on your main, so every time your program starts will automatically connect to the repl.

Note
ClojureScript environments

FlowStorm is supported for ClojureScript in :

  • Browsers

  • NodeJS

  • React native

Note
NodeJs and react-native

On NodeJs and react-native you need to install the websocket library. Do this by running npm install websocket --save

For react-native if your app is running inside a cellphone you will have to also provide the :debugger-host key to flow-storm.debugger.main/start-debugger with your box ip address, unless you are using adb reverse with your ports for which you will have to adb reverse tcp:7722 tcp:7722 (the debugger websocket port)

Note
App initialization debugging

If you need to debug some app initialization, for adding #trace tags before the debugger is connected you will have to require flow-storm.api yourself, probably in your main. All the tracing will be replayed to the debugger once it is connected.

Here is a repo you can use if you want to try FlowStorm with shadow-cljs https://github.com/flow-storm/shadow-flow-storm-basic

Multiple ClojureScript builds

You can setup FlowStorm to debug multiple ClojureScript builds. This can be useful when your application is made up of multiple parts, like when you have web workers.

Debugging multiple builds require multiple debugger instances, one per build.

The FlowStorm UI will start a websocket server, by default on 7722, so if you want to run multiple instances of it, you need to run each instance under a different port. You can do this by providing a :ws-port to the startup command.

So let’s say you want to run two debuggers, one for your page and one for a webworker, your can run them like this :

# on one terminal start your app debugger instance
clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}}' -X flow-storm.debugger.main/start-debugger :port 9000 :repl-type :shadow :build-id :my-app :ws-port 7722

# on a second terminal start your webworker debugger instance
clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}}' -X flow-storm.debugger.main/start-debugger :port 9000 :repl-type :shadow :build-id :my-web-worker :ws-port 7733

Now you also need to configure your builds to tell them what port they should connect to. You do this by writing different preloads for each of your builds, and then using them instead of your flow-storm.storm-preload, like:

my_app.main_storm_preload.cljs

(ns my-app.main-storm-preload
  (:require [cljs.storm.tracer]
            [flow-storm.tracer :as tracer]
            [flow-storm.runtime.debuggers-api :as dbg-api]))

(tracer/hook-clojurescript-storm)
(dbg-api/setup-runtime)
(dbg-api/remote-connect {:debugger-ws-host "localhost" :debugger-ws-port 7722})

my_app.webworker_storm_preload.cljs

(ns my-app.webworker-storm-preload
  (:require [cljs.storm.tracer]
            [flow-storm.tracer :as tracer]
            [flow-storm.runtime.debuggers-api :as dbg-api]))

(tracer/hook-clojurescript-storm)
(dbg-api/setup-runtime)
(dbg-api/remote-connect {:debugger-ws-host "localhost" :debugger-ws-port 7733})

They are the same as flow-storm.storm-preload just with different port numbers.

Now you can configure your shadow-cljs.edn like this :

{...
 :builds
 {:app
  {:target :browser
   ...
   :modules
   {:my-app {:init-fn my.app/init
           :preloads [my-app.main-storm-preload]}
    :my-webworker {:init-fn my.app.worker/init
                   :preloads [my-app.webworker-storm-preload]
                   :web-worker true}}}}}
Note
Multiple debuggers tips

You can change the theme or customize the styles of different instances to make it easier to know which debugger instance is connected to which application.

1.3. Babashka

You can debug your babashka scripts with FlowStorm using the JVM. The process is quite simple.

Let’s say we want to debug this example script https://raw.githubusercontent.com/babashka/babashka/master/examples/htmx_todoapp.clj which runs a webserver with a basic todo app.

First we need to generate a deps.edn by running bb print-deps > deps.edn

Then modify the resulting deps.edn to add the FlowStorm alias like this :

{...
 :aliases {:dev {:classpath-overrides {org.clojure/clojure nil} ;; for disabling the official compiler
                 :extra-deps {com.github.flow-storm/clojure {:mvn/version "RELEASE"}
                              com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}
                 :jvm-opts ["-Dclojure.storm.instrumentEnable=true"
                            "-Dclojure.storm.instrumentOnlyPrefixes=user"]}}}

With clojure.storm.instrumentOnlyPrefixes=user we are telling ClojureStorm to instrument everything inside the user namespace since the script doesn’t contain any namespace declaration.

And that is it, you can now start your clojure repl as usual, with clj -A:dev and then eval the :dbg keyword to start the debugger UI.

Then eval the entire file to compile everything. To start the server in this example you will have to remove the wrapping that is basically only allowing the server to run if we are running from babashka, like this :

(when true #_(= *file* (System/getProperty "babashka.file"))
  ...)

so we can also start it from Clojure.

After the server has started, you can use the app from the browser and everything will get recorded as usual.

2. Remote debugging

You can remotely debug any Clojure application the exposes a nrepl server.

In terms of dependencies, the debuggee side should be setup the same as a normal local setup, with the optional change that you can use flow-storm-inst instead of flow-storm-dbg, being the former a slimmed down version of the later one that doesn’t contain some libraries used only by the UI, but using the full flow-storm-dbg is also ok.

The easiest way to debug a remote application is via a ssh tunnel. You can create it from your dev box like this :

ssh -L 9000:localhost:9000 -R 7722:localhost:7722 my-debuggee-box.com

assuming your remote process at my-debuggee-box.com has started a nrepl server listening on port 9000 and that the debugger websocket server is running on the default port.

After the tunnel is established, you can run you debugger UI like this :

clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}}' -X flow-storm.debugger.main/start-debugger :port 9000

and that is it.

If you need to connect the debugger to a remote process without a ssh tunnel or you need to configure the websocket server port you can do it like this :

clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}}' -X flow-storm.debugger.main/start-debugger :port NREPL-PORT :runtime-host '"YOUR-APP-BOX-IP-ADDRESS"' :debugger-host '"YOUR-BOX-IP-ADDRESS"' :ws-port WS-SERVER-PORT

2.1. Docker

If you run you process inside a docker container, here is a basic template for using FlowStorm with it https://github.com/flow-storm/docker-flow-storm-basic

3. Instrument code

Note
ClojureStorm

Instructions here only apply to vanilla FlowStorm. If you are using ClojureStorm or ClojureScriptStorm this is done automatically for you, so just skip this section.

Code instrumentation in FlowStorm is done by rewriting your code, in a way that doesn’t change its behavior but when executed will trace everything the code is doing.

You can instrument code from the browser tool or the repl.

3.1. Instrument from the repl

Instrument any form with #trace

You can instrument any top level form at the repl by writing #trace before it, like this :

#trace
(defn sum [a b]
  (+ a b))

and then evaluating the form.

important

#trace is meant to be used with forms that don’t run immediately, like: defn, defmethod, extend-type, etc. Use #rtrace to trace and run a form, like #rtrace (map inc (range 10)).

Run code with #rtrace

#rtrace is useful in two situations :

First, when instrumenting and running a simple form at the repl, like:

#rtrace (-> (range) (filter odd?) (take 10) (reduce +))

Second, when you want to run a form with a certain flow id (see flows).

#rtrace by default will instrument and run the form with flow id 0, but you can use #rtrace0, #rtrace1, #rtrace2, #rtrace3, #rtrace4, #rtrace5 to trace with flows ids [0..5].

Instrument namespaces

FlowStorm allows you to instrument entire namespaces by providing flow-storm.api/instrument-namespaces-clj.

You call it like this :

(instrument-namespaces-clj #{"org.my-app.core" "cljs."})

The first argument is a set of namespaces prefixes to instrument. In the previous example it means instrument all namespaces starting with org.my-app.core, and all starting with cljs.

The second argument can be a map supporting the following options :

  • :excluding-ns a set of strings with namespaces that should be excluded

  • :disable a set containing any of #{:expr :binding :anonymous-fn} useful for disabling unnecessary traces in code that generate too many

  • :verbose? when true show more logging

What can’t be instrumented?

These are some limitations when instrumenting forms :

  1. Very big forms can’t be fully instrumented. The JVM spec has a limit on the size of methods and instrumentation adds a lot of code. When instrumenting entire namespaces, if you hit this limit on a form a warning will printed on the console saying Instrumented expression is too large for the Clojure compiler and FlowStorm automatically tries to instrument it with a lighter profile, by disabling some instrumentation.

  2. Functions that call recur without a loop

  3. Functions that return recursive lazy sequences. Like (fn foo [] (lazy-seq (…​ (foo))))

Un-instrument code

Un-instrumenting code that has been instrumented with #trace or #ctrace is straight forward, just remove the tag and re evaluate the form.

To un-instrument entire namespaces you can use flow-storm.api/uninstrument-namespaces-clj which accept a set of namespaces prefixes.

3.2. Instrument with the browser

Most of the time you can instrument code by just clicking in the browser. The only exceptions are functions that were just defined in the repl and weren’t loaded from a file.

Instrument vars

Using the browser you can navigate to the var you are interested in and then use the instrument button to instrument it.

browser var instrumentation

There are two ways of instrumenting a var :

  • Instrument (instrument just the var source code)

  • Instrument recursively (recursively instrument the var and all vars referred by it)

Instrument namespaces

Using the browser you can also instrument multiple namespaces. Do this by selecting the namespaces you are interested in and then a right click should show you a menu with two instrumentation commands.

browser ns instrumentation
  • Instrument namespace :light - record function arguments and return values (not expressions, no bindings tracing)

  • Instrument namespace :full fully instrument everything

Light instrumentation is useful when you know the functions generate too many traces, so you can opt to trace just functions calls and returns. You can then fully instrument whatever functions you are interested in.

Un-instrument code

The bottom panel shows all instrumented vars and namespaces.

browser uninstrument

You can un-instrument them temporarily with the enable/disable checkbox or permanently with the del button.

3.3. Fully instrument a form from the code view

fully instrument form

If you have instrumented a form with the :light profile you can fully instrument it by right clicking on the current form and then clicking Fully instrument this form.

4. Flows tool

The Flows tab contains a bunch of tools for analyzing all traced executions flows.

Flows are identified by a flow-id and can be started by running a form with #rtrace, #rtrace1, #rtrace2, etc,. If you run a flow twice with the same flow id, the first one is going to be replaced.

Note
ClojureStorm

Currently, working with different flows in parallel is only supported in Vanilla FlowStorm. If you are using ClojureStorm or ClojureScriptStorm the only flow you will see is the funnel flow.

A flow will happen in one or more threads. A separate tab will show for each thread.

For example if we trace a form that spawns multiple threads :

#rtrace (-> (pmap (fn [i] (* i i)) (range 5)))
thread outer form

the (→ (pmap …​ (range 5))) form will run on the main thread

thread inner form

while the (fn [i] (* i i)). executions will be distributed in the clojure-agent-send-off-pool-* since clojure.core/pmap is backed by a thread pool.

Anything instrumented that isn’t run under #rtrace will end up in the funnel flow, which accumulates all traces that don’t contain any flow-id.

funnel flow

4.1. Code tool

code tool tab

The code tool is the second of the Flows tab. It provides most of the functionality found in a traditional debugger. You can use it to step over each expression, visualize values, locals and more.

Code stepping

The code tool allows you to step and "travel throught time" in two ways:

  • Use the controls at the top to step over your code in different ways.

  • Click on the highlighted forms to position the debugger at that point in time.

controls

For moving around using the controls we have two rows of buttons.

The second row of controls, the most imoprtant one, are the stepping controls.

From left to right they are :

  • Step over backwards, will make one step backwards always staying on the same frame.

  • Step backwards, will step backwards in time going into sub functions.

  • Step out, will position the debugger in the next step after this function was called.

  • Step forward, will step forward in time going into sub functions.

  • Step over forward, will make one step forwards always staying on the same frame.

The numbers at the center show current_step_index / total_steps. This means that a total of total_steps has been recorded for this thread so far. Write any number (less than total_steps) on the text box to jump into that position in time.

The buttons around the step counter are :

  • Jump to the first step of the recording.

  • Jump to the last step of the recording.

The last stepping controls to the right are the power stepping controls.

On the first row we have more controls, also for moving around in time.

From left to right we have :

  • Undo navigation

  • Redo navigation

  • Add a bookmark

  • Re run flow. This allows you to re-run the entire flow if it is different from the funnel flow. Only useful in vanilla FlowStorm.

  • The value search tool

Note
Highlighting

Only the forms that were executed at least once for the current function frame will be highlighted.

This means that code can be un-highlighted for two reasons:

  • there isn’t any recording for that part of the code

  • there is a recording but doesn’t belong to this function frame.

stepper highlighting

In the contrived example above we see we are stepping the foo function. All inside this function body is highlighted but the bodies of the two anonymous functions for mapping and reducing. This will only get highlighted once you step into their bodies.

In this case you are sure there are recordings for these functions bodies because the reduce is non lazy, so if you keep stepping eventually you will get into their bodies, but there is a faster way.

stepper highlighting 2

For this you can right click on any un-highlighted expression that you think there could be a recording for and select Jump forward here.

stepper highlighting 3

This will make FlowStorm scan from the current point of the timeline searching forward for a value recorded at that coordinate (if any) and move the stepper to that point in time.

You also have Jump to first record here which will scan from the beginning of the timeline and Jump backwards here which will search backwards from the current position.

Power stepping
controls power custom

The controls at the right are power stepping controls. They provide more powerfull ways of stepping through the code.

Clicking on the back and forward button will use the selected power stepping tool.

There are currently 5 power stepping tools :

  • identity, will step to the prev/next value which identity is the same as the current value.

  • 'equality', will step to the prev/next value which is equals (clojure equality) to the current value.

  • same-coord will step to the prev/next value for the same coordinate. This means it will move to the next recording in the timeline for this exact place in the code you are currently in. You can also see it as take me to all the situations when the current expression executed doesn’t matter how we got to it.

  • custom, allows you to provide a predicate, which will be used to find the next step. If you define it like (fn [v] (map? v)) will make the power stepper step over all map values.

  • custom-same-coord, the same as custom but fixed on the current coordinate like same-coord.

  • identity-other-thread, will step to a position which identity is the same as the current value in a different thread. Here the prev and next arrows do the same thing, it will just jump to the first position that matches this value on a different thread. This has some limitations. If there are more than two threads working with this identity there is no way of choosing which thread to go. If you need more control, checkout the programmable debugging section, specially the find-expr-entry function.

Note
Custom stepping

Custom power stepping is only supported in Clojure now.

Searching

You can use the search box under the controls to search for the next expression (starting at your current step) which its string serialization contains your search text.

You can use the print-level and print-length fields to control how deep this serialization will be made. If these numbers are big and you are searching over lots of nested data it will take some time. The search progress will be displayed at the right and you can always use Ctrl-g to stop the search if it is taking too long.

Loops

When clicking on a highlighted form two things can happen :

  • If the form was executed only once for the current frame, the debugger will immediately jump to it.

  • Else if the form was executed multiple times, a context menu will show all the values that form evaluated to, sorted by time, and clicking on them will make the debugger jump to that specific point in time. This is useful for debugging loops.

loops
Exceptions debugging

FlowStorm will report all functions that didn’t return because an exception unwind the stack, even when that exception was captured further and it didn’t bubble up.

exceptions

When an unwind situation is recorded a combobox will show up in the toolbar, containing all the functions names together with the exceptions types. If you hover the mouse over any of them, a tooltip will display the exception message.

Clicking on any of them will position the stepper at that point in time so you can explore what happened before.

Locals

The locals panel will always show the locals bounded for the current point in time.

locals

Right clicking on them will show a menu where you can :

  • define all frame vars

  • define the value with a name, so you can use it at the repl

  • inspect the value with the value inspector

  • tap the value as with tap>

Define all frame vars will define all the bindings for the entire frame in the current form namespace. This is useful for trying things at your editor as described here https://www.cognitect.com/blog/2017/6/5/repl-debugging-no-stacktrace-required

Stack

The stack panel will always show the current stacktrace. Be aware that the stacktrace only include functions calls that had been recorded, so if you aren’t recording everything there will be gaps.

stack

Double clicking on any of the stack entries will make the debugger jump to that point in time.

Value panels

Value panels show in many places in FlowStorm.

value panels

The value panel in the code tool always display a pretty print of the current expression value.

You can configure the print-level and print-meta for the pretty printing by using the controls at the top.

Define value for repl

Use the def button to define a var pointing to the current inspector value.

You can use / to provide a namespace, otherwise will be defined under [cljs.]user

Bookmarks

Bookmarks are useful when you find yourself jumping around, trying to understand a complex execution. They enable you to mark execution positions so you can come back to them later.

bookmarks add btn

You can bookmark the current position by pressing the bookmark button. It will ask you the bookmark description.

bookmarks open btn

Use the button open button to open the bookmarks panel.

bookmarks

Double clicking on any bookmark will make the debugger jump back to its position.

Re running a flow

Every time you run a form with #rtrace FlowStorm keeps a copy of the form. You can use the re-run-flow button after instrumenting or un-instrumenting code, since it allows you to re run the form with a single click.

4.2. Call Stack tree tool

The call stack tree tool is the first one of the Flows tab. It allows you to see the execution flow by expanding its call stack tree.

callstack tool tab

The call stack tree is useful for a high level overview of a complex execution and also as a tool for quickly moving through time.

You can jump to any point in time by double clicking on a node or by right clicking and on the context menu selecting Step code.

callstack tree
Note
Tree refreshing

If FlowStorm keeps receiving traces for the thread you are analyzing, it will keep building the tree but will not automatically refresh its visuals. You can use the refresh button at the root to update it.

Use the button at the top left corner of the tree tool to show the current frame of the debugger in the tree.

There are also two value panels at the bottom that show the arguments and return value for the currently selected function call.

4.3. Functions tool

The functions tool is the third one of the Flows tab.

functions tool tab

It shows a list of all traced functions sort by how many times the have been called.

functions

Normal functions will be colored black, multimethods magenta and types/records protocols/interfaces implementations in green.

Together with the call stack tree it provides a high level overview of a thread execution, and allows you to jump through time much faster than single stepping.

You can search over the functions list by using the bar at the top.

Function calls

Clicking on the calls counter of any function will display all function calls on the right sorted by time. Each line will show the arguments vector for each call, and their return value. Use the check boxes at the top to hide some of the arguments.

function calls

Double clicking on any row in the functions call list will jump to the stepper at that point in time.

You can also use the args and ret buttons to open the values on the inspector.

Un-instrumenting functions

Since the functions tool shows all the functions sorted by how many time they have been called it is a good tool to see where most of your traces are coming from. If you want to reduce the number of traces, to make let’s say, search faster, you can right click on any function to un instrument it. You will have to re run the flow after.

5. Browser tool

The browser tool is pretty straight forward. It allows you to navigate your namespaces and vars, and also instrument/un-instrument them.

browser

See instrument with the browser for more info.

6. Taps tool

Use the taps tool to visualize your tap>.

taps

Everytime FlowStorm starts, it will add a tap, so whenever you tap> something it will show on the taps list.

Double click on any value to show it in the value inspector.

If the tapped value has also been recorded as an expression in Flows, you can right click on it and run Search value on Flows to move the debugger to that point in time.

Note
Search value on Flows

Be aware that if the code that taps your value is something like (tap> :a-key) you won’t be able to jump to it using this, because :a-key isn’t a value recorded by FlowStorm, while if the tapping code is like (tap> some-bind) or (tap> (+ 2 3)) or the tapping of any other expression you should be able to jump to it. So if you want to use this functionality as a "mark" so you can quickly jump to different parts of the recordings from the Taps tool, you can do it like (tap> (str :my-mark))

A #tap tag will also be available, which will tap and return so you can use it like (+ 1 2 #tap (* 3 4)) Use the clear button to clear the list.

There is also #tap-stack-trace. It will tap the current stack trace.

7. Docs tool

Generate projects functions documentation by sampling their executions.

7.1. Generating docs

Let’s say we want to generate documentation for datascript(https://github.com/tonsky/datascript/).

First we clone the repo. Then we can generate it by calling flow-storm.api/cli-doc.

For convenience we are going to create a script document.sh like this :

#!/bin/bash

clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-inst {:mvn/version "RELEASE"}}}' \
    -X:test flow-storm.api/cli-doc \
    :result-name '"datascript-flow-docs-1.4.0"' \
    :print-unsampled? true \
    :instrument-ns '#{"datascript"}' \
    :fn-symb 'datascript.test/test-clj' \
    :fn-args '[]' \
    :examples-pprint? true \
    :examples-print-length 2 \
    :examples-print-level 3

The idea behind flow-storm.api/cli-doc is to act as a trampoline, so it will instrument our code base as specified by :instrument-ns then call whatever function provided by :fn-symb and :fn-args.

For this case we are going to instrument every namespace that starts with "datascript" and then run datascript.test/test-clj without arguments.

For the rest of the options check flow-storm.api/cli-doc doc string.

It will output 3 useful things :

  • datascript-flow-docs-1.4.0.jar containing just a sample.edn file with all the data

  • the coverage percentage (how many fns were sampled over the instrumented ones)

  • unsampled fns, which are all the functions that were instrumented but the test never called

So if you are running your tests, as a bonus you will get your test "coverage" and a list of functions your tests aren’t exercising, you should see something like this after it finishes :

flow docs cli

7.2. Publishing docs

Given the docs are already in jar format you can publish them to your local repo or any maven repo (like Clojars) with the usual mvn utilities.

7.3. Consuming docs

FlowStorm debugger provides a way of visualizing whatever docs you have on your classpath.

For this you can add the docs and FlowStorm to your classpaths as usual, like :

clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"} dsdocs/dsdocs {:local/root "/home/user/datascript/datascript-flow-docs-1.4.0.jar"}}}'

or if you want to use the documentation I already generated and uploaded to my clojars group try :

clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"} com.github.flow-storm/datascript-flow-docs {:mvn/version "1.4.0"}}}'

and now we can run the debugger :

(require '[flow-storm.api :as fs-api])

(fs-api/local-connect)

The documentation will be available under the Docs tool.

You can search and click over all the functions you have loaded from all your imported docs to see the details.

Currently it shows fns meta, arguments, returns, and call examples.

flow docs browser

7.4. TIPS

If you are using the emacs integration you can do C-c C-f d (flow-storm-show-current-var-doc) to show the current function documentation in the debugger.

8. Timeline tool

You can use this tool to record, display and navigate a total order of your recordings in a timeline, in the order they executed. This can be used, for example, to visualize how multiple threads expressions interleave, which is sometimes useful to debug race conditions.

By default recording this total ordering is disabled, but you can enable it just by clicking the Enable checkbox at the top, like this :

timeline enable

Once it is enable FlowStorm will record everything as usual but also record the total order for your function calls and expressions, which you can then retrieve and update by clicking on the refresh button at the top.

As an example, let’s say you record this code :

(pmap (fn my-sum [i] (+ i i)) (range 4))

after hitting refresh you should see something like this :

timeline

As you can see the timeline tool displays a linear representation of your expressions. Times flows from top to bottom and each thread gets assigned a different color. Every time a function is called or returns you will see it under the Function column, and for each expression executed you will see a row with its Expression and Value.

Double clicking any row will take you to the Flows tool code at that point in time.

Note
Big recordings timeline

Rendering the timeline needs some processing to render each sub-form and print each value so be aware it could be slow if you try it on big recordings.

9. Printer tool

FlowStorm has a lot of functionality to replace printing to the console as a debugging method since most of the time it is pretty inefficient. Nonetheless, some times adding a bunch of print lines to specific places in your code base is a very powerful way of understanding your execution.

For this cases FlowStorm has the Printer tool, which allows you to define, manage and visualize print points, without the need of re running your code again. It will work on your recordings as everything else.

You can add and re run print points over your recordings as many times as you need. To add a print point, just right click on any recorded expression. It will ask you for an optional message, which you can use to identify these particular prints in the output console.

printer add

After you add them, the Print tool will show all your prints. You can use the panel at the top to manage them. Before printing anything you need to select the thread you would like to run the prints on. After selecting it, just click the refresh button and everything will be re-printed.

printer

You can tweak your prints at any time, like changing the print-length, print-level, message or just temporarily disable any of them. When you are ok re-setting you prints, just click refresh and they will print again.

Double clicking on any printed line will jump to the Flows code tab, with the debugger pointed to the expression that generated the print.

Note
Print messages

You can use %s in your print messages to place the printed representation of your value. If you provide a message without %s it will be added after a space at the end.

10. Value inspector

Use the value inspector to explore any data.

value inspector

Use it to lazily and recursively navigate your data. It will render collections with links you can use to dig deeper into the data.

The top bar provides a way of navigating back.

  • Use the def button to define the current value for the repl.

  • Use the tap button to tap the current value.

  • When inspecting any value from an execution flow, two more buttons will appear. They allow you to search forward and backwards in the execution the current selected value.

Note
Datafy

Value inspector uses clojure.datafy/datafy under the hood, so you can explore your objects also.

11. Thread breakpoints

thread breaks

FlowStorm is a tracing debugger, which means it can record what is happening without the need of stopping your programs execution. This is all fine but doesn’t cover every possible situation. There are cases where recording everything is impractical, like in a game loop, since it will consume a lot of heap and you are probably not interested in all those frames details.

For situations like the previous one, FlowStorm has the ability to set thread breakpoints, which means to define points in the execution of your program where you want your threads to wait. While the threads are waiting you can explore what happened so far. If the recorded information isn’t enough you can click the play buttons next to each blocked thread to tell it to continue until it hit a breakpoint again, or right click on any of the blocked threads and select Unblock all threads to unlock all of them. Then you can analyse the new recordings. Once you are done, you can choose to uninstrument your functions, or maybe easier to just pause recording using the pause button in the main toolbar. Then you can remove the breakpoints and un-block every thread.

You can define thread breakpoints in two ways :

  • Using the browser (like in the image below), you can navigate to any function and click on the Break button. This will block the calling thread every time the selected function gets called.

  • Or you can also install a break by calling (flow-storm.api/break-at 'my-proj.core/some-fn)

browser breakpoints
Note
Conditional threads breakpoints

The break-at fn accepts a second argument where you can provide a predicate that will be called with the same arguments of the function you are breaking. It will only break when the predicate returns true. If you don’t provide a predicate it will default to (constantly true)

You can remove breakpoints by :

  • Clicking on the browser instrumentation list delete buttons

  • Calling flow-storm.api/remove-break to remove a single breakpoint

  • Calling flow-storm.api/clear-breaks to remove all breakpoints

12. Limiting recording

When recording an application execution (specially when using ClojureStorm or ClojureScriptStorm) is common to find some high frequency functions adding a lot of noise to your recordings. For example a mouse-move event processing will generate a lot of recordings while you use your app.

Most of the time, having the recording paused and just enabling it right before executing the action you are interested in is enough, but when it isn’t, you can try functions call limiting.

There are a couple of ways to limit your functions calls by thread.

The first one is by adding the flowstorm.threadFnCallLimits JVM prop.

For example, you can add "-Dflowstorm.threadFnCallLimits=org.my-app/fn1:2,org.my-app/fn2:4" so every time the system starts, limits will be set for org.my-app/fn1 and org.my-app/fn2. The number next to them is the limit. When a function reaches the limit FlowStorm will stop recording calls to it and all the functions down its callstack.

You can also modify the limits from your repl, by calling flow-storm.runtime.indexes.api/[add-fn-call-limit|rm-fn-call-limit|get-fn-call-limits]. In ClojureScript you need to call them via your cljs repl.

All limits are per thread, so when a thread recording is created it will start with the current defined counters, and each time a function gets called the counter will decrement. When it reaches zero the function and all functions calls under it will stop being recorded.

When you clear your threads you are also clearing its limit counters, so next time you record something new counters will be initialized from your global limits definitions.

13. The tool bar

The toolbar provides quick access to some general commands :

toolbar

From left to right :

  • Clean all. Will clean all flows, taps and every value the debugger is retaining.

  • Cancel current running task. If the debugger is taking too long with something and you want to cancel it use this button.

  • Start/Stop recording. You can keep code instrumented but keep your heap from growing by stopping recording when you are not debugging.

  • Unblock all breakpoint blocked threads if any.

  • Open the bookmarks window.

  • Quick jump. Use it for quickly jumping to the first recording of a function. Will autocomplete the first 25 matches.

14. Debug cmd line programs (clj -X, clj -m, etc)

If you run any Clojure programs from the command line, by using clj -X …​, clj -m …​ etc, you can use flow-storm.api/cli-run as a trampoline, to start a debugger, instrument everything you are interested in an then run you original command.

As an example, let’s say you are compiling ClojureScript code like this :

clj -Sdeps '{:deps {org.clojure/clojurescript {:mvn/version "1.11.57"}}}' \
    -M -m cljs.main -t nodejs ./org/foo/myscript.cljs

you can then run and debug the execution of the same command like this :

clj -Sforce -Sdeps '{:deps {org.clojure/clojurescript {:mvn/version "1.11.57"} com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"} com.github.flow-storm/flow-storm-inst {:mvn/version "RELEASE"}}}' \
	-X flow-storm.api/cli-run :instrument-ns '#{"cljs."}'           \
                              :profile ':light'                     \
                              :require-before '#{"cljs.repl.node"}' \
							  :excluding-ns '#{"cljs.vendor.cognitect.transit"}' \
                              :fn-symb 'cljs.main/-main'            \
                              :fn-args '["-t" "nodejs" "./org/foo/myscript.cljs"]';

15. Programmable debugging

FlowStorm gives you full access to its internal indexes from the repl in Clojure and ClojureScript. These allows you to explore your recordings using Clojure and write small programs to analyze them if what’s provided by the GUI is not enough.

Most of what is documented here is also documented in the flow-storm.runtime.indexes.api namespace docstring, which you can retrieve by evaluating (doc flow-storm.runtime.indexes.api). In fact, this is the only namespace you need to require on your repl in order to work with your recordings.

Let’s say you have recorded some execution and now you want to work with the recordings from the repl.

So first we require the api ns as ia.

(require '[flow-storm.runtime.indexes.api :as ia])

Now from the UI, you can get the thread-id of your recordings (the number next to the thread name) which you will need for accessing them from the repl.

15.1. Timelines

Let’s say you want to explore recordings on thread 32. You can retrieve its timeline by calling ia/get-timeline like this :

(def timeline (ia/get-timeline 32))

Once you have the timeline you can start exploring it.

The timeline implements many of the Clojure basic interfaces, so you can :

user> (count timeline)
798

user> (take 3 timeline)
; (#flow-storm/fn-call-trace [Idx: 0 org.my-app/run-server]
;  #flow-storm/fn-call-trace [Idx: 1 org.my-app/read-config]
;  #flow-storm/fn-call-trace [Idx: 2 org.my-app/check-config])

user> (get timeline 0)
; #flow-storm/fn-call-trace [Idx: 0 org.my-app/run-server]

The easiest way to take a look at a thread timeline is with some code like this :

(->> timeline
     (take 3)
     (map ia/as-immutable))

; ({:type :fn-call,
;   :fn-ns "org.my-app",
;   :fn-name "run-server",
;   :ret-idx 797,
;   :fn-call-idx 0,
;   :parent-indx nil,
;   :fn-args [],
;   :form-id -798068730,
;   :idx 0}
;  ...
;  ...)

In most cases converting all entries into maps with ia/as-immutable is enough, but if you want a little bit more performance you can access entries information without creating a immutable map first.

Timelines entries are of 4 different kinds: FnCallTrace, FnReturnTrace, FnUnwindTrace and ExprTrace.

You can access their data by using the following functions depending on the entry :

All kinds :

  • as-immutable

  • entry-idx

  • fn-call-idx

ExprTrace, FnReturnTrace and FnUnwindTrace :

  • get-coord-vec

ExprTrace, FnReturnTrace :

  • get-expr-val

FnUnwindTrace :

  • get-throwable

FnCallTrace :

  • get-fn-name

  • get-fn-ns

  • get-fn-args

  • get-fn-parent-idx

  • get-fn-ret-idx

  • get-fn-bindings

You can also access the timeline as a tree by calling :

  • callstack-root-node

  • callstack-node-childs

  • callstack-node-frame-data

Take a look at their docstrings for more info.

15.2. Forms

You can retrieve forms by form id with get-form and then use get-sub-form-at-coord and a coordinate.

Here is a little example :

;; retrieve some expression entry into expr
user> (def expr (-> timeline
                    (get 3)
                    ia/as-immutable))

user> expr
{:type :expr, :coord [2 2 1], :result 4, :fn-call-idx 2, :idx 3}

;; retrieve the fn-call entry for our expr
user> (def fn-call (-> timeline
                       (get (:fn-call-idx expr))
                       ia/as-immutable))
user> fn-call
{:type :fn-call,
 :fn-ns "dev-tester"
 :fn-name "other-function",
 :form-id 1451539897,
 ...}

;; grab it's form
user> (def form (-> fn-call
                    :form-id
                    ia/get-form
                    :form/form))
user> form
(def other-function (fn [a b] (+ a b 10)))

;; lets look at the sub-form from form at our expr coordinate
user> (ia/get-sub-form-at-coord form (:coord expr))
a

15.3. Multi-thread timeline

If you have recorded a multi-thread timeline, you can retrieve it with total-order-timeline like this :

(def mt-timeline (ia/total-order-timeline))

which you can then iterate using normal Clojure functions (map, filter, reduce, get, etc).

The easiest way to explore it is again with some code like this :

user> (->> mt-timeline
           (take 3)
           (map ia/as-immutable))

({:thread-id 32,
  :type :fn-call,
  :fn-call-idx 0,
  :fn-ns "org.my-app",
  :fn-name "run",
  :fn-args [],
  :ret-idx 797,
  :parent-indx nil,
  :form-id -798068730,
  :idx 0}
  ...
  ...)

Notice that each of these entries contains a flow-id and thread-id also.

15.4. Other utilities

There are other utitities in the api ns that could be useful, some of the most interesting ones :

  • find-expr-entry useful for searching expressions and return values with different criteria.

  • find-fn-call-entry useful for searching functions calls with different criteria.

  • stack-for-frame

  • fn-call-stats

Take a look at their docstrings for more info.

16. Dealing with mutable values

FlowStorm will retain all values pointers when code executes so you can analyze them later. This works great with immutable values but when your code uses mutable values like this :

#rtrace
(let [a (java.util.ArrayList.)]
  (count a)
  (.add a "hello")
  (count a)
  (.add a "world")
  (.add a "!"))

then every time you step over a it will contain the last value ["hello" "world" "!"].

You can fix this situation by extending the flow-storm.runtime.values/SnapshotP protocol like this :

(extend-protocol flow-storm.runtime.values/SnapshotP
  java.util.ArrayList
  (snapshot-value [a] (into [] a)))

to provide FlowStorm a way of creating a snapshot of the mutable value.

Note
ClojureStorm

If you are using ClojureStorm evaluate the previous defmethod in a ns that is not being instrumented to avoid an infinite recursion.

Be aware that this is tricky in multithreading situations, as always with mutable values.

Note
Atoms and derefable values

If the value implements clojure.lang.IDeref (or cljs.core.IDeref in Cljs) a snapshot will be created automatically by derefing the object, so no need to implement flow-storm.runtime.values/snapshot-value

17. Dealing with too many traces

If you are tracing some code that ends up in a infinite loop the debugger will probably choke on too many traces, making everything slow and where your only option is to restart it.

For preventing this FlowStorm provides a couple of tools :

If you are using vanilla FlowStorm there is :thread-trace-limit, you can use it like this :

#rtrace ^{:thread-trace-limit 200} ;; set our fuse at 200
(loop [i 0]
  (if (> i 100)
    42 ;; we will never reach here
    (recur i)))

the infinite loop will be cut after 200 iterations by a thread-trace-limit exceeded exception, and you will have the traces on the debugger to figure out what went wrong.

18. Controlling instrumentation

If you are using ClojureStorm or ClojureScriptStorm it is important to learn how to control what gets instrumented and how to uninstrument things.

The first important thing is to setup your instrumentation correctly via JVM properties :

On ClojureStorm :

-Dclojure.storm.instrumentOnlyPrefixes=my-app,my-lib
-Dclojure.storm.instrumentSkipPrefixes=my-app.too-heavy,my-lib.uninteresting
-Dclojure.storm.instrumentSkipRegex=.*test.*

On ClojureScriptStorm :

-Dcljs.storm.instrumentOnlyPrefixes=my-app,my-lib
-Dcljs.storm.instrumentSkipPrefixes=my-app.too-heavy,my-lib.uninteresting

Apart from instrumentOnlyPrefixes which you probably already know, there is instrumentSkipPrefixes which also accepts a comma separated list of namespaces prefixes to skip, and instrumentSkipRegex with accepts a regex for namespaces to skip. All these together allows you to instrument you whole app but some undesired namespaces.

The next important thing is to be able to enable/disable instrumentation and add/remove prefixes without restarting the repl. This is slightly different between Clojure and ClojureScript.

18.1. Controlling instrumentation in ClojureStorm

You can disable/enable instrumentation by evaluating the keys :noinst and :inst on your repl. Disabling/enabling instrumentation will not reload your code, so if you need to re-evaluate the forms you are interested in after enabling/disabling instrumentation.

For adding/removing and checking your prefixes without restarting your repl you can call :

(clojure.storm.Emitter/addInstrumentationOnlyPrefix "dev")
(clojure.storm.Emitter/removeInstrumentationOnlyPrefix "dev")

(clojure.storm.Emitter/addInstrumentationSkipPrefix "dev")
(clojure.storm.Emitter/removeInstrumentationSkipPrefix "dev")

(clojure.storm.Emitter/getInstrumentationOnlyPrefixes)
(clojure.storm.Emitter/getInstrumentationSkipPrefixes)

You can also check your current prefixes by evaluating the :help keyword at the repl.

18.2. Controlling instrumentation in ClojureScriptStorm

You can add,remove and check your prefixes and also enable/disable instrumentation in ClojureScript with your Clojure repl (not the Cljs one) by calling :

(cljs.storm.api/add-instr-only-prefix "dev")
(cljs.storm.api/rm-instr-only-prefix "dev")

(cljs.storm.api/add-instr-skip-prefix "dev")
(cljs.storm.api/rm-instr-skip-prefix "dev")

(cljs.storm.api/get-instr-prefixes)
(cljs.storm.api/set-instrumentation true)

The [add|rm]-instr-prefix set of functions have an extra parameter called touch-path. This is kind of a hacky way of going around shadow-cljs cache.

Let’s say you added/removed a prefix and you want to fire a shadow-cljs recompile of your files so the changes take effect. Shadow-cljs will not recompile files that haven’t been modified since the last compilation. You can use the touch-path parameter for this. If you provide src for example, it will walk the directory structure down and touch every file, which should fire a recompilation.

19. Styling and theming

All functions that start the debugger ui (flow-storm.api/local-connect, flow-storm.debugger.main/start-debugger) accept a map with the :styles, :title and :theme keywords. If :styles points to a css file it will be used to overwrite the default styles, in case you want to change colors, make your fonts bigger, etc. :theme could be one of :auto (default), :light, :dark. Title can be used to distinguish between multiple debugger instances.

Like this :

user> (local-connect {:styles "~/.flow-storm/big-fonts.css", :theme :dark, :title "FlowStormMainDebugger"})

If you are using ClojureStorm you can also provide them with :

-Dflowstorm.title=FlowStormMainDebugger
-Dflowstorm.theme=dark
-Dflowstorm.styles=~/.flow-storm/big-fonts.css

20. Key bindings

20.1. General

  • Ctrl-g Cancel any long running task (only search supported yet)

  • Ctrl-l Clean all debugger state

  • Ctrl-d Toggle debug-mode. Will log useful debugging information to the console

  • Ctrl-u Unblock all breakpoint blocked threads if any

  • Ctrl-t Rotate themes

  • Ctrl-plus Increment font size

  • Ctrl-minus Decrement font size

  • F "Select the Flows tool"

  • B "Select the Browser tool"

  • T "Select the Taps tool"

  • D "Select the Docs tool"

20.2. Flows

  • Esc Select the funnel default flow threads list

  • 0 Select flow id 0 threads list

  • t Select the tree tool (needs to be inside a thread)

  • c Select the code tool (needs to be inside a thread)

  • f Select the functions tool (needs to be inside a thread)

  • P Step prev over. Go to previous step on the same frame

  • p Step prev

  • n Step next

  • N Step next over. Go to next step on the same frame

  • ^ Step out

  • < Step first

  • > Step last

  • Ctrl-f Copy current function symbol

  • Ctrl-Shift-f Copy current function call form

  • Ctrl-z Undo navigation

  • Ctrl-r Redo navigation

21. Debugging react native applications

Debugging ClojureScript react native application needs a combination of ClojureScript and remote debugging.

Assuming you are using shadow-cljs, have added the flow-storm-inst dependency, and that it started a nrepl server on port 9000, you can start a debugger and connect to it by running :

clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"}}}' -X flow-storm.debugger.main/start-debugger :port 9000 :repl-type :shadow :build-id :your-app-build-id :debugger-host '"YOUR_DEV_MACHINE_IP"'

You also need to make it possible for the device to connect back to the debugger on port 7722. You can accomplish this by running :

adb reverse tcp:7722 tcp:7722

Also remember that you need to have installed the websocket npm library. You can do this like :

npm install websocket --save

22. Working on Windows with WSL2

For those using WSL2 on Windows. You’ll need to set up an X-Server on Windows, make sure you drill a hole in the firewall for the port, and then specify the display for the WSL process.

Then everything should work. The steps are:

  1. Install VcXsrv on Windows.

  2. Run the XLaunch app on Windows, Choose Multiple Windows, Display Number 0, Start no client, Check all settings on the Extra Settings screen (specifically "Disable access control"

  3. In the WSL2 terminal, run ip addr | grep eth0 to determine the ip of the xserver

  4. On Windows, go to Firewall and network protection, select Advanced Settings, and add a new Inbound Rule in the Windows Defender window that pops up.

  5. For the rule, select Port, then TCP, specific port 6000, then click next twice, and finally name the rule something appropriate, like "XServer rule".

  6. Now find the rule you just created, right click, select Properties, then the Scope tab, and enter the IP address you found at step 3 with an appropriate range in order to allow the port through for the WSL2 subsystem.

  7. Now find the ip address of your windows machine by typing ipconfig in a windows terminal

  8. Now, in the WSL2 terminal, type export DISPLAY=IP_ADDERSS_FOUND_ON_STEP_7:0.0

  9. Also in the WSL2 terminal, type export LIBGL_ALWAYS_INDIRECT=1

  10. Now you can start clojure with the command clj -Sforce -Sdeps '{:deps {com.github.flow-storm/flow-storm-dbg {:mvn/version "RELEASE"} com.github.flow-storm/flow-storm-inst {:mvn/version "RELEASE"}}}'

  11. That should get you into the REPL, where you should type (require '[flow-storm.api :as fs-api])

  12. Finally, type (fs-api/local-connect) and you’re done!

23. Editors/IDEs integration

23.1. Emacs

Checkout Cider Storm an Emacs Cider front-end with support for Clojure and ClojureScript.

24. Tutorials and demos

25. Troubleshooting

25.1. Instrument verbose

By default functions like flow-storm.api/instrument-namespaces-clj will not print warnings on the console. You can log extra information by providing :verbose? true to the options map.

25.2. Run verbose

#rtrace form just expands to (flow-storm.api/runi {} form).

The first argument is a options map, which accepts :verbose? :true, in which case FlowStorm will print to the console tracing stats.

Is useful when you have instrumented a big application and wish to see tracing progress while running.

25.3. Run with JDK 11

FlowStorm UI requires JDK >= 17. If you can’t upgrade your JDK you can still use it by downgrading JavaFx.

If that is the case add these dependencies to your alias :

org.openjfx/javafx-controls {:mvn/version "19.0.2"}
org.openjfx/javafx-base     {:mvn/version "19.0.2"}
org.openjfx/javafx-graphics {:mvn/version "19.0.2"}
org.openjfx/javafx-swing    {:mvn/version "19.0.2"}

26. Internals, diagrams and documentation

For people interested in enhancing, troubleshooting, fixing or just learning about FlowStorm internals take a look at here :

Some useful diagrams :