symfony icon indicating copy to clipboard operation
symfony copied to clipboard

[FrameworkBundle][HttpKernel][WebProfilerBundle] Enable profiling commands

Open HeahDude opened this issue 3 years ago • 2 comments

Q A
Branch? 6.2
Bug fix? no
New feature? yes
Deprecations? yes
Tickets Fix #45241
License MIT
Doc PR ~

TLDR;

I've shown a POC of this feature at the Symfony Live Paris last April to some of the core team members (ping @nicolas-grekas, @stof, @lyrixx, @chalasr, @GromNaN). I propose here a new work from scratch addressing the comments I already got and based on Javier's profiler redesign (#47148). Reviews should better be done by commits.

Summary

This PR aims to leverage the profiler and its collectors by using a DebugRequestStack to aggregate data on virtual requests. Such requests are obfuscated by default to avoid side effects. It can feel like a hack... or a pragmatic way to get, without much complexity, tons of useful feedback on what's going on during console execution, from basic info about the command, time/memory metrics, to every existing features already available in HTTP context: events, message dispatching, http requests, emails, serialization, validation, cache, database queries... and so on, all that just out of the box!

Previous work

There were some work to extract the Profiler logic in a dedicated component, that proved to require a lot of complexity and BC breaks in the API:

  • #10374
  • #14809 (see https://github.com/symfony/symfony/pull/14809#issuecomment-313340589)
  • #20502

Screenshots

For now I've focused only on the functional parts.

Search view Screenshot 2022-08-28 at 11 29 25 PM
Command panel Screenshot 2022-08-28 at 11 30 54 PM Screenshot 2022-08-28 at 11 31 08 PM Screenshot 2022-08-28 at 11 31 21 PM Screenshot 2022-08-28 at 11 31 34 PM

If the command is signal-able the following panel will be available: Screenshot 2022-08-28 at 11 31 46 PM

If sub commands are run using $this->getApplication()->run() sub profiles will be shown as for requests: Screenshot 2022-08-28 at 11 31 56 PM

The server tab is the same as in the request panel.

Performance panel Screenshot 2022-08-28 at 11 32 23 PM Screenshot 2022-08-28 at 11 32 32 PM
Failing command The exception panel is shown by default as for requests: Screenshot 2022-08-28 at 11 33 42 PM
Sub command Screenshot 2022-08-28 at 11 33 19 PM
Profile token when verbose (clickable links with compatible terminals) Screenshot 2022-08-28 at 11 26 51 PM

Opt-in config

framework:
    profiler:
        cli:
            enabled: true
            excludes:
                - cache:clear
                - messenger:consume
                - 'some-namespace:'

Future scopes

  • When I've discussed the limitation of profiling long running processes such as messenger:consume with @GromNaN (one of the reasons why I've added an excludes option), he told that it would be nice it we could find a way to profile consumers as well. So I've added an abstract VirtualRequest and a virtualType property to profiles, that will allow to create a MessengerWorkerRequest and a new type of profile with ease in a follow-up PR if the current implementation is accepted.
  • We could add some dedicated casters for input and output in the VarDumper component (/cc @nicolas-grekas)
  • It could be interesting to decorate and collect traces from some helpers (i.e. when running processes)
  • Add a global option in debug to enable/disable the profiler on the fly when running commands (e.g. a negatable --profile flag)

Limitations

  • No sub profiles are created when using $this->getApplication()->find(...)->run() because events (needed by the profiler to hook into) are dispatched from Application::run(), not from Command::run().
  • No profiles are created when killing the command process (i.e. using ctrl-C, should we add a handler to some signals to force saving profiles?
  • Signals as int may not be as useful as they could in the profiler pages, does it worth trying to add a label to some (knowing that some signals can have different constants (labels) with the same int value)?
  • Long running processes should be excluded via configuration to avoid memory leaks

TODO

  • [ ] I've left some todos inside the code for reviewers to share they thought before I try going further
  • [ ] Add a few tests
  • [ ] Get help for the UI (new top nav, svg for the command panel) /cc @javiereguiluz
  • [ ] PR on symfony/recipes to add the new framework.profiler.cli node

HeahDude avatar Aug 28 '22 23:08 HeahDude

Hey!

I think @Seb33300 has recently worked with this code. Maybe they can help review this?

Cheers!

Carsonbot

carsonbot avatar Aug 29 '22 19:08 carsonbot

Rebased, this PR is now reviewable.

HeahDude avatar Sep 13 '22 16:09 HeahDude

I've pushed a new implementation to use a _virtual request attribute instead of a virtual request type.

HeahDude avatar Oct 22 '22 12:10 HeahDude

I've added support for profiling commands interrupted by signals. I've updated the PR description and added a screenshot as well.

HeahDude avatar Oct 22 '22 14:10 HeahDude

@HeahDude did not find time to test on a personal project it but just a raw comment:

given you screenshot in PR desc, is it possible to have the command with its arguments/options being copy on click? like by default the url of the request giving the current profile we are redirected to the url, here we mimic the logic but with the console, thus having the cli il the clipboard

197962426-c85e9f2b-89f6-4312-8e79-1de4ecaa5d19

ℹ️ also I do not know if it is feasible, if the "server" is symfony cli, copy something like:

  • symfony console app-delete-user -v

instead of

  • bin/console app-delete-user -v

94noni avatar Oct 26 '22 07:10 94noni

This would be very useful. Would a similar abstraction become possible for profiling messages processed by the messenger?

Jeroeny avatar Nov 22 '22 14:11 Jeroeny

This would be very useful. Would a similar abstraction become possible for profiling messages processed by the messenger?

@Jeroeny as per PR desc Future scopes, I think yes

94noni avatar Nov 22 '22 15:11 94noni

ℹ️ also I do not know if it is feasible, if the "server" is symfony cli, copy something like:

  • symfony console app-delete-user -v

instead of

  • bin/console app-delete-user -v

I think now you can thanks to https://github.com/symfony-cli/symfony-cli/pull/231

maxhelias avatar Nov 27 '22 16:11 maxhelias

This is an amazing contribution @HeahDude. I hope this is reviewed and merged soon 🙏

I have some ideas related to the UI of this feature. If you agree, I'd prefer to merge this PR and then I can open a new PR with the proposed UI changes to discuss about them.

Thanks!

javiereguiluz avatar Jan 13 '23 12:01 javiereguiluz

Hey, first party CLI profiler support sounds awesome!

I have been using a hacky solution to get CLI/Messenger profiles: https://github.com/sourceability/instrumentation/blob/1.0.1/src/Profiler/SymfonyProfiler.php

Which is really really useful with https://github.com/sourceability/console-toolbar-bundle that renders the profiler toolbar within the terminal. There is a video demo in https://github.com/Sylius/Sylius/pull/12711

118685271-3f385080-b803-11eb-95f0-7d68c0e96857 118682929-321a6200-b801-11eb-8390-90e2c7056c95

adrienbrault avatar Mar 24 '23 10:03 adrienbrault

I want this as well. I think targeting 6.4 is fine, we'll need to adapt the way commands are tracked to take the upcoming Console changes into account anyway.

chalasr avatar Mar 27 '23 16:03 chalasr

I'd like this to be one of the main new features of Symfony 7 (more exactly, 6.4 and 7.0).

Please, let's work together to make this mergeable. Later, I'll send a PR to propose some design tweaks so we can iterate on that too. Thanks!

javiereguiluz avatar Jun 19 '23 10:06 javiereguiluz

Count me in for another round of review/local testing if needed!

94noni avatar Jun 19 '23 13:06 94noni

@HeahDude did not find time to test on a personal project it but just a raw comment:

given you screenshot in PR desc, is it possible to have the command with its arguments/options being copy on click? like by default the url of the request giving the current profile we are redirected to the url, here we mimic the logic but with the console, thus having the cli il the clipboard

197962426-c85e9f2b-89f6-4312-8e79-1de4ecaa5d19

ℹ️ also I do not know if it is feasible, if the "server" is symfony cli, copy something like:

  • symfony console app-delete-user -v

instead of

  • bin/console app-delete-user -v

I've added support for supporting Symfony CLI detection: Screenshot 2023-09-17 at 6 53 40 PM Copy/pasting the command could be done in another PR, I guess we have enough features in this one to review 😅.

HeahDude avatar Sep 17 '23 16:09 HeahDude

The PR is rebased, updated with minor fixes and while getting rid of most limitations from the description which is updated as well.

Following a discussion with @fabpot at the Symfony Live last March, I've dropped the semantic configuration in favor of a global --profile option for opt-in profiling.

The console profiler is still compatible with the only_exceptions config that allows to profile only if the option is set AND if an error occurs, should we drop support for this config as well?

Also, I've leveraged the new SignalMap to display proper labels for handled signals in the profiler page: Screenshot 2023-09-16 at 3 19 38 PM

The exiting logic of handled signals prevents to profile a command when that occurs because signals listeners are run before the command handle them. So I added a dispatch of console.terminate event in those cases which works pretty well. But I guess it is a feature on its own, maybe to be extracted in another PR, WDYT?

I'm ready for another round of testing and reviews :), thanks!

HeahDude avatar Sep 17 '23 17:09 HeahDude

The console profiler is still compatible with the only_exceptions config that allows to profile only if the option is set AND if an error occurs, should we drop support for this config as well?

I would drop this :)

