FlowStorm is a tracing debugger for Clojure and ClojureScript.

intro screenshot

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 Clojure compiler at dev time 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 lein profiles.

1.1.1. 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.

You can start a repl with FlowStorm like this :

;; on Linux and OSX
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"]}}}' -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"""]}}}' -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 evaluate the :dbg keyword to bring up the UI and then click on Help→Tutorial on the menu for a tour of the basics.

After the tutorial you would want to add and configure it for your projects, or create a global config 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 {;; for disabling the official compiler
                 :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=YOUR_INSTRUMENTATION_STRING"]}}}

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

my-project.,lib1.,lib2.core

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

All this can be changed after without restarting your repl from FlowStorm browser.

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"] ]
                   ;; for disabling the official compiler
                   :exclusions [org.clojure/clojure]
                   :jvm-opts ["-Dclojure.storm.instrumentEnable=true"
                              "-Dclojure.storm.instrumentOnlyPrefixes=YOUR_INSTRUMENTATION_STRING"]}}
  :main foo.core)

with YOUR_INSTRUMENTATION_STRING as described above.

A global configuration

By using the -Dclojure.storm.instrumentAutoPrefixes=true JVM property it is possible to create a global deps.edn alias or lein profile so you don’t have to modify each of your projects in order to use FlowStorm.

This is convenient when you are working with multiple projects or when teams don’t want to add personal tooling to project files.

For example you can modify your ~/.clojure/deps.edn like this :

{:aliases {...
           :1.12-storm
           {:classpath-overrides {org.clojure/clojure nil}
            :extra-deps {com.github.flow-storm/clojure {:mvn/version "1.12.0-alpha9_1"}
                         com.github.flow-storm/flow-storm-dbg {:mvn/version "3.13.1"}}
            :jvm-opts ["-Dflowstorm.startRecording=false"
                       "-Dclojure.storm.instrumentEnable=true"
                       "-Dclojure.storm.instrumentAutoPrefixes=true"
                       ;; with whatever editor you have
                       "-Dflowstorm.jarEditorCommand=emacsclient -n +<<LINE>>:0 <<JAR>>/<<FILE>>"
                       "-Dflowstorm.fileEditorCommand=emacsclient -n +<<LINE>>:0 <<FILE>>"
                       ]}}}

With this configuration, whenever you add a :1.12-storm to the starting command of any deps.edn project you will have your debugging environment and every namespace that shows in folders outside jars instrumented. Those will also include :local/root references!

As soon as you start the repl you can run :help to see what instrumentation prefixes where automatically added, and use the instructions there to finetune them.

If you are using leiningen instead, you can modify your ~/.lein/profiles.clj like this :

{:flowstorm
 {:dependencies [[com.github.flow-storm/clojure "1.12.0-alpha9_1"]
                 [com.github.flow-storm/flow-storm-dbg "3.13.1"]]
  :exclusions [org.clojure/clojure]
  :jvm-opts ["-Dflowstorm.startRecording=false"
             "-Dclojure.storm.instrumentEnable=true"
             "-Dclojure.storm.instrumentAutoPrefixes=true"
             ;; with whatever editor you have
             "-Dflowstorm.jarEditorCommand=emacsclient -n +<<LINE>>:0 <<JAR>>/<<FILE>>"
             "-Dflowstorm.fileEditorCommand=emacsclient -n +<<LINE>>:0 <<FILE>>"]}}
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.

1.1.2. Vanilla FlowStorm

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

;; on Linux and OSX
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. Click on the recording button to leave the debugger in recording mode and the let’s debug something:

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), but now you will also have the debugger UI showing your recordings.

From here you probably want to check out the Flows tool which contains a lot of information about exploring your recordings.

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.

1.2.1. 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)

1.2.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.

1.2.3. 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

1.2.4. 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-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-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. The tool bar

The toolbar as well as the menu provides quick access to some general commands.

toolbar

From left to right :

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

  • Discard all recordings so far.

  • Cancel current running task. Whenever you a running a task that can take some time, this button will be red, and you can use it to cancel the task.

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

  • This will not always be visible but if your recordings contains any exceptions they will be shown here.

3. Flows tool

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

First of all, what are Flows?

A Flow is a "execution flow" recording unit. The only purpose of a flow is to group recording activity. This grouping allows us for example to run some code and record it under "flow-0", then modify our code, run it again, and record this second run (or flow) under "flow-1". Now we can access both recordings separately.

You can use the Rec on combo-box to select under what flow new recordings are going to be stored.

Whenever there is something recorded for a flow, a new tab with the flow name will appear.

Execution inside a flow will be grouped by threads. So the first thing you will see on a flow is a menu of threads we have recordings for so far. This threads will be referred sometimes as timelines, since they are a sequence of recorded execution steps.

Let’s say for example we have selected to record under flow-1 and run some multi threaded code.

We are going to see something like this :

thread outer form

There is a lot going on in the screenshot above, but the most important are :

  • we have configured FlowStorm to record under flow-1

  • we have recorded stuff under flow-1 and flow-0

  • we are currently looking at flow-1, we have opened to explore thread 18 and we are exploring it in the code stepper

thread inner form

This second image shows us exploring another thread on flow-1.

There are currently 3 tools to explore the recorded timelines, which we are going to describe next.

3.1. Code tool

code tool tab

The code tool is the first 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.

3.1.1. 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.

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

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

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.

3.1.2. 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.

3.1.3. Searching

You can use the search tool to search over all your recorded values. Find the search tool on the toolbar (the magnifier glass) or under View → Search.

search

The first row let us constrain our search to a specific Flow or Thread and also let us select one of two types of searches.

Search by pr-str let us provide a query string, a print-level and a print length and it will walk the specified timelines doing a pr-str over each recorded value and collect every entry where our query string is found inside the printed value. With bigger print-level/print-length you search deeper into values but it is also slower.

Search by predicate let us provide a predicate that will be applied over each recorded values to collect them. You can put whatever Clojure code you like here like (fn [v] (map? v)) to search over all recorded maps.

After configuring your search, click on the magnifier glass on the first row (next to the search type selection) to run the search. You should start seeing results as the match and you can always cancel the search by the normal means.

Double clicking on any of the search results will take you to that point in time.

3.1.4. Loops

Whenever you click a highlighted form that has been executed multiple times inside the same function call (any kind of loop), instead of immediately jumping into it, FlowStorm will popup a menu, like in the picture below :

loops

This is the loops navigation menu. It allows you to quickly move around interesting iterations of the loop.

The menu will display slightly different options depending on you current position. The [FIRST] …​ and [LAST] …​ entries will always show, which allows you to quickly jump to the first and last iteration of the loop.

If you are currently before the loop, clicking into any expression inside the loop will show the first 20 values for the clicked expression.

If instead you are currently in a expression after the loop, clicking back to an expression inside the loop, will show the last 20 values for the clicked expression.

Now if you are currently stepping inside the loop, clicking any other expression inside it will show you 10 values before and 10 values after of your current position.

Clicking on any of this entries will take you to that position in time.

If this is not enough, and you want to see all the values taken by some expression along the loop, you can always use the printer tool.

3.1.5. 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.

3.1.6. 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

3.1.7. 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.

3.1.8. 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

3.1.9. Goto to file:line

Clicking on the Actions→Goto file:line menu allows you to search and jump to the first recording of a expression with a file and line, given that one exists.

It will ask you for a file and line in the format of <class-path-file-path>:<line>.

If you have a file like src/org/my_app/core.clj and you are interested in expressions evaluating on like 42 you should search like org/my_app/core.clj:42.

3.2. 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 in the code tool, next to your stepping controls. It will ask you the bookmark description.

You can find you bookmarks on the top menu View → Bookmarks.

bookmarks

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

3.3. Call Stack tree tool

The call stack tree tool is the second 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

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.

3.4. 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 calls

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.

3.4.1. 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.

4. Browser tool

The browser tool is pretty straight forward. It allows you to navigate your namespaces and vars, and provides ways of managing what gets instrumented.

browser

5. 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.

6. 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.

There is also a Only functions? checkbox at the top that will retrieve only function calls and can be used to visualize the threads interleaving at a higher level.

7. 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.

8. 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.

9. 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. 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

10. 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.

10.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.

10.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

10.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.

10.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.

11. 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

11.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

12. Dealing with too many traces

When recording an application’s execution (specially when using ClojureStorm or ClojureScriptStorm) it could happen that your process starts running out of heap. This section documents some tools FlowStorm provides to deal with this situations.

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, here are some other options.

A common situation is to see some high frequency functions adding a lot of noise to your recordings. For example a MouseMove event processing will generate a lot of recordings while you use your app. There are a couple of ways to limit your functions calls by thread. You can identify this kind of functions with the functions tool.

One tool you can use in this situations is 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 api.

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.

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

For preventing this, FlowStorm provides a fuse/breaker, called thread trace limit. It is off by default but you can enable it from the Config menu.

thread trace limit menu
thread trace limit dialog

Let’s say you added a limit of 1000. If you now run any code that generates more than a 1000 traces FlowStorm will only record those first 1000 traces and stop recording. Your code will continue execution as normal, which you can break using your normal editor breaking commands if its an infinite loop, but now you have recordings to look at what is going on.

You can set a limit of 0 to disable it again.

This is not only useful for infinite loops. It can be used to limit your recordings if you think you can blow up your heap.

13. 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

14. 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. You can configure what gets instrumented automatically on startup via JVM properties but also change this while your repl is running without the need to restart it.

14.1. Setup startup instrumentation

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.

14.2. Modifying instrumentation with the Browser

You can use the Browser tool to check and change on the fly the prefixes you configured in the previous section.

browser storm instrumentation 1

On the bottom pane (instrumentations) you will see your current instrumentation configution (if any). Here we can see that everything under ring and compojure will get instrumented but anything under compojure.middleware will be skipped.

You can remove entries using the del buttons or temporarly disable/enable them using the Enable all checkbox.

browser storm instrumentation 2

You can use the Add menu in the picure above to add instrumentation prefixes.

Note
Instrumentation

Just changing the prefixes will not make your currently loaded code [un]instrumented. You still need to recompile the code. You can recompile it as usual with your editor commands, or by using the Reload namespace option in the context menu on any namespace, which will execute a (require 'the-selected.namespace :reload) expression for you. You can bulk reload by selecting many namespace on the browser or by using Clojure’s reload-all functionality by running on your repl (require 'my-core-namespace :reload-all).

14.3. Instrumentation in Vanilla FlowStorm

Note
ClojureStorm

Instructions here only apply to vanilla FlowStorm. If you are using ClojureStorm or ClojureScriptStorm (recommended) 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.

14.3.1. 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)).

14.3.2. 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 +))

14.3.3. 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

14.3.4. 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))))

14.3.5. 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.

14.3.6. 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.

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.

15. 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

16. Key bindings

16.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"

16.2. Flows

  • 0-9 Open focus flow-N threads menu, N being the pressed key

  • 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

17. 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

18. 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!

19. Opening forms in editors

You can add this two jvm options to tell FlowStorm how to open forms in files and inside jars :

  • flowstorm.jarEditorCommand : a command with optional <<JAR>>, <<FILE>> and <<LINE>> placeholders

  • flowstorm.fileEditorCommand : a command with optional <<FILE>> and <<LINE>> placeholders

If you define those, clicking on your forms namespaces link in the code tool should run the provided commands. On expressions sub-forms that contains line meta you should also be able to right click and select "Open in editor" which should open the file at that specific line (useful for long forms).

Here are some known setups for most common editors :

19.1. Emacs

"-Dflowstorm.jarEditorCommand=emacsclient -n +<<LINE>>:0 <<JAR>>/<<FILE>>"
"-Dflowstorm.fileEditorCommand=emacsclient -n +<<LINE>>:0 <<FILE>>"

19.2. VSCode

"-Dflowstorm.fileEditorCommand=code --goto <<FILE>>:<<LINE>>"

19.3. IntelliJ

"-Dflowstorm.fileEditorCommand=idea --line <<LINE>> <<FILE>>"

19.4. Vim

"-Dflowstorm.fileEditorCommand=vim +<<LINE>> <<FILE>>"

20. Editors/IDEs integration

20.1. Emacs

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

21. Tutorials and demos

22. Troubleshooting

22.1. 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"}

23. Internals, diagrams and documentation