- Notifications
You must be signed in to change notification settings - Fork1
Omnipotent/omniscient tracing and debugging for clojure(script)
License
Cyrik/omni-trace
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
reified execution for Clojure(script)
API is still a little unstable but it's only for dev time so there shouldn't be any problems.
or just through github source:
:deps {omni_trace/omni_trace {:git/url"https://github.com/Cyrik/omni-trace":sha"07f290cbfa266ee2da214e1a67e49ba8cbf3f521"}}
contrived example code for demo debugging pupose
(nsuser (:require [cyrik.omni-trace:as o] [cyrik.omni-trace.testing-ns:as e] [cyrik.omni-trace.instrument:as i] [cyrik.omni-trace.flamegraph:as flame] [portal.web:as p]))(comment;instrument a namespace (o/trace 'cyrik.omni-trace.testing-ns);; (o/trace ['cyrik.omni-trace.testing-ns]);; (o/trace ["cyrik.omni-trace.testing-ns"]);run functions in that namespace (-> e/machine-init (e/insert-coin:quarter) (e/insert-coin:dime) (e/insert-coin:nickel) (e/insert-coin:penny) (e/press-button:a1) (e/retrieve-change-returned));throws on purpose for demonstration;look at traces for every function that was traced @i/workspace;connect to portal (defportal (p/open)) (add-tap #'p/submit);send the trace to portal as a vegajs flamegraph (tap> (o/flamegraph));remove tracing from a namesapce (o/uninstrument-ns 'cyrik.omni-trace.testing-ns))
Example "real world" usage story (Spoilers for Advent of Code 2021 Day1)
(require '[cyrik.omni-trace:as o]);; uncomment to also trace clojure.core, tested with testing-ns only;; (require '[cyrik.omni-trace.instrument :as i]);; (reset! i/ns-blacklist [])(o/run (cyrik.omni-trace.testing-ns/run-machine));; run this for cljs;; (o/run-traced-cljs 'cyrik.omni-trace.testing-ns/run-machine)(tap> (o/rooted-flamegraph 'cyrik.omni-trace.testing-ns/run-machine))
This usesclj-kondo to find all transitive calls from the provided symbol.It then runs the function with any supplied args and untraces everything.It reaches down into clojure.core if you remove the blacklist.
If your deeptraced function only traced itself make sure its namespace is required somewhere inside "src" or "dev".I will add more options for choosing namespaces later.Currently there are still a few problems with recursion, will have to rewrite the deps graph for it.I'm guessing a lot of code will still explode when allowing clojure.core trace, since the tracing code itself, uses those.Will probably clean up the blacklist and try to use local function copies for tracing.
get the WIP debux version:
:debux-src {:extra-deps {philoskim/debux {:git/url "https://github.com/Cyrik/debux.git" :sha "a957ee863f08551d4c0a0adad85c501976c36ef2"}}}
(require '[cyrik.omni-trace:as o])(require '[cyrik.omni-trace.instrument:as i])(defnfoo [a b & [c]] (if c (* a b c) (* a b100)))(o/instrument-fn 'user/foo {::o/workspace i/workspace:inner-tracetrue})(foo23)(print @i/workspace);; =>{:log {:67969 {:args [23],:parent:root,:return600,:name user/foo,:file"/Users/lukas/Workspace/clojure/omni-trace/dev/user.clj",:start1638382816113,:inner [{:level0,:form-id1274972146,:form (defnfoo [a b & [c]] (if c (* a b c) (* a b100)))} {:form c,:coordinate [31],:result nil,:id67970} {:form a,:coordinate [331],:result2,:id67971} {:form b,:coordinate [332],:result3,:id67972} {:form (* a b100),:coordinate nil,:result600,:id67973} {:form (if c (* a b c) (* a b100)),:coordinate nil,:result600,:id67974} {:binding result__20657__auto__,:coordinate nil,:result600,:id67975}],:id:67969,:end1638382816116}},:max-callsites #{}};; works with loop recur(defnfact [num] (loop [acc1 n num] (if (zero? n) acc (recur (* acc n) (dec n)))))(o/instrument-fn 'user/fact {::o/workspace i/workspace:inner-tracetrue})(print @i/workspace);; =>{:log {:68336 {:args [3],:parent:root,:return6,:name user/fact,:file"/Users/lukas/Workspace/clojure/omni-trace/dev/user.clj",:start1638382991955,:inner [{:level0,:form-id-364126590,:form (defnfact [num] (loop [acc1 n num] (if (zero? n) acc (recur (* acc n) (dec n)))))} {:form num,:coordinate [313],:result3,:id68337} {:form n,:coordinate [3211],:result3,:id68338} {:form (zero? n),:coordinate nil,:resultfalse,:id68339} {:form acc,:coordinate [32311],:result1,:id68340} {:form (* acc n),:coordinate nil,:result3,:id68341} {:form (dec n),:coordinate nil,:result2,:id68342} {:form n,:coordinate [3211],:result2,:id68343} {:form acc,:coordinate [32311],:result3,:id68344} {:form (* acc n),:coordinate nil,:result6,:id68345} {:form (dec n),:coordinate nil,:result1,:id68346} {:form n,:coordinate [3211],:result1,:id68347} {:form acc,:coordinate [32311],:result6,:id68348} {:form (dec n),:coordinate nil,:result0,:id68349} {:form n,:coordinate [3211],:result0,:id68350} {:form (zero? n),:coordinate nil,:resulttrue,:id68351} {:form (loop [acc1 n num] (debux.common.util/insert-blank-line) (if (zero? n) acc (recur (* acc n) (dec n)))),:coordinate nil,:result6,:id68352} {:binding result__20657__auto__,:coordinate nil,:result6,:id68353}],:id:68336,:end1638382991968}},:max-callsites #{}}
works pretty well already, but:
- works in clj only for now, working on cljs
- only one innertrace at a time or omni-trace will write the results to the wrong one
- debux still needs a lot of cleanup in the atom version
- api very unstable
- no good way to display it
- Works in clojure and clojurescript
- Instrument whole namespaces from the repl
- show the trace as a Flamegraph in Portal or anything else that understands Vega.js
- remembers the call that caused the exception and shows the arguments
- stops tracing callsites if they have been called to often, default is 100, can be changed with :omni-trace.omni-trace/max-callsite-log option
- better trace output to the REPL
- performance
- callbacks from Portal so you can rerun an updated function with the old params by clicking on it in the Flamegraph
- (maybe) timetravel in trace
- Debux: tracing library that show what is going on inside of a function call. Hopefully this can be integrated as the "inner" function trace in omni-trace
- Sayid: clojure only version of what omni-trace is trying to do
- Postmortem: great library for debugging dataflow
About
Omnipotent/omniscient tracing and debugging for clojure(script)