Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve debugging info in useAtomsDevtools #931

Closed
justjake opened this issue Jan 5, 2022 · 11 comments
Closed

Improve debugging info in useAtomsDevtools #931

justjake opened this issue Jan 5, 2022 · 11 comments
Labels
enhancement New feature or request

Comments

@justjake
Copy link
Contributor

justjake commented Jan 5, 2022

The current useAtomsDevtools is a great start for debugging tools. I've been playing with it a bit and want to suggest a few features:

  1. Better atom labeling. Currently, atoms are labeled sequentially as atom1, atom2, etc. Here's some ideas:

    • Automatically label atoms by capturing a stack trace during atom construction (in dev mode only), and adding a partial formatted stack trace to atom.toString()'s output. I've experimented a little with this, for example an atom I create as const testAtom = implicitAtom(...) has this toString: WritableImplicitAtom 5 (created at Object.<anonymous> (/Users/jitl/src/jitl-monorepo/packages/state/src/lib/implicitAtom.test.ts:108:32))
    • A less general option is to encourage setting debugName or toString by adding a new optional final argument to atom() as atom(read, write, options?: { debugName?: string, toString?: () => string }); this would allow a nicer way to write atoms with labels in one line - atom(initialValue, { debugName: 'foo atom' }) is easier to remember than atom(initialValue); atom.debugName = 'foo atom'.
  2. Better action/mutation/change labeling. Currently, the only info about a change is when it occurred.
    Ideally, we could expose the following information about each new snapshot on the fake Redux action:

    • The place where the setAtomState function came from, eg the stack trace of the useAtom or useSetAtom hook that produced it. This allows the user to easily locate the React component that's updating Jotai's state.
    • The writable atoms (and/or their atom.toString()) involved in a state change. This could be complicated to gather for a derived writable atom that "fans out" by writing many other atoms, but we could at least capture the debug info of the top-level writable atom that started the write. That atom's identity is very analogous to the concept of "action type" in Redux.
  3. Advanced: show why a change caused what React components to re-render. I call this “causality debugging”. This helps developers detect O(n) re-rendering due to change fan-out, which is a performance problem that can be very difficult to debug in large apps. This is the most complex idea, but I know it’s possible since I implemented this in my own framework. This would build on the stack trace capture work in 1 & 2. Here’s how it works:

    • Whenever derived atom A reads a dependency atom D, save the stack trace of that get(D) call alongside the revision number in the dependency map (or in a devtools only location).
    • Whenever a writer atom W function writes an atom WD, save the stack trace of the set(WD, …) into a Map<W, [WD, Stack]> representing the action, stored in the devtools action log.
    • Whenever a component C subscribed to an atom via useAtom, capture a stack trace representing the component’s subscription.
    • Whenever C re-renders due to an atom change, you can use the saved information described above to “join” together a causality event E describing “C rendered because it read A which was changed by W”. Associate E with the relevant action in the devtools action log

    The way I expose this information to developers is by running a profile for N seconds, and then printing out a table of events E sorted by how many times the event occurred. So if a single atom changed and caused 100 components to re-render, that would cause 100 events and probably be at the top row of the table. But we could also might expose this info in the Redux devtools as part of the action log?

@dai-shi
Copy link
Member

dai-shi commented Jan 6, 2022

Better atom labeling

We'd like to utilize atom.debugLabel. This is not only for useAtomsDevtools, but also for useAtomDevtools and Provider's React DevTools.
Our current solution is https://jotai.org/docs/api/babel#plugin-debug-label, but it only works for babel. The problem is there are many non-babel setups.

capturing a stack trace during atom construction (in dev mode only)

Sounds interesting. @Thisen What do you think?

Better action/mutation/change labeling

Is this possible? @Aslemammad Can you look into it and discuss about it?

@justjake are you also willing to work on those improvements?

@justjake
Copy link
Contributor Author

justjake commented Jan 6, 2022

@dai-shi I might have some spare time to pick away at some of these issues slowly - in some ways, I'm trying to share ideas inspired by the closed-source state management system used by my employer.

For a direction to follow around 1a using stack frames for labeling, we might be able to learn something from React's use of stack frame capturing here: https://github.com/facebook/react/blob/cae635054e17a6f107a39d328649137b83f25972/packages/react-devtools-shared/src/backend/DevToolsComponentStackFrame.js

@justjake
Copy link
Contributor Author

justjake commented Jan 6, 2022

I added a third idea which builds on top of the first two. Instrumenting my framework with all 3 ideas as suggested above took me 3-4 days of full-time work, but, I know my framework quite well and it’s simpler than Jotai because (1) it’s not concurrent-mode correct the way Jotai is and (2) does not have writable derived atoms, (3) has much more predictable stack formats since it’s in-repo with my application. Those reasons make it harder (for me) to build this for Jotai. Still, I wanted to share these ideas even if I don’t have time to write all the code.

@justjake
Copy link
Contributor Author

justjake commented Jan 9, 2022

@dai-shi I started experimenting with this a bit. There are two approaches to implementing this:

  1. add a lot more event callbacks to store (onAtomRead, onAtomWrite, and some kind of info to associate them). This keeps bundle size in store.ts lower but is much more confusing since there is more empty logic in store.ts to call these callbacks, and the logic will be brittle when it comes to version and other complexities.
  2. Build all the tracing logic into store.ts, and figure out how to code-eliminate it to avoid paying the bundle size cost in production. React does this by checking process.env.NODE_ENV without typeof process, we could do the same thing? This would let us add extensive tracing right in store.ts in a much less brittle way, and in dev mode we can store debugging metadata right in store’s data structures.

React’s dead code elimination config for roll up: https://github.com/facebook/react/blob/fe905f152f1c9740279e31ce4478a5b8ca75172e/scripts/rollup/build.js#L372

@dai-shi
Copy link
Member

dai-shi commented Jan 9, 2022

@justjake Thanks for working on this!
I'm not super happy with adding complexity in store.ts, but if this is DEV-only thing (zero overhead in production), it's worthwhile. I'd put my effort on refactoring too.
typeof process is required for browser support. We need NODE_ENV check inline. Unlike React, we don't provide two builds. I know it's a bit annoying, but it's a different issue, and should be tackled separately.

@dai-shi dai-shi added the enhancement New feature or request label Apr 26, 2022
@cortesa
Copy link

cortesa commented May 17, 2022

can be added a 'devLabel' or so when create the atom? something like this:
const countAtom = atom(initValue, devlabel)
and take it to show in devtools?

@dai-shi
Copy link
Member

dai-shi commented May 18, 2022

We already have .debugLabel property in case you missed it.

const countAtom = atom(0);
countAtom.debugLabel = 'countAtom';

and, we do have a babel plugin to do it automatically behind the scene.

@cortesa
Copy link

cortesa commented May 18, 2022

We already have .debugLabel property in case you missed it.

const countAtom = atom(0);
countAtom.debugLabel = 'countAtom';

and, we do have a babel plugin to do it automatically behind the scene.

great!!, yes I've missed it sorry, thanks for the info

@cortesa
Copy link

cortesa commented Jan 9, 2023

hi, is it possible to hide an atom from redux store browser, I have a derived readonly atom that want to debug but don't want to see the updaters it introduce a lot of noise in the redux store browser

@dai-shi
Copy link
Member

dai-shi commented Jan 9, 2023

hi, is it possible to hide an atom from redux store browser, I have a derived readonly atom that want to debug but don't want to see the updaters it introduce a lot of noise in the redux store browser

Not at this point. It's on our action items. (cc: @arjunvegda btw, see OP, you might be interested.)

Meanwhile, a workaround could be using useAtomDevtools instead of useAtomsDevtools.
@cortesa Please open a new discussion and leave this issue for the original topic.

@dai-shi
Copy link
Member

dai-shi commented Apr 10, 2023

Moved to jotaijs/jotai-devtools#55

@dai-shi dai-shi closed this as completed Apr 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants