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(devtools): Use Chrome DevTools Performance extension API #55805

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

and-oli
Copy link

@and-oli and-oli commented May 15, 2024

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.io application / infrastructure changes
  • Other... Please describe:

What is the new behaviour?

Hi Angular folks! 👋

This change is a proof of concept of how the new Chrome DevTools Performance extension API can be used to surface Angular runtime data directly in the Chrome DevTools Performance panel.

Specifically, it implements the following changes:

1. Use the profiling status notification API to toggle the Timing API:

The notification API is implemented under the chrome.devtools.performance extension namespace and consits of two events: ProfilingStarted and ProfilingStopped, dispatched when the Performance panel has started and stopped recording, respectively. This API is used to enable the Timings API in Angular when the recording has started in the Performance panel and disable it when recording has stopped. This enables change 2:

2. Use the User Timings detail field format specification of the Performance extension API to inject Angular runtime data into the Performance panel timeline.

The Angular profiler uses several hooks to measure framework tasks like change detection. With this change, these measurements are visible in the same context as the runtime data collected by the browser in the Performance Panel timeline.

You can see an example of how this data would look like in the following image:

image

Note: right now, to enable the user timings to be collected in the first place, one needs to open the Angular DevTools panel before recording with the Chrome Devtools Performance panel so that the related artifacts are loaded in the page. This shortcoming can be fixed in a follow up so that the extra step isn't necessary.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Why?

We (the Chrome Page Quality team) think allowing developers to extend the Peformance Panel can significantly offer a better experience for developers looking to improve performance to the current solutions available (think of fragmented workflows due to context switching between tools).

We aim to collect feeback about the API and explore further potential use cases where the API (or an expansion of it) would offer value in the web ecosystem. Doing this in collaboration with the Angular team would be invaluable, so please let us know your thoughts!

This change is a proof of concept of how the new Chrome DevTools
Performance extension API (https://bit.ly/rpp-e11y) can be used to
surface Angular runtime data directly in the Chrome DevTools Performance
panel.

Specifically, it implements the following changes:

1. Use the profiling status notification API to toggle the Timing API:
The notification API is implemented under the
chrome.devtools.performance extension namespace and consits of two
events: ProfilingStarted and ProfilingStopped, dispatched when the
Performance panel has started and stopped recording, respectively. This
API is used to enable the Timings API when the recording has started in
the Performance panel and disable it when recording has stopped.

2. Use the User Timings `detail` field format specification of the
Performance extension API
(https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/User_timing)
to inject data collected by the Angular Profiler into the
Performance panel timeline. Angular Profiler uses several hooks to
measure framework tasks like change detection. With this change, this
measurements are visible in the same context as the runtime data
collected by the browser in the Performance Panel timeline.

Note: to enable the user timings to be collected in the first place, one
needs to open the Angular DevTools panel so that the related artifacts
are loaded in the page. This shortcoming can be fixed in a follow up so
that the extra step isn't necessary.
@pullapprove pullapprove bot requested a review from dgp1130 May 15, 2024 10:37
@angular-robot angular-robot bot added the detected: feature PR contains a feature commit label May 15, 2024
Copy link
Contributor

@dgp1130 dgp1130 left a comment

Choose a reason for hiding this comment

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

Thanks for putting this together! This is super interesting and a very cool feature for Angular.

@AleksanderBodurri can you take a look at this? The PR seems fine to me, but the UX implications of this feature might be worth thinking more deeply about.

Also CC @AndrewKushnir who might be interested in this.

Some UX thoughts:

  • We already have a "Profiler" tab in Angular DevTools with our own UI to understand change detection. That gives much more information than we probably can in the Performance tab in Chrome, but I do think this is a useful feature just so we can put Angular side-by-side with everything else going on in the page. This way a user might notice a dropped frame in the Performance tab and be able to easily correlate it with a particular Angular change detection cycle.
  • It would be very cool if we could somehow link the change detection cycle displayed in this timings API to the "Profiler" tab in Angular DevTools as a way of linking the user to more details about that particular cycle. It doesn't seem like this is possible based on the doc you linked, but even then we don't capture these metrics unless the user clicks "Record" in the "Profiler" tab, so it would take some effort to do that automatically a link things up.
  • We could consider a performance.mark call for when the app is hydrated, I think that would be useful to see how hydration is contributing to your page's lifecycle.
  • For partial hydration, we could maybe mark for each @defer which gets hydrated, though that might be a little noisy for large apps.
  • What should the actual Angular track show and what could it point to? The obvious answer here is showing change detection cycles, but what would be good to show in details or provide as help text? Should we color long CD cycles yellow or red to flag?
  • We could also include hydration work in this timeline as well, but I don't think we're at the point yet where users really need to think about optimizing hydration.

Most of these are ideas we can follow up with in the future rather than hard blockers for this PR, I'm just trying to think through the full implications of this. I'll be OOO for a bit, but when I get back I'll install this build and try it out to see how the UX shakes out in practice and what we should be thinking about to improve it.

},
},
};
performance.measure(name, measureOptions);
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: Does this necessarily need to be done in Angular DevTools? Could we put these options in performance.measure calls directly in the Angular framework and support users who don't have it installed?

I'm not sure if that would be the worth the bundle size impact (we could gate this on ngDevMode so they're supported in development) but I want to make sure that's something we're considering at least.

