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

structural graph #1378

Closed
luoxiaozero opened this issue Jul 18, 2023 · 17 comments
Closed

structural graph #1378

luoxiaozero opened this issue Jul 18, 2023 · 17 comments
Labels
enhancement New feature or request

Comments

@luoxiaozero
Copy link
Contributor

Can leptos add interface of component structural graph?

Used to provide basic capabilities for browser extension development.

@luoxiaozero
Copy link
Contributor Author

I implement an example in examples/debugger, code in leptos_debugger. It doesn't feel good.

principle:

Create a node and collect node information. Finally, the dependency graph is generated.

Signals are transformed and collected by feature(min_specialization)

@gbj
Copy link
Collaborator

gbj commented Jul 19, 2023

I think this is a great idea. All of the data needed to create a dependency graph can be found in the Runtime struct in runtime.rs and specifically in these three nodes fields:

pub nodes: RefCell<SlotMap<NodeId, ReactiveNode>>,
pub node_subscribers:
RefCell<SecondaryMap<NodeId, RefCell<FxIndexSet<NodeId>>>>,
pub node_sources:
RefCell<SecondaryMap<NodeId, RefCell<FxIndexSet<NodeId>>>>,
These types are somewhat complex but the concept is very simple: it's just a tree of NodeIds, and each one is linked to both its parents and its children:

  • nodes: map of NodeId to the actual data about the node, including its type (signal, effect, memo) and value
  • node_subscribers: map of a NodeId to all of its children, the nodes "below" it in the graph, i.e., nodes that depend on this one
  • node_sources: map of a NodeId to all of its parents, the nodes "above" it in the graph, i.e., the nodes that this one depends on

An identical structure exists for Scopes too:

pub scopes: RefCell<SlotMap<ScopeId, RefCell<Vec<ScopeProperty>>>>,
pub scope_parents: RefCell<SparseSecondaryMap<ScopeId, ScopeId>>,
pub scope_children: RefCell<SparseSecondaryMap<ScopeId, Vec<ScopeId>>>,

I'm not sure what kind of API you're looking for, but I'm happy to expose whatever is useful for debugging purposes. A PR with what you need would be totally welcome.

@gbj gbj added the enhancement New feature or request label Jul 19, 2023
@luoxiaozero
Copy link
Contributor Author

I didn't make it clear that what the leptos_reactive Runtime provides is not what I need. I think the API provides component and element names, properties and children in real time. I'll probably end up providing a simple PR to discuss this by the end of the week.

@gbj
Copy link
Collaborator

gbj commented Jul 21, 2023

I see, I misread your initial post. You're looking for the component (and element) tree, not the reactive graph.

It's worth taking a look at the existing tracing instrumentation here as well. It's possible to use this to generate traces like this. More can probably be done to augment this but it may be a good starting point.

  DEBUG  clicked button
    at src/main.rs:132
    in on with event: click
    in HtmlElement with tag: <button/>
    in <MyComponent />
    in <Test />

I created this by augmenting leptos_dom/examples/test-bench/src/main.rs with the following:

#[component]
pub fn MyComponent(cx: Scope) -> impl IntoView {
    view! { cx,
        <main>
            <div>
                <button on:click=move |_| {tracing::debug!("clicked button")}>
                    "Click"
                </button>
            </div>
        </main>
    }
}

As you can see it traces the component tree but not the nesting of HTML elements, so as I said — more can be done if you want the complete tree.

EDIT: I'm not super familiar with tracing but I assume it's possible to create a subscriber of some kind that hooks into this instrumentation in real time for your purposes.

@luoxiaozero
Copy link
Contributor Author

I'm not familiar with tracing. I need to investigate tracing.
For the example above, can I get it when executing the mount_to_body method instead of clicking the button?

@luoxiaozero
Copy link
Contributor Author

luoxiaozero commented Jul 24, 2023

I looked at the tracking and had a few questions:

  1. can I get it when executing the mount_to_body method instead of clicking the button?
  2. If a user's library also uses tracking, will their logs be mixed with ours?

I feel that it is better not to use the tracing library to trace the component dependency graph. In the future, tracing can be used as an underlying library for leptos_debugger to provide a call stack for user-triggered events.

Maybe I haven't thought about it enough.
In addition, I also provided a simple initial PR #1429 to discuss.

@gbj
Copy link
Collaborator

gbj commented Jul 25, 2023

@jquesada2016 and @benwis, you both know more about tracing than I do. We have instrumented pretty much everything in the framework with spans. I think the proposal in this issue/PR (adding some debugging capabilities, and possibly building dev tools from this) is well-suited to tracing: if you collect all the events happening (create a component, create an element, create a signal, update a signal, etc.) you can construct a visualization of the whole application.

Taking a look it seems to me like we've instrumented everything to add spans, but nothing emits any events. So, if a user logs an event you get a nice log (especially with tracing_subscriber_wasm) but otherwise nothing.

However it's very easy to add these sorts of tracing events. If, for example, I go to leptos_dom/src/html.rs:1237 and add

#[cfg(any(debug_assertions, feature = "ssr"))]
tracing::event!(tracing::Level::TRACE, tag = stringify!([<$tag>]), "Creating element");

and use one of our examples with a tracing subscriber, I get a nice TRACE-level message every time an element is created.

  TRACE  Creating element, tag: "main"
    at /Users/gjohnston/Documents/Projects/leptos-main/leptos/leptos_dom/src/html.rs:1329
    in HtmlElement with tag: <main/>
    in <MyComponent />

Presumably events like this could be used for the debugging purposes being described here.

What's not clear to me is whether we'd need to add all these tracing events manually, or whether some of what we've annotated with spans should really events (like getting or setting a signal), or whether it's possible for a subscriber to hook into "you're entering a new span now." I don't know tracing well enough to know the answer.

@gbj
Copy link
Collaborator

gbj commented Jul 25, 2023

Oh actually I think the answer is implementing the Subscriber trait with the new_span method.

@jquesada2016
Copy link
Contributor

@gbj I think it'd be a good idea to approach this from a top-down perspective. If you know what the end goal should look like, it'd be much easier to audit the current API to see if something's missing. Perhaps adding some kind of proof-of-concept design in Figma to see what the leptos debugger would look like would go a long way.

Nevertheless, simply having the tracing spans is enough, no events need to be emitted manually because events are already being emitted for every span entered/exited. Just that by default tracing doesn't log them out, but you can enable this in the tracing_subscriber::fmt builder API. If you're using another tracing subscriber, you'd need to check with that crate's docs to make sure the subscriber has support for showing them (tracing_wasm does not, for example).

So, what would need to happen for something like this to be done at runtime, would be for us to create a custom tracing subscriber that can be added to the tracing_subscriber::registry() in order to allow normal user logging as well as the infrastructure needed for an extension to be able to hook into leptos' runtime representation.

There is one fundamental problem with the current API that I can see, however. This is that components don't emit span enter/leave events unless the component is actually mounted/rendered. This means we'd be able to make tooling for showing a debug view of the app and signals, but only for components that are actually mounted, and would not be able to see the entire app's component tree.

For example:

view! { cx,
  <Content>
    {if signal() { view! { cx, <CompA /> } } else { view! { cx, <CompB /> } }}
  </Content>
}

We'd be able to see the abb structure like:

App -> Content -> CompA -> blah -> lah

but would not be able to show:

App
 |-> Content
       |-> CompA
       |-> CompB

@gbj
Copy link
Collaborator

gbj commented Jul 25, 2023

Right, thanks. This pointed me in the right direction.

@luoxiaozero Here are my steps to get started understanding the power of tracing here:

  1. open the example /leptos_dom/examples/test-bench and open src/main.rs
  2. import some necessary traits + types from tracing and tracing_subscriber
use tracing::{field::debug, span, Subscriber};
use tracing_subscriber::{
    prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt,
};
  1. Create a struct that implements tracing_subscriber::Layer to hook into the on_new_span messages:
struct SpanTracingLayer {}

impl<S: Subscriber> tracing_subscriber::Layer<S> for SpanTracingLayer {
    fn on_new_span(
        &self,
        attrs: &span::Attributes<'_>,
        id: &span::Id,
        ctx: tracing_subscriber::layer::Context<'_, S>,
    ) {
        // this can be whatever you want... for now it just logs
        // eventually it will be used to build up and modify some data structure 
        leptos::log!("on_new_span {attrs:#?}");
    }
}
  1. Add this new Layer to the tracing subscriber
fn main() {
    console_error_panic_hook::set_once();

    tracing_subscriber::fmt()
        // this level can be adjusted to filter out messages of different levels of importance
        .with_max_level(tracing::Level::TRACE)
        .without_time()
        .with_file(true)
        .with_line_number(true)
        .with_target(false)
        .with_writer(tracing_subscriber_wasm::MakeConsoleWriter::default())
        .with_ansi(false)
        .pretty()
        .finish()
        // added it here
        .with(SpanTracingLayer {})
        .init();

    // note: I also renamed `view_fn` to `App` and made it a `#[component]` just so it gets logged
    mount_to_body(App);
}
  1. trunk serve --open

Now you can see every single instrumented call firing!

tracing has lots of different levels of verbosity. TRACE is the "include everything" level. You can tweak it to other levels to filter the flow of messages to what you need.

I'd be very happy to change anything in the tracing instrumentation we have to make it more useful for your purposes. But I think it should be a really good start — it achieves what I think you're trying to do, without adding additional code to the framework.

@benwis
Copy link
Contributor

benwis commented Jul 25, 2023

TBH I need to change several things about tracing. Greg's example is good for CSR, but for SSR there's an issue with the spans getting disconnected inside some of the spawning methods, and there is far, far too much output at too high of a trace level.
@jquesada2016 is right in that it will only show what gets called, but tracing is a good start here. I have no idea how the Rust, Svelte, or Solid graphs/plugins work, but it'd be worth investigating them

@luoxiaozero
Copy link
Contributor Author

When I read the tacing library, I have a question. If I need to collect DOM to implement the function in the picture, can I obtain DOM in the tracing_subscriber::Layer Trait.

v2-7c300dc2cde6975a69872bbdc91828c5_b

@gbj
Copy link
Collaborator

gbj commented Aug 1, 2023

I don't think the DOM nodes are currently included as one of the tracing attributes but they probably could be, for example by adding the element as a field in the instrumentation for HtmlElement<_>::into_view.

@luoxiaozero
Copy link
Contributor Author

Okay, thank you. I already have an idea.

@luoxiaozero
Copy link
Contributor Author

I wrote a PR that tracks component properties. Look #1531.

@gbj gbj closed this as completed Apr 2, 2024
@bicarlsen
Copy link
Contributor

@luoxiaozero are you still working on this?

@luoxiaozero
Copy link
Contributor Author

@bicarlsen Currently, I have shelved it. If you want to do work on this, you can refer to https://github.com/luoxiaozero/leptos-devtools

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