fabpot avatar Sep 18 '23 14:09 fabpot

I love this feature, and IMHO the code is close to being good enough for inclusion in 6.4, but relying on a custom "HTTP" request emulation layer is definitely a hack.

In a future iteration, couldn't we make the debug context independent from an HTTP request? For instance, we could introduce a DebugContextInterface that will be implemented by Request as well as by new specialized classes that will not inherit from everything related to HTTP such as CliDebugContext, MessengerDebugContext...).

dunglas avatar Sep 26 '23 19:09 dunglas

What about @dunglas comments?

fabpot avatar Oct 11 '23 07:10 fabpot

@fabpot all comments addressed, PR rebased, and changelogs updated :).

HeahDude avatar Oct 11 '23 07:10 HeahDude

As discussed privately, this feature is so useful that it should not have to build on hacks. The doors it opens make it worth being implemented it in a way that does not imply to make CLI commands act as HTTP requests; as it goes against the core design of the Console component.

Basically, we think it's time to reconsider https://github.com/symfony/symfony/pull/14809#issuecomment-313340589 and make the web profiler a standalone component that works in non-HTTP contexts, based on the work started there. Also, that component and its integration should probably be named Inspector instead of WebProfiler.

If we agree then this PR can be closed so that @HeahDude and I can work towards the above goal.

chalasr avatar Oct 11 '23 09:10 chalasr

Last commit makes DebugRequestStack internal, and removes its type-hint from everywhere. The introduced code of this PR is debug only, all final and internal. We're free to merge it as is in 6.4 and rework it altogether for 7.1 :).

HeahDude avatar Oct 12 '23 11:10 HeahDude

https://github.com/symfony/symfony/pull/52038 has been merged - rebase unlocked.

chalasr avatar Oct 13 '23 09:10 chalasr

Rebased and squashed.

HeahDude avatar Oct 13 '23 10:10 HeahDude

The « deprecation » label can be removed :).

HeahDude avatar Oct 14 '23 13:10 HeahDude

LGTM after a few changes I just pushed, but I agree with @stof it'd be great to get rid of this strange event-dispatcher + listener logic to start/stop sections. Doable in this PR?

nicolas-grekas avatar Oct 17 '23 12:10 nicolas-grekas

Thank you @HeahDude.

nicolas-grekas avatar Oct 17 '23 13:10 nicolas-grekas

1 and a half year after showing a POC of this feature at the Symfony Live, and 188 comments later, so happy to have this merged.

Thanks to all the reviewers for their challenging reviews 🎉 🥰

HeahDude avatar Oct 17 '23 13:10 HeahDude