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: Add toggle option for xhprof command, rework xdebug, fixes #5782 #6082

Merged
merged 2 commits into from
May 16, 2024

Conversation

GuySartorelli
Copy link
Collaborator

@GuySartorelli GuySartorelli commented Apr 11, 2024

The Issue

The xdebug command has a toggle option, but xhprof doesn't. They're both really similar in their operation so it makes sense to add a ddev xhprof toggle option.

How This PR Solves The Issue

Adds the option.

Also some unrelated-but-adjacent refactoring of xdebug, which was pretty convoluted.

Manual Testing Instructions

  • Try ddev xhprof toggle and validate the status with ddev xhprof status
  • Check that hitting tab after typing ddev xhprof gives you toggle amongst the other options.
  • Try ddev xdebug toggle and ddev xdebug status as a sanity check for the refactor

Automated Testing Overview

Existing tests should cover the xdebug refactoring.

There's no tests at all for xhprof that I could find, so adding them seems out of scope. Happy to add if maintainers disagree with that assessment though.

Related Issue Link(s)

Release/Deployment Notes

N/A

@rfay
Copy link
Member

rfay commented Apr 11, 2024

See

// TestDdevXhprofEnabled tests running with xhprof_enabled = true, etc.
func TestDdevXhprofEnabled(t *testing.T) {
if runtime.GOOS == "darwin" {
// TODO: Return to this when working on xhprof xhgui etc.
t.Skip("Skipping on darwin to ignore problems with 'connection reset by peer'")
}
assert := asrt.New(t)
origDir, _ := os.Getwd()
testcommon.ClearDockerEnv()
projDir := testcommon.CreateTmpDir(t.Name())
app, err := ddevapp.NewApp(projDir, false)
require.NoError(t, err)
app.Type = nodeps.AppTypePHP
err = app.WriteConfig()
require.NoError(t, err)
// Create the simplest possible php file
err = fileutil.TemplateStringToFile("<?php\nphpinfo();\n", nil, filepath.Join(app.AppRoot, "index.php"))
require.NoError(t, err)
runTime := util.TimeTrackC(fmt.Sprintf("%s %s", app.Name, t.Name()))
// Does not work with php5.6 anyway (SEGV), for resource conservation
// skip older unsupported versions
phpKeys := []string{}
exclusions := []string{"5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0"}
for k := range nodeps.ValidPHPVersions {
if !nodeps.ArrayContainsString(exclusions, k) {
phpKeys = append(phpKeys, k)
}
}
sort.Strings(phpKeys)
// Most of the time we can just test with the default PHP version
if os.Getenv("GOTEST_SHORT") != "" {
phpKeys = []string{nodeps.PHPDefault}
}
err = app.Init(app.AppRoot)
require.NoError(t, err)
t.Cleanup(func() {
err = app.Stop(true, false)
assert.NoError(err)
err = os.Chdir(origDir)
assert.NoError(err)
_ = os.RemoveAll(projDir)
})
webserverKeys := make([]string, 0, len(nodeps.ValidWebserverTypes))
for k := range nodeps.ValidWebserverTypes {
if k == nodeps.WebserverNginxGunicorn {
continue
}
webserverKeys = append(webserverKeys, k)
}
// Most of the time we can just test with the default webserver_type
if os.Getenv("GOTEST_SHORT") != "" {
webserverKeys = []string{nodeps.WebserverDefault}
}
for _, webserverKey := range webserverKeys {
app.WebserverType = webserverKey
for _, v := range phpKeys {
t.Logf("Beginning XHProf checks with XHProf webserver_type=%s php%s\n", webserverKey, v)
fmt.Printf("Attempting XHProf checks with XHProf PHP%s\n", v)
app.PHPVersion = v
err = app.Restart()
require.NoError(t, err)
stdout, _, err := app.Exec(&ddevapp.ExecOpts{
Service: "web",
Cmd: "php --ri xhprof",
})
require.Error(t, err)
assert.Contains(stdout, "Extension 'xhprof' not present")
// Run with Xhprof enabled
_, _, err = app.Exec(&ddevapp.ExecOpts{
Cmd: "enable_xhprof",
})
assert.NoError(err)
stdout, _, err = app.Exec(&ddevapp.ExecOpts{
Service: "web",
Cmd: "php --ri xhprof",
})
require.NoError(t, err)
assert.Contains(stdout, "xhprof.output_dir", "xhprof is not enabled for %s", v)
out, _, err := testcommon.GetLocalHTTPResponse(t, app.GetPrimaryURL(), 2)
require.NoError(t, err, "Failed to get base URL webserver_type=%s, php_version=%s", webserverKey, v)
require.Contains(t, out, "module_xhprof")
out, _, err = testcommon.GetLocalHTTPResponse(t, app.GetPrimaryURL()+"/xhprof/", 2)
require.NoError(t, err)
// Output should contain at least one run
assert.Contains(out, ".ddev.xhprof</a><small>")
// Disable all to avoid confusion
_, _, err = app.Exec(&ddevapp.ExecOpts{
Cmd: "disable_xhprof && rm -rf /tmp/xhprof",
})
require.NoError(t, err)
}
}
runTime()
}
for TestDdevXhprofEnabled.

Your IDE should have been able to find this! :)

@rfay
Copy link
Member

rfay commented Apr 11, 2024

Does this mean that you often use xhprof?

@GuySartorelli
Copy link
Collaborator Author

Does this mean that you often use xhprof?

I almost never use it, but if there's anyone using DDEV who does a lot of profiling, I could see this being helpful - while at the same time it adds next to no additional maintenance.

@GuySartorelli
Copy link
Collaborator Author

Your IDE should have been able to find this! :)

If I had done a blanket search for xhprof it would have - I was just expecting a separate file like xdebug has since the commands are basically identical.

Looking at that test, it's testing the underlying enable_xhprof and disable_xhprof functions, not the ddev xhprof command itself. Unless I missed something.

@rfay
Copy link
Member

rfay commented Apr 11, 2024

The xhprof command just runs enable_xhprof, etc.

I found the test by using symbol search and typing TestXhprof and it brought it right up (Goland)

@GuySartorelli
Copy link
Collaborator Author

The xhprof command just runs enable_xhprof, etc.

By that argument there's no need for me to update any tests, because that's still what it's doing. That doesn't test whether the existing ddev xhprof status command outputs correctly or whether this new toggle option works as expected - but as I say, that's a completely different test than the one that currently exists.

@GuySartorelli
Copy link
Collaborator Author

I'm happy to add testing coverage for the actual ddev xhprof command itself if you want - just noting that that's not what the current test covers, which is why I didn't add tests for the toggle option there.

@rfay
Copy link
Member

rfay commented Apr 11, 2024

It's all good, no need to go further IMO. Maybe when we integrate xhprof more thoroughly later this year with integrating the ddev-xhgui behavior.

@rfay rfay changed the title feat: Add toggle option for xhprof command, fixes #5782 feat: Add toggle option for xhprof command, rework xdebug, fixes #5782 Apr 16, 2024
@rfay rfay requested a review from stasadev April 29, 2024 14:14
Copy link
Member

@stasadev stasadev left a comment

Choose a reason for hiding this comment

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

Tested, looks good to me 👍

P.S. Nice refactoring for the xdebug command, thanks!

@rfay rfay added this to the v1.23.1 milestone May 3, 2024
@rfay
Copy link
Member

rfay commented May 15, 2024

In testing this PR I did ddev xdebug toggle a number of times. When xdebug was not enabled, it did the right thing. But often when xdebug was enabled, it would hang. The hang is in php -r 'echo ini_get("xdebug.mode");'.

I was testing on macOS, and where this happened I was on php 8.2

  • It has nothing to do with this PR. I tested with HEAD and with v1.23.0 and saw the same.
  • It doesn't matter whether mutagen is enabled or not
  • It happens on xdebug 3.2.2 or other
  • ddev xdebug status hangs for the same reason when xdebug is enabled

@GuySartorelli
Copy link
Collaborator Author

GuySartorelli commented May 15, 2024

Ohhh, interesting. I guess that's 'cause in order to get the xdebug status, we're executing php code while xdebug is enabled - and xdebug is trying to connect to an IDE or anything that'll listen, and not finding anything to connect to?

Probably we should use php -i, or check the INI file(s) directly, or something else that doesn't require executing code via PHP to get the status.

@GuySartorelli
Copy link
Collaborator Author

GuySartorelli commented May 15, 2024

php --ri xdebug will exit with a 0 status if xdebug is enabled, and a non-0 status if it's disabled, so that's another option.
That should work regardless of xdebug version too, so would simplify the command slightly.

@rfay
Copy link
Member

rfay commented May 15, 2024

You're right, congratulations! This doesn't happen if PhpStorm IDE isn't listening!

@rfay
Copy link
Member

rfay commented May 15, 2024

I think that php --version, which is widely used in tests and such is probably an extremely robust technique. php --version | grep xdebug for example.

@rfay
Copy link
Member

rfay commented May 15, 2024

Followups in

@rfay rfay requested a review from tyler36 May 16, 2024 00:05
Copy link
Member

@rfay rfay 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 fine, thanks so much, and sorry for the long wait.

I've asked our local xhprof expert, @tyler36, to take a quick look, but this can be pulled in the morning if he either has no objections or can't get to it.

else
enable_xdebug
echo "0"
fi
;;
v2*)
Copy link
Member

Choose a reason for hiding this comment

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

Wow, I forgot all about Xdebug v2. But I guess we still support several PHP versions that can only have that.

Comment on lines +24 to +25
status=$(php -m | grep 'xhprof')
if [ "${status}" = "xhprof" ]; then
Copy link
Member

Choose a reason for hiding this comment

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

This could be simpler with

if php -m | grep xhprof >/dev/null; then

but it's fine the way it is.

@tyler36
Copy link
Collaborator

tyler36 commented May 16, 2024

XHprof works as expected. No conflicts with tyler36/ddev-xhgui.
xdebug also continues to function after "rework".

Seems good to me.

Test

  1. Confirm DDEV version
$ ddev -v                                                          
ddev version v1.23.0-beta1-23-gbb51e6fc8
  1. Start a new Drupal 10 project following quick start guide.
  2. Toggle xhprof
$ ddev xhprof toggle
Enabled xhprof.
After each web request or CLI process you can see all runs, most recent first, at
https://xhprof-test.ddev.site/xhprof
  1. Visit https://xhprof-test.ddev.site/xhprof to confirm working.
  2. Refresh page to confirm XHprof is generating content

Test with tyler36/ddev-xhgui

  1. Install tyler36/ddev-xhgui addon
ddev get tyler36/ddev-xhgui
ddev restart
  1. Following Drupal instructions, add perftools/php-profiler
ddev composer require perftools/php-profiler --dev
  1. Following Drupal instructions, update web/sites/default/settings.php
if (file_exists("/mnt/ddev_config/xhgui/collector/xhgui.collector.php")) {
 require_once "/mnt/ddev_config/xhgui/collector/xhgui.collector.php";
}
  1. Toggle xhprof
ddev xhprof toggle
  1. Launch XHGui page
ddev xhgui
  1. Visit main Drupal page and confirm reports are generated in XHGui.

Test Xdebug still functions

  1. Add breakpoint to web/index.php on lin 16: $kernel = new DrupalKernel('prod', $autoloader);
  2. Start xdebug listening in Vscode
  3. Toggle xdebug on
ddev xdebug on
  1. Attempt to homepage; page does NOT finish loading as breakpoint is hit (expected behavior).

@rfay rfay merged commit 354feef into ddev:master May 16, 2024
28 checks passed
@GuySartorelli GuySartorelli deleted the 20240411_GuySartorelli_xhprof-toggle branch May 16, 2024 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants