Skip to content

Commit

Permalink
Add "Timeline tool" implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Juan Pedro Monetta Sanchez authored and Juan Pedro Monetta Sanchez committed Jul 27, 2023
1 parent 9f76efe commit 2403a05
Show file tree
Hide file tree
Showing 22 changed files with 435 additions and 88 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### New Features

- Add "Search value on Flows" to taps
- Add "Timeline tool" implementation

### Changes

Expand Down
5 changes: 3 additions & 2 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
com.cognitect/transit-clj {:mvn/version "1.0.333"}
com.cognitect/transit-cljs {:mvn/version "0.8.280"}
io.github.clojure/tools.build {:mvn/version "0.9.4" :exclusions [com.google.guava/guava org.slf4j/slf4j-nop]}
com.github.jpmonettas/hansel {:mvn/version "0.1.74"}
com.github.jpmonettas/hansel {:local/root "/home/jmonetta/my-projects/hansel" #_#_:mvn/version "0.1.74"}

org.openjfx/javafx-controls {:mvn/version "19.0.2.1"}
org.openjfx/javafx-base {:mvn/version "19.0.2.1"}
org.openjfx/javafx-graphics {:mvn/version "19.0.2.1"}
Expand All @@ -26,7 +27,7 @@
:extra-deps {com.github.jpmonettas/clojure {:mvn/version "1.12.0-master-SNAPSHOT"}}
:jvm-opts ["-Dproject-name=StormTest"
"-Dflowstorm.startRecording=true"
"-Dflowstorm.theme=light"
"-Dflowstorm.theme=dark"
"-Dclojure.storm.instrumentEnable=true"
"-Dclojure.storm.instrumentSkipPrefixes=clojure.,flow-storm.,cider.,nrepl.,hansel.,refactor-nrepl."]}

Expand Down
36 changes: 36 additions & 0 deletions docs/user_guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,42 @@ If you are using the emacs integration you can do `C-c C-f d` (flow-storm-show-c
in the debugger.


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

image::user_guide_images/timeline_enable.png[]

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, lets say you record this code :

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

after hitting refresh you should see something like this :

image::user_guide_images/timeline.png[]

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

== Value inspector

Use the value inspector to explore any data.
Expand Down
162 changes: 111 additions & 51 deletions docs/user_guide.html

Large diffs are not rendered by default.

Binary file added docs/user_guide_images/timeline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/user_guide_images/timeline_enable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions resources/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,11 @@
.docs-example-ret-symbol {
-fx-text-fill: -fx-theme-links;
}

/*****************/
/* Timeline tool */
/*****************/

.timeline-tool .controls-box {
-fx-padding : 10;
}
14 changes: 12 additions & 2 deletions src-dbg/flow_storm/debugger/runtime_api.clj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
(fn-call-stats [_ flow-id thread-id])
(find-fn-frames-light [_ flow-id thread-id fn-ns fn-name form-id])
(find-timeline-entry [_ criteria])
(total-order-timeline [_])
(async-search-next-timeline-entry [_ flow-id thread-id query-str from-idx opts])
(discard-flow [_ flow-id])

Expand Down Expand Up @@ -73,7 +74,8 @@
(remove-breakpoint [_ fq-fn-symb])

(stack-for-frame [_ flow-id thread-id fn-call-idx])
(toggle-recording [_]))
(toggle-recording [_])
(set-total-order-recording [_ x]))

(defn cached-apply [cache cache-key f args]
(let [res (get @cache cache-key :flow-storm/cache-miss)]
Expand Down Expand Up @@ -137,6 +139,7 @@
(fn-call-stats [_ flow-id thread-id] (api-call :local "fn-call-stats" [flow-id thread-id]))
(find-fn-frames-light [_ flow-id thread-id fn-ns fn-name form-id] (api-call :local "find-fn-frames-light" [flow-id thread-id fn-ns fn-name form-id]))
(find-timeline-entry [_ criteria] (api-call :local "find-timeline-entry" [criteria]))
(total-order-timeline [_] (api-call :local "total-order-timeline" []))
(async-search-next-timeline-entry [_ flow-id thread-id query-str from-idx opts] (api-call :local "async-search-next-timeline-entry" [flow-id thread-id query-str from-idx opts]))
(discard-flow [_ flow-id] (api-call :local "discard-flow" [flow-id]))
(def-value [_ var-symb val-ref] (api-call :local "def-value" [(or (namespace var-symb) "user") (name var-symb) val-ref]))
Expand Down Expand Up @@ -225,7 +228,10 @@
(api-call :local "stack-for-frame" [flow-id thread-id fn-call-idx]))

(toggle-recording [_]
(api-call :local "toggle-recording" [])))
(api-call :local "toggle-recording" []))

(set-total-order-recording [_ x]
(api-call :local "set-total-order-recording" [x])))

;;;;;;;;;;;;;;;;;;;;;;
;; For Clojure repl ;;
Expand All @@ -249,6 +255,7 @@
(fn-call-stats [_ flow-id thread-id] (api-call :remote "fn-call-stats" [flow-id thread-id]))
(find-fn-frames-light [_ flow-id thread-id fn-ns fn-name form-id] (api-call :remote "find-fn-frames-light" [flow-id thread-id fn-ns fn-name form-id]))
(find-timeline-entry [_ criteria] (api-call :remote "find-timeline-entry" [criteria]))
(total-order-timeline [_] (api-call :remote "total-order-timeline" []))
(async-search-next-timeline-entry [_ flow-id thread-id query-str from-idx opts] (api-call :remote "async-search-next-timeline-entry" [flow-id thread-id query-str from-idx opts]))
(discard-flow [_ flow-id] (api-call :remote "discard-flow" [flow-id]))
(def-value [_ var-symb val-ref]
Expand Down Expand Up @@ -353,6 +360,9 @@
(toggle-recording [_]
(api-call :remote "toggle-recording" []))

(set-total-order-recording [_ x]
(api-call :remote "set-total-order-recording" [x]))

Closeable
(close [_] (stop-repl))
)
2 changes: 1 addition & 1 deletion src-dbg/flow_storm/debugger/ui/flows/functions.clj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
[javafx.scene.control CheckBox SplitPane]
[javafx.scene.input MouseButton]))

(defn- functions-cell-factory [x]
(defn- functions-cell-factory [_ x]
(if (number? x)
(label (str x))

Expand Down
14 changes: 11 additions & 3 deletions src-dbg/flow_storm/debugger/ui/main.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
[flow-storm.debugger.ui.browser.screen :as browser-screen]
[flow-storm.debugger.ui.taps.screen :as taps-screen]
[flow-storm.debugger.ui.docs.screen :as docs-screen]
[flow-storm.debugger.ui.timeline.screen :as timeline-screen]
[flow-storm.debugger.runtime-api :as runtime-api :refer [rt-api]]
[flow-storm.debugger.ui.state-vars
:as ui-vars
Expand Down Expand Up @@ -40,7 +41,9 @@
(flows-screen/fully-remove-flow fid))

(runtime-api/clear-recordings rt-api)
(runtime-api/clear-api-cache rt-api))
(runtime-api/clear-api-cache rt-api)

(timeline-screen/clear-timeline))

(defn bottom-box []
(let [progress-box (h-box [])
Expand Down Expand Up @@ -112,8 +115,12 @@
:class "vertical-tab"
:content (docs-screen/main-pane)
:on-selection-changed (event-handler [_])})
timeline-tab (tab {:text "Timeline"
:class "vertical-tab"
:content (timeline-screen/main-pane)
:on-selection-changed (event-handler [_])})

tabs-p (tab-pane {:tabs [flows-tab browser-tab taps-tab docs-tab]
tabs-p (tab-pane {:tabs [flows-tab browser-tab taps-tab docs-tab timeline-tab]
:rotate? true
:closing-policy :unavailable
:side :left
Expand Down Expand Up @@ -253,7 +260,7 @@

(ui-utils/run-now
(try
(let [{:keys [recording?] :as runtime-config} (runtime-api/runtime-config rt-api)
(let [{:keys [recording? total-order-recording?] :as runtime-config} (runtime-api/runtime-config rt-api)
;; retrieve runtime configuration and configure before creating the UI
;; since some components creation depend on it
_ (ui-vars/configure-environment runtime-config)
Expand Down Expand Up @@ -314,6 +321,7 @@
(into #{}))]

(set-recording-btn recording?)
(timeline-screen/set-recording-check total-order-recording?)
(doseq [fid all-flows-ids]
(create-flow {:flow-id fid})))

Expand Down
89 changes: 89 additions & 0 deletions src-dbg/flow_storm/debugger/ui/timeline/screen.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
(ns flow-storm.debugger.ui.timeline.screen
(:require [flow-storm.debugger.ui.utils
:as ui-utils
:refer [label table-view h-box border-pane icon-button event-handler]]
[flow-storm.debugger.ui.state-vars
:as ui-vars
:refer [obj-lookup store-obj]]
[flow-storm.debugger.runtime-api :as runtime-api :refer [rt-api]]
[clojure.string :as str]
[flow-storm.debugger.state :as dbg-state]
[flow-storm.debugger.ui.flows.screen :as flows-screen])
(:import [javafx.scene.layout Priority VBox]
[javafx.scene.control TableCell TableRow CheckBox]
[javafx.scene.input MouseButton]))

(def thread-colors ["#DAE8FC" "#D5E8D4" "#FFE6CC" "#F8CECC" "#E1D5E7" "#60A917" "#4C0099" "#CC00CC"])

(defn set-recording-check [recording?]
(ui-utils/run-later
(let [[record-btn] (obj-lookup "total-order-record-btn")]
(.setSelected record-btn recording?))))

(defn clear-timeline []
(let [[{:keys [clear]}] (obj-lookup "total-order-table-data")]
(clear)))

(defn main-pane []
(let [{:keys [table-view-pane table-view add-all] :as table-data}
(table-view {:columns ["Thread Id" "Thread Name" "Function" "Expression" "Value"]
:resize-policy :constrained
:cell-factory-fn (fn [_ cell-val]
(doto (label (str cell-val))
(.setStyle "-fx-text-fill: #333")))
:row-update-fn (fn [^TableRow trow row-vec]
(doto trow
(.setStyle (format "-fx-background-color: %s" (-> row-vec meta :color)))
(.setOnMouseClicked
(event-handler
[mev]
(when (and (= MouseButton/PRIMARY (.getButton mev))
(= 2 (.getClickCount mev)))
(let [{:keys [flow-id thread-id thread-timeline-idx]} (meta row-vec)]
(flows-screen/goto-location {:flow-id flow-id
:thread-id thread-id
:idx thread-timeline-idx})))))))
:search-predicate (fn [[_ thread-id function expression] search-str]
(boolean
(or (str/includes? (str thread-id) search-str)
(str/includes? function search-str)
(str/includes? expression search-str))))
:items []})
record-btn (doto (CheckBox.)
(.setSelected false))
_ (.setOnAction record-btn
(event-handler [_] (runtime-api/set-total-order-recording rt-api (.isSelected record-btn))))
refresh-btn (icon-button :icon-name "mdi-reload"
:on-click (fn []
(let [timeline (runtime-api/total-order-timeline rt-api)
thread-ids (->> timeline
(map :thread-id)
(into #{}))
thread-color (zipmap thread-ids thread-colors)]
(->> timeline
(mapv (fn [{:keys [type thread-id fn-ns fn-name expr-str expr-val-str] :as tl-entry}]
(let [{:keys [thread/name]} (dbg-state/get-thread-info thread-id)]
(with-meta
(case type
:fn-call [thread-id name (format "%s/%s" fn-ns fn-name) "" ""]
:fn-return [thread-id name "RETURN" "" ""]
:expr-exec [thread-id name "" expr-str expr-val-str])
(assoc tl-entry :color (thread-color thread-id))))))
add-all)))
:tooltip "Refresh the content of the timeline")
main-pane (border-pane
{:top (doto (h-box [refresh-btn (label "Enable:") record-btn] "controls-box")
(.setSpacing 5))
:center table-view-pane}
"timeline-tool")]

(store-obj "total-order-record-btn" record-btn)
(store-obj "total-order-table-data" table-data)

(VBox/setVgrow table-view Priority/ALWAYS)

main-pane))

(comment

)
20 changes: 16 additions & 4 deletions src-dbg/flow_storm/debugger/ui/utils.clj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(ns flow-storm.debugger.ui.utils
(:require [flow-storm.utils :refer [log-error]])
(:import [javafx.scene.control Button ContextMenu Label ListView SelectionMode ListCell MenuItem ScrollPane Tab
Alert ButtonType Alert$AlertType ProgressIndicator ProgressBar TextField TextArea TableView TableColumn TableCell
Alert ButtonType Alert$AlertType ProgressIndicator ProgressBar TextField TextArea TableView TableColumn TableCell TableRow
TabPane$TabClosingPolicy TabPane$TabDragPolicy TableColumn$CellDataFeatures TabPane Tooltip]
[javafx.scene.layout HBox VBox BorderPane]
[javafx.geometry Side Pos]
Expand Down Expand Up @@ -377,10 +377,10 @@

"Create a TableView component.
`columns` should be a vector of strings with columns names.
`cell-factory-fn` a fn that gets called with each item (from `items`) and should return a Node for the cell.
`cell-factory-fn` a fn that gets called with each cell and item and should return a Node for the cell.
`items` a collection of data items for the table. Each one should be a vector with the same amount "

[{:keys [columns cell-factory-fn items selection-mode search-predicate on-click on-enter resize-policy]
[{:keys [columns cell-factory-fn row-update-fn items selection-mode search-predicate on-click on-enter resize-policy]
:or {selection-mode :multiple
resize-policy :unconstrained}}]

Expand Down Expand Up @@ -408,10 +408,11 @@
(.setGraphic nil)
(.setText nil))

(let [cell-graphic (cell-factory-fn item)]
(let [cell-graphic (cell-factory-fn this item)]
(doto this
(.setText nil)
(.setGraphic cell-graphic))))))))))))

columns (map-indexed make-column columns)
table-selection (.getSelectionModel tv)
selected-items (fn [] (.getSelectedItems table-selection))
Expand All @@ -431,6 +432,17 @@
:clear clear
:add-all add-all}]

(when row-update-fn
(.setRowFactory
tv
(proxy [javafx.util.Callback] []
(call [tcol]
(proxy [TableRow] []
(updateItem [item empty?]
(let [^TableRow this this]
(proxy-super updateItem item empty?)
(row-update-fn this item))))))))

(.clear (.getColumns tv))
(.addAll (.getColumns tv) columns)

Expand Down
2 changes: 1 addition & 1 deletion src-dbg/flow_storm/debugger/ui/value_renderers.clj
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
(defn create-map-browser-pane [amap on-selected]
(let [{:keys [table-view-pane table-view]}
(table-view {:columns ["Key" "Value"]
:cell-factory-fn (fn [item]
:cell-factory-fn (fn [_ item]
(:node-obj (make-node item on-selected)))
:search-predicate (fn [[k-item v-item] search-str]
(boolean
Expand Down
4 changes: 4 additions & 0 deletions src-dev/dev.clj
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,8 @@

(flow-storm.api/continue)

(defn my-sum [a b] (+ a b))

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

)
Loading

0 comments on commit 2403a05

Please sign in to comment.