This is a pre-alpha project, aiming at making the JS runtime and its dynamic call graph visual and interactive through a combination of instrumentation (using Babel) + a VSCode extension, effectively (ultimately) making it an amazing tool for (i) program comprehension + (ii) debugging.
The master
branch is not quite active yet. Check out the dev
branch instead.
Here is a (very very early, read: crude) 1min demo video of just a small subset of the features:
- node
- vscode
- yarn
git clone https://github.com/Domiii/dbux.git
cd dbux
code .
npm run dbux-install
# if dependencies bug out, run the (very aggressive) clean-up command: `npm run dbux-uninstall`
code . # open project in vscode
npm start # start webpack build of all projects in watch mode
- go to your debug tab, select
dbux-code
and press F5 (runs the vscode extension in debug mode) - Inside of the new window, you can:
dbux-run # instruments + executes currently opened file
- test on one of the pre-configured projects
- use
dbux-cli
to setup + run your own project
In the analyze/
folder, you find several python notebooks that allow you analyze the data that dbux
generates. Here is how you set that up:
- Run some program with Dbux enabled (e.g.
samples/[...]/oop1.js
) - In the VSCode extension, open a file of that program that has traces in it
- In VSCode
Run Command
(CTRL/Command + SHIFT + P
) ->Dbux: Export file
- Make sure you have Python + Jupyter setup
- Windows
- Install
Anaconda
withchocolatey
- Set your
%PYTHONPATH%
in system config to your AnacondaLib
+DLLs
folders (e.g.C:\tools\Anaconda3\Lib;C:\tools\Anaconda3\DLLs;
) - Done!
- Install
- Windows
- Run one of the notebooks, load the file, and analyze :)
- After you opened a new VSCode window with
dbux-code
enabled (see steps above), in that window you can run + trace all kinds of code. - Dbux currently has one frontend project pre-configured for testing purposes, that is todomvc's
es6
version.- install it first:
npm run p1-install
- install it first:
- Run it:
npm run p1-start
(starts webpack + webpack-dev-server) - Open in browser (http://localhost:3030), then check results of the run in the extension test window
This is a multi-project monorepo.
Why is it not using LERNA? Because I did not know about LERNA when I started; but it's working quite well nevertheless :)
`# jest` yarn add --dev jest jest-expect-message jest-extended
`# babel basics` yarn add --dev @babel/core @babel/cli @babel/node @babel/register
`# babel plugins` yarn add --dev @babel/preset-env @babel/plugin-proposal-class-properties @babel/plugin-proposal-optional-chaining @babel/plugin-proposal-decorators @babel/plugin-proposal-function-bind @babel/plugin-syntax-export-default-from @babel/plugin-syntax-dynamic-import @babel/plugin-transform-runtime && \
`# babel runtime` yarn add core-js@3 @babel/runtime
`# eslint` yarn add --dev eslint eslint-config-airbnb-base
`# webpack` yarn add --dev webpack webpack-cli webpack-dev-server nodemon
`# flow` yarn add --dev flow-bin @babel/preset-flow eslint-plugin-flowtype && npx flow init #&& npx flow
`# babel dev` yarn add --dev @babel/parser @babel/traverse @babel/types @babel/generator @babel/template @babel/code-frame babel-plugin-tester
or with npm:
`# jest` npm i -D jest jest-expect-message jest-extended
`# babel basics` npm i -D @babel/core @babel/cli @babel/node @babel/register
`# babel plugins` npm i -D @babel/preset-env @babel/plugin-proposal-class-properties @babel/plugin-proposal-optional-chaining @babel/plugin-proposal-decorators @babel/plugin-proposal-function-bind @babel/plugin-syntax-export-default-from @babel/plugin-syntax-dynamic-import @babel/plugin-transform-runtime && \
`# babel runtime` npm i -S core-js@3 @babel/runtime
`# eslint` npm i -D eslint eslint-config-airbnb-base
`# babel` npm run dbux-install --force --save @babel/cli@latest @babel/core@latest @babel/node@latest @babel/plugin-proposal-class-properties@latest @babel/plugin-proposal-decorators@latest @babel/plugin-proposal-function-bind@latest @babel/plugin-proposal-optional-chaining@latest @babel/plugin-syntax-dynamic-import@latest @babel/plugin-syntax-export-default-from@latest @babel/plugin-syntax-flow@latest @babel/plugin-transform-runtime@latest @babel/preset-env@latest @babel/preset-flow@latest @babel/register@latest
`# babel instrumentation` @babel/code-frame@latest @babel/template@latest
- replace:
"([^"]+)": "([^"]+)",\n\s*
w/$1@latest
- Getting the debugger to work when it just won't work!
- Tell debugger to skip files
- Chrome: Blackboxing
- babel plugin handbook
- babel parser docs
- babel-parser AST explorer
- babel-types src (core)
- babel parser AST specs
- babel traverse src
- NodePath:modification
- At its core: path.visit() and path._call()
- path.get
- generateUidBasedOnNode
- NOTE: babel/traverse is not properly documented, so we go to the source
- Problem: babel plugin ordering
- babel-preset-env
Istanbul + NYC add require hooks to instrument any loaded file on the fly
- NOTES: How does it work?
- They are using
require.extensions
(which are deprecated) - More info here: [https://gist.github.com/jamestalmage/df922691475cff66c7e6](Breakdown of How Require Extensions Work)
- They are using
- References: Instrumentation
- https://github.com/istanbuljs/nyc/blob/master/lib/instrumenters/istanbul.js#L20
- https://github.com/istanbuljs/istanbuljs/blob/master/packages/istanbul-lib-instrument/src/instrumenter.js#L50
- https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-lib-instrument
- Istanbul visitor (babel plugin)
- counter statement generator (e.g.
__cov().branches[123]++
) - visitor bookkeeping
- counter statement generator (e.g.
- API
- References:
require
hook - Sourcemap problems
- sourcemaps don't work right with NYC if
@babel/register
is not used- istanbuljs/nyc#619
- "This issue is blocked by evanw/node-source-map-support#239. The issue is that nyc source-maps are inline but node-source-map-support does not look at inline source-maps by default."
- istanbuljs/nyc#619
- babel does not support proper sourcemap merging yet
- sourcemaps don't work right with NYC if
- Configuring Babel for NYC + Istanbul
- More references
- Gitlens provides custom widgets with clickable buttons that pop up on hover
- adding custom queries/filters to treeview through configuration
- how to let WebView control VSCode
window
and vice versa: - idea to navigate between or control traces in code: Jumpy?
- idea to provide inline context menus:
registerCompletionItemProvider
? - cannot currently set
TreeItem
text color- Limited capability for some file names: microsoft/vscode#47502 (comment)
- Suggested API discussion: microsoft/vscode#54938
(only very few of the features are explained here, a lot more to come in the future...)
- Indexes
- [shape] an index is a complete partitioning of all data of one particular collection
- [storage method] all new data is categorized into all matching indexes
- [storage invalidation] previously indexed data will generally never get evicted
- [key type] (currently) keys of indices can only be numbers
- TODO: add string keys as well, without reducing performance of number-based indices
- [storage type] objects in indexes are always entries of
Collection
s
- Queries
- [shape] usually we want Queries to be
CachedQueries
(currently all are) which perform an expensive computation and then store the result thereof - [storage method] only results of individual queries are cached when queried (not cached when data comes in)
- [storage invalidation] cache will be invalidated when new data comes in (unless
cfg.versionDependencies
is empty) - [key type] the keys of cached query results are the input arguments ("
args
")- that's why
args
should ideally be a single primitive data type or a flat array of primitive data types
- that's why
- [storage type] queries can return and cache any data type
- [shape] usually we want Queries to be
- don't call
process.exit
(at least not immediately)process.exit
will kill the process before all recorded data has been sent out- if you MUST call it, make sure to put it in a
setTimeout
with 0.5-1s delay - NOTE: many frameworks that might kill your process allow disabling it (e.g.
Mocha
's--no-exit
argument)
- What applications work so well with DBUX?
- TODO: we are still exploring that
- What applications won't work so well with DBUX?
- Proxies and custom object getters with side effects
- For serialization
dbux-runtime
iterates (or will in the future iterate) over object properties - Thus possibly causing side effects with proxy and getter functions
- At least it will leave unwanted traces (while attempting to "observe") - Damn you, Observer effect!!! :(
- TODO: at least flag traces caused by
dbux-runtime
by setting sometrace-triggered-from-dbux-builtin-call
flag while running built-in functions- NOTE: This will still mess with proxy and getter functions that themselves have side effects, such as caching functions, tracers and more.
- For serialization
- Proxies and custom object getters with side effects
- Issues under Windows
- sometimes, when running things in VSCode built-in terminal, it might change to lower-case drive letter
- This causes a mixture of lower-case and upper-case drive letters to start appearing in
require
paths- => this makes
babel
unhappy (github issue)
- => this makes
- Official bug report: microsoft/vscode#9448
- Solution: run command in external
cmd
or find a better behaving terminal
- This causes a mixture of lower-case and upper-case drive letters to start appearing in
- sometimes, when running things in VSCode built-in terminal, it might change to lower-case drive letter
- https://vscodecandothat.com/
- https://medium.com/club-devbytes/how-to-use-v-s-code-like-a-pro-fb030dfc9a72
NOTE: VSCode's Terminal has no "Clear" keybinding anymore You can re-add it manually:
- CTRL+SHIFT+P -> "Open Keyboard Shortcuts (JSON)"
- add (for Windows use
ctrl
; for MAC usecmd
):
{
"key": "ctrl+k",
"command": "workbench.action.terminal.clear",
"when": "terminalFocus"
},
Socket.IO
depends onuws
which is deprecated- fix: tell webpack to ignore it, since by default its not being used
- see: socketio/engine.io#575
- see: socketio/socket.io#3342
- see: https://github.com/mmdevries/uws
socket.io-client
bugs out becausews
is bundled as targeting browsercode-insiders .\dbux-runtime\node_modules\engine.io-client\lib\transports\websocket.js
- Babel Config pain
- Which parts of my code executed?
- How often did this code execute?
- What did these expressions evaluate to during each execution?
- What were the arguments passed to this function call?
- Where did the execution go from here? Where did it come from?
- Which events were triggered and how did its handlers execute?
- Sub-graph filtering
- Search sub graph contexts by keyword (QuickInput)
- All traces/contexts/runs that referenced some object (ValueRef)
- Sub-graph filtering
- Multiple filter UI modes
- hide vs. grayed out?
- Multiple filter UI modes
- What is the critical path in this sub-graph, in terms of call-stack depth?
- NOTE: we don't aim to do performance analysis, so we can't find the actual critical path
- Given two traces, find shortest path (or path that is most likely to be the actual path?)
- TODO: Somehow visualize and allow interactions with that path
- -> Possibly like a car navigation system -> listing all the twists and turns in a list
- TODO: Somehow visualize and allow interactions with that path
- Interactive visualized call graph
- zoom- and pan-able
- multi-resolution
- features and filters can be enabled and disabled
- multiple coloring schemes (e.g. one each for color per file/context/feature type and more)11
- Instrumentation
- Collection
- Postprocessing
- adding one-to-one fields (pre-index)
- Index
- adding one-to-one fields (post-index)
- Query + CachedQuery
- Are dynamic vs. static exit traces of functions the same?
- special attention:
try
statements
- {Previous,Next}InContext
- Use
getTracesOfRealContext
- [no_trace]
- [Previous && current trace is Push && previous trace is Pop]
- -> go
- [Next && current trace is Pop && next trace is Push]
- -> go
- [Previous && current trace is Push && previous trace is Pop]
- Use
- PreviousParent
- First trace in current context --> context's
parentTraceId
- First trace in current context --> context's
- NextParent
- Same as
PreviousParent
, but get "next in context" ofparentTrace
- Same as
- PreviousChild
- Use
ParentTracesInRealContextIndex
- Use
- NextChild
- Same as
PreviousChild
, but use "next in context" ofparentTrace
- Same as