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"
                       ;; change for your specific editor
                       "-Dflowstorm.fileEditorCommand=emacsclient -n +<<LINE>>:0 <<FILE>>"
                       "-Dflowstorm.jarEditorCommand=emacsclient --eval '(let ((b (cider-find-file \"jar:file:<<JAR>>!/<<FILE>>\"))) (with-current-buffer b (switch-to-buffer b) (goto-char (point-min)) (forward-line (1- <<LINE>>))))'"
                       ]}}}

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"
             ;; change for your specific editor
             "-Dflowstorm.fileEditorCommand=emacsclient -n +<<LINE>>:0 <<FILE>>"
             "-Dflowstorm.jarEditorCommand=emacsclient --eval '(let ((b (cider-find-file \"jar:file:<<JAR>>!/<<FILE>>\"))) (with-current-buffer b (switch-to-buffer b) (goto-char (point-min)) (forward-line (1- <<LINE>>))))'"
             ]}}
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

Currently :

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

3. Flows tool

The Flows vertical tab contains a bunch of tools for recording and analyzing your programs executions.

First of all, what are Flows?

A Flow is an "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.

recording controls

When you first open FlowStorm UI you will see four things, from left to right :

  • Clear your recordings if any.

  • Start/Stop recording. You can keep your heap from growing by stopping recording when you don’t need it.

  • Start/Stop recording the multi-thread timeline. Check out the multi-thread timeline tool.

  • 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 :

multi flows 1

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

  • we have configured FlowStorm to record new executions under flow-1

  • we have recorded stuff under flow-1 and there are also some previous recordings under flow-0

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

  • Threads [4] indicates we have recorded activity in 4 threads, which we can access via this menu

Now for a different example :

multi flows 2

This second image shows us exploring the recordings of a thread with id 474, called pool-4-thread-4 on flow-0.

flows toolbar

The Flows tool also contains a toolbar that contains the Quick jump box. Use it for quickly opening the first recording of a function in the code stepper. Will autocomplete the first 25 matches.

In the screenshot above we see analyzing the recordings in the code stepper but there are many 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 first, back, next or last buttons will navigate the timeline using the selected power stepping tool in the dropdown.

There are currently six 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.

  • fn-call, allows you to provide a function to step to.

Note
Custom stepping

Custom power stepping is only supported in Clojure now.

3.1.3. Searching

search access

You can use the search tool to search over all your flow recorded expressions and then make the stepper jump to them. You can find the search tool under More tools → Search.

There are multiple ways of searching:

  • By pr-str

  • By data window current value

  • By predicate

Searching by pr-str
search pr str

This type of search will walk over the selected threads expressions, converting their values to strings with pr-str up to the selected level and depth and then checking if the resulting string contains your provided query string.

Searching by DataWindow value
search data window

Searching by data window value allows you to select any of the current data windows and will search for the current selected data window value over the selected threads expressions values using identity.

Searching by predicate
search pred

Searching by predicate allows you to provide a Clojure predicate which will be used over all selected threds expressions values.

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 a data window

  • 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 panels2

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.

The value panel showing the current expression in the code stepper is a little bit special since it also contains a data window tab which allows you to quickly navigate the value or give it custom visualizations.

value panels1
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. 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.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 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.3.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.

3.4. Multi-thread timeline

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

You enable/disable the multi-thread timeline recording using its button on the toolbar. Recording on the multi-thread timeline will make your program execution a little slower so it is recommended to have it paused unless you need it.

When you have something recorded on the multi-thread timeline you access the tool from the top right corner.

multi timeline access

As an example, let’s say you record the execution this function :

(defn run-parallel []
  (->> (range 4)
       (pmap (fn [i] (factorial i)))
       (reduce +)))

By opening the tool a window like this should pop up :

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 make your code stepper (on the main window) jump to the 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.

3.5. Printer

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, sometimes adding a bunch of print lines to specific places in your code is a very powerful way of understanding 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. 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.

printer add

It will ask you for a couple optional fields.

printer add box

The Message format is the "println text". A message to identify the print on the printer output. Here you can use any text, in which you can optionally use %s for the printed value, same as you would use it with format.

The Expression field can be use to apply a transfomer function over the value before printing it. Useful when you want to see a part of the value.

printer access

After you add them, you can access the Printers tool by navigating to More tools → Printers.

The threads selector allows you to select the thread the prints are going to run on. Leaving it blank will run prints over all threads recordings (checkout the notes for caveats). Clicking the refresh button will [re]run the printing again over the current recordings.

printer

You can tweak your prints at any time, like changing the print-length, print-level, message, transform-fn 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.

Important
Multi-thread prints order

If you select All threads, and have a multi-thread timeline recording, then the printer will use it and you can use prints to debug threads interleaving for example, but if you run your printers with All threads selected without a multi-thread timeline recording they will print sorted by thread and not in the order they happened.

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

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

outputs

The outputs tool can be used instead of your normal IDE/Editor panel to visualize your evaluations results, your taps outputs and your out and err streams writes (like printlns).

The advantages being :

  • Custom visualizations

  • Quick nested values navigations

  • Quick taps values navigation

  • Datafy nav navigation

  • Access to all previously tapped values

  • Access to the last 10 evaluated values (instead of just *1 and *2)

  • Ability to search tapped values in Flows

The taps visualization system works out of the box while the evals result and printing capture currently depends on you using nrepl and starting with the flow-storm middleware. Checkout the outputs setup section for instructions.

Note
ClojureScript support

Only the taps viewer is currently supported on ClojureScript. The last evaluations and the out and err streams capture aren’t supported yet.

5.1. Middleware setup

For using all the features in the Outputs tool you need to be using nrepl and start your repl with flow-storm.nrepl.middleware/wrap-flow-storm middleware.

If you use Cider for example you can add it to cider-jack-in-nrepl-middlewares via customizing the global value or by using .dir-locals.el.

5.2. Output data window

The top panel is a data window for displaying evaluations and taps. As soon as you evaluate or tap something it will be displayed here.

5.3. Last evals

The last evals pane gives you access to the last 10 evaluation results, same as *1 and *2.

Click on any value to display it on the top data window.

5.4. Taps

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

Click on any value to display it on the top data window.

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.

5.5. Out and Err streams

Everything written on out or err will be captured and displayed on the bottom panel. You can copy anything from this area with normal tools.

6. Data Windows

data window

Data Windows are a user extensible tool to visualize and explore your data. Their role is to support :

  • a way to navigate nested structures in a lazy way

  • visualize and navigate metadata

  • multiple visualizations for each value

  • lazy/infinite sequences navigation

  • a way to define the current sub-values so you can use them at the repl

  • a mechanism for realtime data visualization

  • clojure.datafy navigation out of the box

  • tools for the user to add custom visualizations on the fly

The next sections will explore each of them.

6.1. Data navigation

data window dig

You can navigate into any key or value by clicking on it.

Use the breadcrums at the top to navigate back.

6.2. Metadata navigation

data window meta

If any value contains metadata, it will be shown at the top. Clicking on it will make the data window navigate into it.

6.3. Multiple visualizers

data window multiple viz

You can change how to display your current value by using the visualizers selector dropdown at the top.

6.4. Sequences

data window seqable

The seqable visualizer allows you to navigate all kind of sequences (even infinite ones) by bringing more pages on demand.

Click on More to bring the next page in.

6.5. Defining values

You can always define a var for the current value being shown on any data window by clicking the def button. Clicking on it will raise a popup asking for a symbol name. If you don’t provide a fully qualified symbol it will define the var under user or cljs.user if you are in ClojureScript.

A quick way to use it is to provide a short name, let’s say foo, and then access it from your code like user/foo.

6.6. Realtime visualizations

data window realtime

DataWindows not only support displaying and navigating values, but also updating them in real time from your application.

From your program’s code you can always create a data window with :

(flow-storm.api/data-window-push-val :changing-long-dw-id 0 "a-long")

by providing a data window id, a value, and optionally the initial breadcrum label.

But you can also update it (given that the selected visualizer supports updating like :scope for numbers) with :

(flow-storm.api/data-window-val-update :changing-long-dw-id 0.5)

This data-window-val-update is pretty useful when called from loops or refs watches, specially paired with a custom visualizer.

6.7. Clojure datafy/nav

data window datafy nav

Data Windows support datafy nav out of the box. The data window will always be showing the result of clojure.datafy/datafy of a value. For maps or vectors where keys provide navigation it will automatically add a blue arrow next to the value.

Clicking on the value will just dig the data, while clicking on the blue arrow will navigate as with clojure.datafy/nav applied to that collection on that key.

6.8. Custom visualizers

An important aspect of Data Windows is to be able to provide custom visualizers on the fly.

Let’s say we have model a chess board as a set of maps which represent our pieces.

(def chess-board
  #{{:kind :king  :player :white :pos [0 5]}
    {:kind :rook  :player :white :pos [5 1]}
    {:kind :pawn  :player :white :pos [5 3]}
    {:kind :king  :player :black :pos [7 2]}
    {:kind :pawn  :player :black :pos [6 6]}
    {:kind :queen :player :black :pos [3 1]}})

(flow-storm.api/data-window-push-val :chess-board-dw chess-board "chess-board")

If we open a data window with data-window-push-val we are going to see something like this :

data window custom1

but we can do better, we can create a custom visualizer so we can see it like this :

data window custom2

Data visualization in FlowStorm is composed of two things:

  • a data aspect extractor, which runs on the runtime process, and will build data for the visualization part

  • a visualizer, which runs on the debugger process, and will render extracted data for a value using javafx

For a basic Clojure session everything will be running under the same process, but this is not the case for ClojureScript or remote Clojure.

First let’s require some namespaces :

(require '[flow-storm.api :as fsa])
(require '[flow-storm.debugger.ui.data-windows.visualizers :as viz])
(require '[flow-storm.runtime.values :as fs-values])

We can register a custom visualizer by calling register-visualizer.

(viz/register-visualizer
     {:id :my-viz
      :pred (fn [val] )
      :on-create (fn [val] {:fx/node :any-java-fx-node-that-renders-the-value
                            :more-ctx-data :anything})
      ;; OPTIONALLY
      :on-update (fn [val created-ctx-map {:keys [new-val]}] )
      :on-destroy (fn [created-ctx-map] )
      })

The important part there are :id, :pred, and :on-create.

The :id will be the one displayed on the visualizers dropdown, and re-registering a visualizer with the same id will replace the previous one.

:pred is a predicate on the data extracted from values, it should return true if this visualizer can handle the value.

And :on-create will be a function that receives this value and renders a java fx node.

Optionally you can provide :on-update and :on-destroy.

:on-update will receive values from the runtime via fsa/data-window-val-update. It will also get a handle to the original value (the one that created the DataWindow) and whatever map was returned by :on-create.

:on-destroy will be called everytime a visualizer gets removed, because you swapped your current visualizer or because you went back with breadcrums. It can be useful in case you need to clear resources created by :on-create.

:pred and :on-create will not recieve the original value but the extracted aspects of it after all registered extractors run.

You can check the data available to your visualizer for a value in a data window by calling :

(viz/data-window-current-val :chess-board-dw)

If the data already extracted from your value is not enough for your visualizer you can register another extractor.

6.8.1. Data aspect extraction

(fs-values/register-data-aspect-extractor
   {:id :chess-board
    :pred (fn [val] (and (set? val)
                         (let [{:keys [kind player pos]} (first val)]
                           (and kind player pos))))
    :extractor (fn [board] {:chess/board board})})

In this case we are going to register and extractor that will only run for vals which are sets and contains at least one element which is a map with :kind, :player and :pos. The extracted data will be the entire board.

All ids of extractors that applied for a value will be appended under ::fs-values/kinds of the value as you will see next.

6.8.2. Visualizers

Now we can register a visualizer that will show only on values which contains a :chess-board kind.

(import '[javafx.scene.layout GridPane])
(import '[javafx.scene.control Label])

(viz/register-visualizer
   {:id :chess-board
    ;; only be available if the chess-board data extractor run on this value
    :pred (fn [val] (contains? (::fs-values/kinds val) :chess-board))

    ;; use the chess/board info to render a board with java fx
    :on-create (fn [{:keys [chess/board]}]
                 (let [kind->sprite {:king "♚" :queen "♛" :rook "♜" :bishop "♝" :knight "♞" :pawn "♟"}
                       pos->piece (->> board
                                       (mapv #(vector (:pos %) %))
                                       (into {}))]
                   {:fx/node (let [gp (GridPane.)]
                               (doall
                                (for [row (range 8) col (range 8)]
                                  (let [cell-color (if (zero? (mod (+ col (mod row 2)) 2)) "#f0d9b5" "#b58863")
                                        {:keys [kind player]} (pos->piece [row col])
                                        cell-str (kind->sprite kind "")
                                        player-color (when player (name player))]
                                    (.add gp (doto (Label. cell-str)
                                               (.setStyle (format "-fx-background-color:%s; -fx-font-size:40; -fx-text-fill:%s; -fx-alignment: center;"
                                                                  cell-color player-color))
                                               (.setPrefWidth 50))
                                          (int col)
                                          (int row)))))
                               gp)}))})

6.9. Default visualizers

You can make any visualizer the default by calling add-default-visualizer which takes a predicate on the val-data (the same received by :on-create) and a visualizer key, like this :

(viz/add-default-visualizer (fn [val-data] (contains? (:flow-storm.runtime.values/kinds val-data) :chess-board)) :chess-board)

For all FlowStorm provided visualizers take a look at flow-storm.debugger.ui.data-windows.visualizers namespace.

Default visualizers predicates are added in a stack, and tried from the top. This means that you can always overwrite a default by adding a new one.

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

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

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

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

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

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

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

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

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

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

(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
Automatic derefing

FlowStorm will automatically deref Atoms, Refs, Agents, Vars and all pending-realized derefables on tracing so no need to implement flow-storm.runtime.values/snapshot-value for them.

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

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

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

Right clicking any namespace will give you options for what level of a namespace you want to instrument.

On the bottom pane (instrumentations) you will see your current instrumentation configution (if any). Here we can see that everything under ring.middleware.anti-forgery will be instrumented every time something inside it gets compiled.

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.

After changing any prefix FlowStorm will ask if you want it to reload the affected namespaces for you. Namespace reloading will all reload all namespaces it depends on in topological order, so it shoulnd’t break your system in any way.

browser storm instrumentation 3

You can also provide functions to be called before and after reloading in case you need to stop and start your system with :

(flow-storm.api/set-before-reload-callback! (fn [] (println "Before reloading")))
(flow-storm.api/set-after-reload-callback!  (fn [] (println "After reloading")))
Note
Instrumentation

Just changing the prefixes without reloading will not make your currently loaded code [un]instrumented. If you haven’t let FlowStorm reload them for you, you can always recompile them as usual with your editor commands or by executing something like (require 'the-selected.namespace :reload).

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

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

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

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

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

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

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

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

14. Key bindings

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

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

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

16. Working on Windows with WSL2

For those using current versions of WSL2 on Windows it should be pretty straight forward.

  • export DISPLAY=:0

  • export WSL2_GUI_APPS_ENABLED=1

Font issues had been reported on some distros, like java.lang.NullPointerException: Cannot read field "firstFont" because "<local4>" is null which seams to be solved just by installing font packages like dejavu-fonts or ttf-dejavu depending on the distro.

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

17.1. Emacs

;; for opening your project files
"-Dflowstorm.fileEditorCommand=emacsclient -n +<<LINE>>:0 <<FILE>>"

;; simple way for opening files inside jars (works on linux only)
"-Dflowstorm.jarEditorCommand=emacsclient -n +<<LINE>>:0 <<JAR>>/<<FILE>>"

;; for opening files inside jars that works on every OS (requires FlowStorm >= 3.17.3)
"-Dflowstorm.jarEditorCommand=emacsclient --eval '(let ((b (cider-find-file \"jar:file:<<JAR>>!/<<FILE>>\"))) (with-current-buffer b (switch-to-buffer b) (goto-char (point-min)) (forward-line (1- <<LINE>>))))'"

17.2. VSCode

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

17.3. IntelliJ

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

17.4. Vim

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

18. Editors/IDEs integration

18.1. Emacs

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

19. Tutorials and demos

20. Troubleshooting

20.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"}
org.openjfx/javafx-web      {:mvn/version "19.0.2"}

21. Internals, diagrams and documentation