Copy link
Author

Choose a reason for hiding this comment

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

Great question! It can also happen at the framework level so that having the extension installed isn't necessary.
For the sake of demonstrating its usage, implementing the call in the Angular profiler is practical because that way calls are done in a single place. Also, we have access to the ProfilingStarted and ProfilingStopped extension events to enable/disable the calls (to save the potential overhead when not profiling).

If this is done at the framework level I can imagine the implementation could be more complex depending on the tasks that are profiled and how the gate is implemented (with ngDevMode or otherwise). However, it could be well worth it if developers don't need to install an extension to profile their Angular app and can use Chrome DevTools right off the bat

Copy link
Contributor

Choose a reason for hiding this comment

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

@pkozlowski-opensource would probably be the authority on whether it makes sense to gate this on ngDevMode. I think it's worth considering to see if we can provide this feature without requiring Angular DevTools to be installed, but we can follow up with that separately. Since DevTools gets access to the ProfilingStarted and ProfilingStopped events, it makes sense to do this through DevTools for now. Doing this directly in the framework is a potential future improvement we can consider.

@ngbot ngbot bot added this to the Backlog milestone May 22, 2024
});
chromeDevToolsPerformance?.onProfilingStopped.addListener(() => {
this._messageBus.emit('disableTimingAPI');
});
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need to remove these listeners in ngOnDestroy? Otherwise this would be a memory leak whenever the component is recreated?

},
},
};
performance.measure(name, measureOptions);
Copy link
Contributor

Choose a reason for hiding this comment

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

@pkozlowski-opensource would probably be the authority on whether it makes sense to gate this on ngDevMode. I think it's worth considering to see if we can provide this feature without requiring Angular DevTools to be installed, but we can follow up with that separately. Since DevTools gets access to the ProfilingStarted and ProfilingStopped events, it makes sense to do this through DevTools for now. Doing this directly in the framework is a potential future improvement we can consider.

extensionName: 'Angular',
},
color: 'primary',
track: '🅰️ Angular Extension',
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: If you're referring to the DevTools extension here, then I think the proper name would be "Angular DevTools" based on how we typically brand it.

dataType: 'track-entry',
extensionName: 'Angular',
},
color: 'primary',
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider: Should we make the color conditional on end - start? Values close to or exceeding the frame rate could be red, shorter values yellow or green? We can also investigate that in a follow up PR, so it doesn't necessarily need to be in this one.

Copy link
Member

@AleksanderBodurri AleksanderBodurri left a comment

Choose a reason for hiding this comment

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

This is awesome 🙏 Thank you for the PR

I wonder if there are any incompatibilities with these changes and non-chrome browsers? Notably Angular DevTools also supports Firefox

// workaround to prevent TypeScript failures while the cooresponding
// type is added upstream.
const chromeDevToolsPerformance = (chrome.devtools as any).performance;
chromeDevToolsPerformance?.onProfilingStarted.addListener(() => {
Copy link
Member

Choose a reason for hiding this comment

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

We should either change this to chromeDevToolsPerformance?.onProfilingStarted?.addListener?.(...) or wrap these in an if entirely

if (chromeDevToolsPerformance !== undefined) {
  // setup listeners
  ...
}

Copy link
Author

Choose a reason for hiding this comment

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

I wonder if there are any incompatibilities with these changes and non-chrome browsers? Notably Angular DevTools also supports Firefox

Not at the moment. However, we are currently exploring the standardization options for API. This could likely require us to encourage other browser vendors to get involved and work together in an API implementation across browsers.

Incorporate feedback form previous commit:

Remove listeners when component is distroyed
Rename track name to Angular DevTools
Use optional chaining deeper in chrome devtools performance namespace
@and-oli
Copy link
Author

and-oli commented Jun 7, 2024

Please take another look. Added a new commit that tackles the feedback and ensures that the flag to enable user timings set by the user in Angular DevTools, remains unaffected after recording with the browser Performance panel.

Copy link
Contributor

@dgp1130 dgp1130 left a comment

Choose a reason for hiding this comment

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

I recommend squashing these commits (or marking additions as fixup commits) so that when we merge this into the repo we can keep a clean history.

ngOnDestroy(): void {
const chromeDevToolsPerformance = (chrome.devtools as any).performance;
chromeDevToolsPerformance?.onProfilingStarted?.addListener?.(this.onProfilingStartedListener);
chromeDevToolsPerformance?.onProfilingStopped?.addListener?.(this.onProfilingStoppedListener);
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: I think you intended this to be removeListener?

// restore keep the value of the timings API flag as the user had set it
// in Angular DevTools before the recording started.
export const disableTimingAPI = (forBrowserProfiler: boolean = false) =>
(timingAPIFlag = forBrowserProfiler && lastTimingAPIFlagStatus);
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: I don't think I understand the goal here. Why do we need to restore this? Is it to handle the case where the user records in both the Chrome Performance panel and Angular DevTools Profiler panel?

}).bind(this);
private onProfilingStoppedListener = (() => {
this._messageBus.emit('disableTimingAPI');
}).bind(this);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Do we need .bind(this)? I think the arrow function will already inherit the correct this value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: devtools detected: feature PR contains a feature commit
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants