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

Feat View hierarchy #1189

Merged
merged 18 commits into from
Jan 10, 2023
Merged

Feat View hierarchy #1189

merged 18 commits into from
Jan 10, 2023

Conversation

marandaneto
Copy link
Contributor

@marandaneto marandaneto commented Dec 14, 2022

📜 Description

View hierarchy as an attachment

💡 Motivation and Context

getsentry/team-mobile#64

💚 How did you test it?

Screenshot 2022-12-15 at 08 30 33

📝 Checklist

  • I reviewed submitted code
  • I added tests to verify changes
  • I updated the docs if needed
  • All tests passing
  • No breaking changes

🔮 Next steps

@marandaneto marandaneto changed the title Feat/remove parameter from manual setup classes (#816) Feat View hierarchy Dec 14, 2022
@github-actions
Copy link
Contributor

github-actions bot commented Dec 14, 2022

Fails
🚫 Please consider adding a changelog entry for the next release.
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

Example:

## Unreleased

- Feat View hierarchy ([#1189](https://github.com/getsentry/sentry-dart/pull/1189))

If none of the above apply, you can opt out of this check by adding #skip-changelog to the PR description.

Generated by 🚫 dangerJS against 82eccb8

@codecov-commenter
Copy link

codecov-commenter commented Dec 14, 2022

Codecov Report

❗ No coverage uploaded for pull request base (v7.0.0@20def04). Click here to learn what that means.
Patch has no changes to coverable lines.

Additional details and impacted files
@@            Coverage Diff            @@
##             v7.0.0    #1189   +/-   ##
=========================================
  Coverage          ?   88.02%           
=========================================
  Files             ?      122           
  Lines             ?     3816           
  Branches          ?        0           
=========================================
  Hits              ?     3359           
  Misses            ?      457           
  Partials          ?        0           

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.


SentryViewHierarchy? toSentryViewHierarchy() {
final rootNode = _toSentryViewHierarchyElement(rootElement);
rootElement.visitChildElements(_visitor(rootNode));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ueman so the result of this for simple screens is quite deep in-depth, and it looks way more deeper in depth than the view hierarchy of the dev tooling, have you noticed that, or did I mess up something?

Copy link
Collaborator

@ueman ueman Dec 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately yes. See getsentry/team-mobile#64 (comment) for some ideas on how to tackle that.

Edit: Copied it over:

The view hierarchy in Flutter is way deeper than those of Android and iOS. Classes of views (views are called widgets in Flutter) prefixed with a _ are private (that's how it works in Dart). It might be worth to ignore private classes since you most probably can't look them up in docs and so on. Public children of private classes should be shown again, though. Private classes are often used to improve the structuring of a widget, so they are kinda an implementation detail. Classes not prefixed with an underscore are public and one should be able to look them up in docs. Ignoring private classes is currently not done in my code. Also, you don't know whether a class is from user code, 3-party code or framework code, since there's no reflection/introspection in Dart. When enabling obfuscation in Flutter, the class names are of course not really human readable, but I guess that's similar for Android/iOS.

In addition to the names of the widgets, you could also add the content of well known widgets like Text and *Buttons, as well as the on screen size and coordinates.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh indeed, missed that, thank you for sharing your thoughts, let me try this out.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the _ check for private classes also works in obfuscated code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are also a few methods that print the tree, but only on debug mode https://docs.flutter.dev/testing/code-debugging#debug-flags-performance

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ueman That does not work when obfuscated is enabled because everything is renamed at compile time.
Still worth keeping this quick workaround; it improves at least when it's disabled.

@marandaneto
Copy link
Contributor Author

marandaneto commented Dec 15, 2022

@markushi @brustolin @krystofwoldrich early feedback is welcomed since it's a working solution already.
Views are really nested, unfortunately.
Unclear what the extra params could be, we have to figure out what's useful besides the default ones.
It's not working on Android due to an issue on the bridge most likely, trying to figure out what's happening during de/serialization on Android.
Edit, issue.

if (instance == null) {
return event;
}
final sentryViewHierarchy = walkWidgetTree(instance);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being not too familiar with Flutter, it would be interesting to know:

  • how fast walking the tree is
  • if there are any multi-threading concerns (e.g. walking the tree whilst it get's modified)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dart is single-threaded, so no multi-threading concern, its also a sync call.
Running on debug mode (Profile mode isn't supported on iOS simulator, Debug is much slower) takes ~30ms, for our sample app/main screen, this is dynamic because it depends on the app's view, so hard to measure.
Final JSON around ~50-60kb

Copy link
Collaborator

@ueman ueman Dec 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there are any multi-threading concerns (e.g. walking the tree whilst it get's modified)

I actually think that's a valid concern because that's also a common problem when creating screenshots. Sometime you'll get an error which says it's not possible to create a screenshot because the current on screen content was already invalidated. But I also think it's something not a lot of people will run into, since it should be fast enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature is best effort, if for some reason you can't read the tree or take the screenshot, that's fine, there will be always a state that the framework won't allow you to do it, and we don't have control over that.

@markushi
Copy link
Member

Looks good to me, nice one!

Regarding the deep nesting: I'm wondering if it could make sense to aggregate certain "known" widgets.
E.g.

{
  "opacity": 0.5,
  "children": [{
    "visible": true,
    "children": [{
      "identifier": "button_login"
    }]
  }]
}

turns into

{
  "identifier": "button_login",
  "opacity": 0.5,
  "visible": true,
}

@marandaneto
Copy link
Contributor Author

Looks good to me, nice one!

Regarding the deep nesting: I'm wondering if it could make sense to aggregate certain "known" widgets. E.g.

{
  "opacity": 0.5,
  "children": [{
    "visible": true,
    "children": [{
      "identifier": "button_login"
    }]
  }]
}

turns into

{
  "identifier": "button_login",
  "opacity": 0.5,
  "visible": true,
}

I believe this should be an optimization for the FE, where we can fix/improve over time rather than changing on the SDK level and waiting for people to upgrade their SDKs before getting a meaningful view, but I agree whenever possible, the super nested view should be improved when debugging issues, but the SDK should report as it is.

@marandaneto
Copy link
Contributor Author

@krystofwoldrich its a Draft because there's a bug on Relay, but its ready to review, test are failing locally, working on them.

@markushi
Copy link
Member

markushi commented Dec 21, 2022

I believe this should be an optimization for the FE

I agree, that's something which could be done on FE! Looping in @narsaynorath

@krystofwoldrich
Copy link
Member

The CI is failing but otherwise looks good to me.

@marandaneto
Copy link
Contributor Author

The CI is failing but otherwise looks good to me.

CI should fail, that's expected (just the analyze) step.

@marandaneto
Copy link
Contributor Author

Docs: getsentry/sentry-docs#5986

@narsaynorath
Copy link
Member

Is the event from your sample screenshot available in prod? I'd be interested in seeing how deep it actually ends up being. Seeing it would also help me understand the aggregated optimization mentioned above.

When this example was provided:

{
  "opacity": 0.5,
  "children": [{
    "visible": true,
    "children": [{
      "identifier": "button_login"
    }]
  }]
}

is this form the result of ignoring the private views? And the idea to aggregate the children is because the private views will show up in the data as nodes, but without anything meaningful since we're ignoring them? Is there a chance that a private view contains two (or more) children or is it always a 1:1 relationship in those cases?

@marandaneto
Copy link
Contributor Author

Is the event from your sample screenshot available in prod? I'd be interested in seeing how deep it actually ends up being. Seeing it would also help me understand the aggregated optimization mentioned above.

When this example was provided:

{
  "opacity": 0.5,
  "children": [{
    "visible": true,
    "children": [{
      "identifier": "button_login"
    }]
  }]
}

is this form the result of ignoring the private views? And the idea to aggregate the children is because the private views will show up in the data as nodes, but without anything meaningful since we're ignoring them? Is there a chance that a private view contains two (or more) children or is it always a 1:1 relationship in those cases?

Shared the JSON in the channel a few weeks ago.
Yes, private views are ignored, but it only works if minification isn't enabled.
I can't tell if it's always a 1:1 relationship, views are super nested, so I'd assume it's not.

@marandaneto marandaneto marked this pull request as ready for review January 10, 2023 10:09
@marandaneto
Copy link
Contributor Author

@krystofwoldrich @brustolin this is ready for final review.

Copy link
Member

@krystofwoldrich krystofwoldrich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉🚀🚀

I know I asked about the failing analyzer before, but why does it fail/why is it expected?

@marandaneto
Copy link
Contributor Author

LGTM 🎉🚀🚀

I know I asked about the failing analyzer before, but why does it fail/why is it expected?

You can ignore this, it will be automatically solved once it's published.

@marandaneto marandaneto merged commit 1275c1e into v7.0.0 Jan 10, 2023
@marandaneto marandaneto deleted the feat/view_hierarchy branch January 10, 2023 14:40
double? alpha;

// Widget has to be RenderBox to have a size
if (widget is RenderBox) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @marandaneto, is it possible that this should be
if (element.renderObject is RenderBox) {
instead?
@narsaynorath is seeing elements in the View Hierarchy which have a x/y coordinate but no width/height.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, #1258

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants