Problem/Motivation

We've been working for many months now to improve Drupal 8 performance. But not only is measuring (i.e. "profiling") it hard, it's also hard — even for the most prolific contributors — to not accidentally introduce performance regressions. Currently, it's trivial to introduce performance regressions that go unnoticed: additional services being added for one purpose may in fact be initialized always (even when unnecessary — see #1973618-27: DIC: Lazy instantiation of service dependencies (ProxyManager for "proxy services")), additional JS assets that are loaded accidentally (e.g. #2160555: Disable Edit on admin pages), and so on.

The only way (currently) to see if a patch negatively impacts performance, is by performing many manual steps:

  1. XHProf-based profiling requires a lot of labor, hence very few people use it
  2. alternatively: webprofiler.module, which is a lot simpler, but very few people use it and it's often broken due to changes in HEAD
  3. generating content is very cumbersome: either you have to do it manually, or you have to wait for devel_generate to be fixed (it, too, is often broken due to changes in HEAD)

Not to mention that actually making proper comparisons is extremely hard.

It's nigh impossible to do it with XHProf unless you have a computer with a constant, controlled environment — results vary wildly with the environment (hardware, PHP version, system load, etc.)! On top of that: if we're comparing a specific, approximately-real-world-scenario, you need generated content, and it's currently very painful to generate the exact same content in different versions of Drupal 8 (due to changes in HEAD).

Some profiling is better than none: increasing awareness and accessibility

In order for Drupal 8's performance to only move in the general right direction performance-wise from now on, we need to:

  1. increase awareness and accessibility: when people post a patch that affects performance (in the positive or negative sense), they should be notified
  2. provide a default set of profiling scenarios in core, so that it's easy for anybody to profile the same scenarios on their systems — this also helps developers onboard to more advanced profiling

Ideally, we'd have an interface to install Drupal (#2115533: Add InstallerInterface to provide a stable non-interactive installer) as well as an API to generate content. Then, anyone could write scripts for the scenarios they care about. But, that's a massive undertaking (which msonnabaum eventually had to give up on) and it still doesn't help with the above points.

Also ideally, we'd be able to automatically run XHProf (but alas, it's never installed by default and in fact for a few months you've needed a custom build of XHProf for it to work with Drupal 8 — none of the packaged XHProf versions work with PHP 5.5 + Drupal 8!), and even more ideally we'd be able to get the number of CPU instructions it took to render a page. Neither of those are achievable. And even if they were, we'd still not have apples-to-apples comparable numbers, because the results depend on the environment (hardware, PHP version, etc.)

In an überideal world, we'd have a PerfBot running in tandem with TestBot. But creating the infrastructure for this is non-trivial, especially because there are more strict environment requirements for a PerfBot, if we want its results to actually be correct.

What is achievable, is measuring numbers that don't depend on the environment and still are good performance indicators: number of CSS/JS assets, cache/config/state gets, DB queries, initialized services, rendered blocks … And to measure those things, we can simply build on top of what we already have in core: http://api.symfony.com/2.4/Symfony/Component/HttpKernel/Profiler/Profiler.htmlSymfony's Profiler! (This is also what Web Profiler uses.)

Yes, those numbers should be interpreted with care as well. There is nothing inherently bad about the number of services or the number of cache gets for a certain response. But it usually is bad when those numbers increase, and all too often that happens without us noticing. So that is what this issue wants to provide. And at the same time, we get profiling scenarios in core that we can

Proposed resolution

Therefore, this issue proposes a simpler set of steps:

  1. create a 'Profiling' install profile that reuses the 'Standard' profile but adds generated content to it — being done in #2497185: [no patch] Create standardized core profiling scenarios and start tracking metrics for them
  2. this new install profile would include a ProfilingTest (much like the 'Standard' profile has StandardTest), but instead of testing whether things work as expected (which is already covered by the current body of tests), it enables a hidden 'Simple Profiler' module (part of the 'Profiling' profile), which compares the actual number of cache gets, config gets, initialized services … with the expected numbers for various scenarios — initially this will only test peak memory, also being done in #2497185: [no patch] Create standardized core profiling scenarios and start tracking metrics for them
  3. this 'Simple Profiler' module would be a very barebones, no-UI version of Web Profiler, and it would leverage Symfony's Profiler and DataCollector (just like Web Profiler)
  4. the test would mark every status quo or improvement as a "pass" and would mark each regression (by 5% more than the expected peak memory, or more than the exact number of initialized services, other metrics TBD). This is how every patch author would be notified of performance regressions.

This is what that looks like (in the old iteration of this patch that requires precise matches, and is this much more of a PITA — screenshot to be updated):

Remaining tasks

  1. Discuss the general idea.
  2. If we want to go forward with this, we'll also want to discuss what the specific profiling scenarios should be. We can always add more though!Done at #2497185-20: [no patch] Create standardized core profiling scenarios and start tracking metrics for them.
  3. Step one: 4 scenarios, very minimal metric tracking: #2497185: [no patch] Create standardized core profiling scenarios and start tracking metrics for them
  4. Step two: DB queries metric, issue to be created, see #19
  5. Step three: initialized services metric, issue to be created, see #19

User interface changes

None.

API changes

None.

CommentFileSizeAuthor
#90 interdiff.txt6.63 KBsorressean
#90 profiling-2308683-90.patch65.73 KBsorressean
#87 interdiff.txt822 bytessorressean
#87 profiling-2308683-87.patch64.41 KBsorressean
#84 interdiff.txt2.19 KBsorressean
#84 profiling-2308683-84.patch64.41 KBsorressean
#79 interdiff.txt4.33 KBsorressean
#79 profiling-2308683-79.patch64.72 KBsorressean
#66 profiling-2308683-64.patch67.48 KBsorressean
#66 profiling-2308683-64.patch67.48 KBsorressean
#66 profiling-2308683-64.patch67.48 KBsorressean
#66 profiling-2308683-64.patch67.48 KBsorressean
#66 profiling-2308683-64.patch67.48 KBsorressean
#66 profiling-2308683-64.patch67.48 KBsorressean
#66 profiling-2308683-64.patch67.48 KBsorressean
#66 profiling-2308683-64.patch67.48 KBsorressean
#66 profiling-2308683-64.patch67.48 KBsorressean
#66 profiling-2308683-64.patch67.48 KBsorressean
#64 profiling-2308683-64.patch67.48 KBsorressean
#64 interdiff.txt1.36 KBsorressean
#60 profiling-2308683-59.patch67.45 KBsorressean
#59 interdiff.txt546 bytessorressean
#56 profiling-2308683-55.patch67.45 KBsorressean
#55 interdiff.txt5.42 KBsorressean
#51 interdiff.txt2.52 KBwim leers
#51 profiling-2308683-51.patch70.68 KBwim leers
#50 interdiff.txt4.31 KBwim leers
#50 profiling-2308683-50.patch70.33 KBwim leers
#49 interdiff.txt8.08 KBwim leers
#49 profiling-2308683-49.patch68.21 KBwim leers
#44 profiling-10027765-43.patch61.83 KBsorressean
#43 interdiff.txt3.05 KBsorressean
#43 profiling-10027765-21.patch12.06 KBsorressean
#40 profiling-10027765-40.patch60.25 KBsorressean
#37 profiling-10027765-37.patch60.48 KBsorressean
#35 interdiff.txt3.97 KBwim leers
#35 profiling_profile-2308683-35.patch60.22 KBwim leers
#31 interdiff.txt805 bytessorressean
#31 profiling-10027765-31.patch60.2 KBsorressean
#28 interdiff.txt928 bytessorressean
#28 profiling-10027765-28.patch60.22 KBsorressean
#25 interdiff.txt52.17 KBsorressean
#25 profiling-10027765-25.patch60.25 KBsorressean
#21 interdiff.txt64.59 KBsorressean
#21 profiling-10027765-21.patch12.06 KBsorressean
#1 profiling_profile-2308683-1-without_config_files-do-not-test.patch34.36 KBwim leers
#1 profiling_profile-2308683-1.patch66.75 KBwim leers
profilingtest_output.png36.4 KBwim leers

Comments

wim leers’s picture

Assigned: Unassigned » wim leers
Status: Active » Needs review
StatusFileSize
new66.75 KB
new34.36 KB

Note: all the DataCollectors and service wrappers were essentially copy/pasted from https://www.drupal.org/project/webprofiler. Many thanks to lussoluca and dawehner for Web Profiler!

The 'Profiling' profile's default config is identical to this:

cp profiles/standard/config/install/* profiles/profiling/config/install/
cp profiles/testing/config/install/* profiles/profiling/config/install/

I've attached a *-without_config_files-do-not-test.patch file for easier reviewing.


For reviewers:

  1. we can add more data collectors, but I kept it to 3 simple/clear/cleanly implementable ones for this initial patch: cache, config and services
  2. the default content is being generated in profiling.install
  3. the profiled scenarios are these:
      /**
       * Profiles various scenarios, compares with the expected profiling data.
       */
      public function testScenarios() {
        $this->profileScenario('anon, front page, cold cache', '', new ExpectedProfilingData(73, 70, 67, 210));
        $this->profileScenario('anon, front page, warm cache', '', new ExpectedProfilingData(84, 0, 34, 194));
        $this->profileScenario('anon, 404, cold cache', 'non-existing-relative-url-that-will-result-in-a-404', new ExpectedProfilingData(15, 0, 26, 167));
        $this->profileScenario('anon, 404, warm cache', 'non-existing-relative-url-that-will-result-in-a-404', new ExpectedProfilingData(15, 0, 26, 167));
        $this->profileScenario('anon, /contact, cold cache', 'contact', new ExpectedProfilingData(40, 9, 32, 181));
        $this->profileScenario('anon, /contact, warm cache', 'contact', new ExpectedProfilingData(43, 0, 31, 181));
        $this->drupalGet('system/ajax');
        $this->profileScenario('anon, /system/ajax, warm cache', 'system/ajax', new ExpectedProfilingData(3, 0, 6, 135));
    
        $this->drupalLogin($this->root_user);
        $this->profileScenario('auth, front page, cold cache', '', new ExpectedProfilingData(109, 11, 48, 199));
        $this->profileScenario('auth, front page, warm cache', '', new ExpectedProfilingData(93, 0, 39, 197));
        $this->profileScenario('auth, 404, cold cache', 'non-existing-relative-url-that-will-result-in-a-404', new ExpectedProfilingData(25, 1, 33, 172));
        $this->profileScenario('auth, 404, warm cache', 'non-existing-relative-url-that-will-result-in-a-404', new ExpectedProfilingData(26, 0, 33, 172));
        $this->drupalGet('system/ajax');
        $this->profileScenario('auth, /system/ajax, warm cache', 'system/ajax', new ExpectedProfilingData(3, 0, 5, 134));
        $this->profileScenario('auth, /admin, cold cache', 'admin', new ExpectedProfilingData(37, 6, 31, 176));
        $this->profileScenario('auth, /admin, warm cache', 'admin', new ExpectedProfilingData(40, 0, 30, 176));
        $this->profileScenario('auth, /node/add/article, cold cache', 'admin', new ExpectedProfilingData(40, 0, 30, 176));
        $this->profileScenario('auth, /node/add/article, warm cache', 'admin', new ExpectedProfilingData(40, 0, 30, 176));
      }
    
  4. This reuses a lot from Symfony:
    services:
      profiler:
        class: Symfony\Component\HttpKernel\Profiler\Profiler
        arguments: ['@profiler.file_storage', '@logger.channel.profiler']
      profiler.listener:
        class: Symfony\Component\HttpKernel\EventListener\ProfilerListener
        arguments: ['@profiler']
        tags:
          - { name: event_subscriber }
      profiler.file_storage:
        class: Symfony\Component\HttpKernel\Profiler\FileProfilerStorage
        arguments: ['%data_collector.storage%']
      logger.channel.profiler:
        class: Drupal\Core\Logger\LoggerChannel
        factory_method: get
        factory_service: logger.factory
        arguments: ['profiler']
    
wim leers’s picture

Title: Create a 'Profiling' install profile » Create a 'Profiling' install profile, for testbot-powered simple profiling and easier local profiling
dawehner’s picture

I love to see work on automatic profiling!
Additional I really like that you have chosen to leverage the abstractions from the webprofiler.

  1. +++ b/core/profiles/profiling/modules/profiler/profiler.info.yml
    --- /dev/null
    +++ b/core/profiles/profiling/modules/profiler/profiler.services.yml
    
    +++ b/core/profiles/profiling/modules/profiler/profiler.services.yml
    +++ b/core/profiles/profiling/modules/profiler/profiler.services.yml
    @@ -0,0 +1,17 @@
    
    index 0000000..f0b062c
    --- /dev/null
    
    --- /dev/null
    +++ b/core/profiles/profiling/modules/profiler/src/Cache/CacheBackendWrapper.php
    

    it seems odd to not have this module available in normal core

  2. +++ b/core/profiles/profiling/modules/profiler/profiler.services.yml
    @@ -0,0 +1,17 @@
    +    arguments: ['@profiler.file_storage', '@logger.channel.profiler']
    ...
    +  profiler.file_storage:
    +    class: Symfony\Component\HttpKernel\Profiler\FileProfilerStorage
    +    arguments: ['%data_collector.storage%']
    

    it would be great to name it @profiler.storage by default, so replacing it is easier.

  3. +++ b/core/profiles/profiling/modules/profiler/src/Cache/CacheBackendWrapper.php
    @@ -0,0 +1,173 @@
    +  public function getMultiple(&$cids, $allow_invalid = FALSE) {
    +    foreach ($cids as $cid) {
    +      $this->cacheDataCollector->registerCache($this->bin, $cid, CacheDataCollector::CACHE_HIT);
    +    }
    +    return $this->cacheBackend->getMultiple($cids, $allow_invalid);
    

    i know this is a copy and paste but this feels like a bug. This does not respect the result of getMultiple ...

  4. +++ b/core/profiles/profiling/modules/profiler/src/Cache/CacheFactoryWrapper.php
    @@ -0,0 +1,67 @@
    +class CacheFactoryWrapper implements CacheFactoryInterface,  ContainerAwareInterface {
    

    <3 This will ensure that everyone is using the cache factory interface

  5. +++ b/core/profiles/profiling/profiling.info.yml
    @@ -0,0 +1,41 @@
    +#hidden: true
    

    On the longrun you plan to make it hidden, right?

  6. +++ b/core/profiles/profiling/src/Tests/ProfilingTest.php
    @@ -0,0 +1,147 @@
    +      $this->pass(String::format('%category: as expected. (Δ = @delta, @expected → @actual)', $args));
    

    Cool, unicode!

lussoluca’s picture

Wow Wim, this is awesome!

I think this is the correct approach if we can't put all Webprofiler (data collectors and UI) into core. Because the way the webprofiler is done it would be difficult to separate data collectors from the toolbar and from the backend

wim leers’s picture

#3.1: I didn't add it to normal core because 1) it has no UI; it can only be used programmatically, 2) it's intended to be hidden (so to #3.5: yes!) — it's specifically designed to be used by the tests. Perhaps I should rename it to test_profiler. Therefore I think #3.2 is also unnecsesary.

#3.3: I agree that this feels bizarre — but as you already say, it comes straight from Web Profiler. I do think it's valuable to register the number of cache hits. But I also think we should record how many actual "cache gets" there are. If we have 50 cache hits, but get them in 20 cache gets, that's important to know.

#3.4: Indeed :)

#4: Great to get confirmation, thanks :)

larowlan’s picture

Couldn't see it mentioned, does this include memory use?

wim leers’s picture

#6: not yet, but it could. I fear those results do depend on the environment though: PHP version and CPU architecture would affect this, and possibly the OS used as well.

msonnabaum’s picture

I haven't dug into the approach much yet, but I'm +1 to the idea in general. It's not the whole picture, but anything to get us started seems positive to me.

Anonymous’s picture

i like the idea of tests that set up 'real world' scenarios - generating data, setting up views etc.

i'd rather we didn't hard-code counts of expected cache hits, but instead did xhprof feature detection, and wrote out dumps.

then we have all the pieces we need for the non-drupal bits to be built around it to get xhprof diffs from D8 clean vs D8 + a patch.

moshe weitzman’s picture

We discussed this in the Perf call this week and nobody loves this approach, but we do think it is way better than nothing (i.e. the status quo). So, +1 from me.

A followup that I'd be interested in is a run-profiling.sh script that runs a scenario. Then we could compare xhprof runs pre/post a given patch.

moshe weitzman’s picture

  1. Why does the patch duplicate many config files like user.role.administrator.yml. It will be hard to keep the dupes in sync with Standard profile. Or maybe we are OK with drift here? If profiling profile has to have own config, then maybe we symlink from standard profile? I'd be OK with profiling profile requiring symlink support. PHP+Windows has had support for this since Vista and Server 2008.
  2. Articles should have their image field populated.
  3. Articles should have 50 or so comments
  4. Multilingual? Could be a followup
jhedstrom’s picture

Status: Needs review » Needs work

+1 on this idea.

Moving to needs work as per #11.

dawehner’s picture

Some basic ideas to move this issue forward:

  • Split up the patch into a) a basic patch which adds the classes into core, adds the install profile itself, and registers the services needed for it
  • Discuss the scenario we want to test
  • Add an easier version to take care about the profiles. Maybe adding the symbolic links in the Test class which deals with the profiling
  • On top of that we (@amateescu, @dawehner) discussed to work on the testbot site, maybe a dedicated testbot, for that.
wim leers’s picture

Title: Create a 'Profiling' install profile, for testbot-powered simple profiling and easier local profiling » [meta] Create a 'Profiling' install profile, for testbot-powered simple profiling and easier local profiling
Assigned: wim leers » Unassigned

Turning this into a meta, we should get this done, in baby steps. @dawehner and @amateescu are taking this on.

dawehner’s picture

dawehner’s picture

I hope amateescu manages to find some time to resetup his configuration again :( :( :( :(

webchick’s picture

If that happens, could I make a small request, which is to spin it off into a proper project, rather than a branch of a core sandbox? While we'd want to move this back into core eventually, in the interim while it lives in contrib, it would be much easier to test if it was a matter of drush dl foo.

webchick’s picture

Reviewing #2497185: [no patch] Create standardized core profiling scenarios and start tracking metrics for them, these all seem to use standard as the base. Looks like Wim's patch does as well. Seems like this would be easier if we had #1356276: Allow profiles to define a base/parent profile. Then no need to keep this profile and Standard in sync all the time.

Incidentally, there already exists https://www.drupal.org/project/profiling as well as https://www.drupal.org/project/profiler which are something else.

Since good namespaces are scarce, for giggles, I added https://www.drupal.org/project/zoom in the vain hopes I'd have time to work on this this evening. :P Added catch, wim, dawehner, and amateescu as co-maintainers. Happy to kill the project though if that's not helpful.

wim leers’s picture

Title: [meta] Create a 'Profiling' install profile, for testbot-powered simple profiling and easier local profiling » Create a 'Profiling' install profile, for testbot-powered simple profiling and easier local profiling
Issue summary: View changes
Status: Needs work » Active
Parent issue: » #2470679: [meta] Identify necessary performance optimizations for common profiling scenarios

Per #2497185-20: [no patch] Create standardized core profiling scenarios and start tracking metrics for them, where we also agreed upon the (initial) set of scenarios to profile:

Proposed plan of attack. (From a call with Moshe, effulgentsia, catch and Tyler Littlefield.)

  1. Put the scenarios in the IS into core in #2308683: [meta] Create a 'Profiling' install profile, for testbot-powered simple profiling and easier local profiling.
  2. We should automate metrics that we can automate, to get feedback *while* we're working on core.
    We'll add metrics one by one, to ensure they don't increase beyond the recorded number, starting with:
    1. peak mem: number+5%
    2. DB queries (if possible: distinguish between reads and inserts): exact number
    3. initialized services: exact number
  3. The number of function calls, we cannot automate. So, we should manually record the number of function calls in the various scenarios today, and then compare before tagging the first RC. (For RC, it should simply not exceed the number as of today.)

Doing steps 1 and 2.2 in #2497185: [no patch] Create standardized core profiling scenarios and start tracking metrics for them. Will file child issues for 2.2, 2.3 and 3 later.

wim leers’s picture

Title: Create a 'Profiling' install profile, for testbot-powered simple profiling and easier local profiling » [meta] Create a 'Profiling' install profile, for testbot-powered simple profiling and easier local profiling
Category: Task » Plan

(#19 accidentally removed the "meta" prefix, oops!)

Quoting @catch at #2497185-22: [no patch] Create standardized core profiling scenarios and start tracking metrics for them.

So, we should manually record the number of function calls in the various scenarios today, and then compare before tagging the first RC. (For RC, it should simply not exceed the number as of today.)

I think I'd probably want to modify this slightly to 'with critical performance patches applied' - just because if we don't do that, then we could in theory re-introduce the equivalent of several critical performance issues without noticing.

sorressean’s picture

StatusFileSize
new12.06 KB
new64.59 KB

I've removed a lot of what doesn't actualy belong here and started narrowing this down. Once this finally installs I can finish up adding the other required metrics.

This is currently failing after install; the error is as follows:

PHP Fatal error:  Call to a member function getUrl() on null in /var/www/test/core/modules/menu_link_content/src/Entity/MenuLinkContent.php on line 83 
1. {main}() /var/www/test/core/install.php:0
2. install_drupal() /var/www/test/core/install.php:32
install_run_tasks() /var/www/test/core/includes/install.core.inc:117
install_run_task() /var/www/test/core/includes/install.core.inc:528
install_install_profile() /var/www/test/core/includes/install.core.inc:650
Drupal\Core\Extension\ModuleInstaller->install() /var/www/test/core/includes/install.core.inc:1659
Drupal\Core\Extension\ModuleHandler->invoke() /var/www/test/core/lib/Drupal/Core/Extension/ModuleInstaller.php:274
call_user_func_array:{/var/www/test/core/lib/Drupal/Core/Extension/ModuleHandler.php:384}() /var/www/test/core/lib/Drupal/C
ore/Extension/ModuleHandler.php:384
profiling_install() /var/www/test/core/lib/Drupal/Core/Extension/ModuleHandler.php:384
Drupal\Core\Entity\Entity->save() /var/www/test/core/profiles/profiling/profiling.install:56
Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() /var/www/test/core/lib/Drupal/Core/Entity/Entity.php:340
Drupal\Core\Entity\EntityStorageBase->save() /var/www/test/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php:921
Drupal\menu_link_content\Entity\MenuLinkContent->postSave() /var/www/test/core/lib/Drupal/Core/Entity/EntityStorageBase.php:419
Drupal\menu_link_content\Entity\MenuLinkContent->getPluginDefinition() /var/www/test/core/modules/menu_link_content/src/Ent
ity/MenuLinkContent.php:218
PHP message: PHP  15. Drupal\menu_link_content\Entity\MenuLinkContent->getUrlObject() /var/www/test/core/mo...
tim.plunkett’s picture

Status: Active » Needs review

Might as well let the testbot have a crack at it

Status: Needs review » Needs work

The last submitted patch, 21: profiling-10027765-21.patch, failed testing.

wim leers’s picture

#21: I'm pretty sure that's happening because profiling_install() puts the first ten nodes in the main menu, yet that menu does not exist. It does not exist because the "Profiling" install profile in this patch does not include any config to be installed. The patch in #1 does. The patch in #1 basically copied all config from the "Standard" install profile. I think you want to do the same here. (And perhaps then modify it going forward, but that's a good starting point at least.)

sorressean’s picture

StatusFileSize
new60.25 KB
new52.17 KB

Updated to include profile configuration as suggested in #24, also fixed syntax errors I always forget to check for.

sorressean’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 25: profiling-10027765-25.patch, failed testing.

sorressean’s picture

StatusFileSize
new60.22 KB
new928 bytes

Updated to fix some exceptions and fixed profile.install.

sorressean’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 28: profiling-10027765-28.patch, failed testing.

sorressean’s picture

StatusFileSize
new60.2 KB
new805 bytes

I updated this to work with the most recent commits. Same issues on my tests: field issues which will probably show on next tests. I'm not sure how to fix this, so any help here would be awesome.

sorressean’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 31: profiling-10027765-31.patch, failed testing.

wim leers’s picture

I'll take a look at fixing #31. Will assign to me when I actually work on it.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new60.22 KB
new3.97 KB

This should fail with a single exception: for the single assertion that the newly added test adds.

Status: Needs review » Needs work

The last submitted patch, 35: profiling_profile-2308683-35.patch, failed testing.

sorressean’s picture

StatusFileSize
new60.48 KB

Updated to insure that it passes profile test. Adding more metrics shortly.

sorressean’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 37: profiling-10027765-37.patch, failed testing.

sorressean’s picture

StatusFileSize
new60.25 KB

trying again--sorry about readme.

sorressean’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 40: profiling-10027765-40.patch, failed testing.

sorressean’s picture

StatusFileSize
new12.06 KB
new3.05 KB

Fixed exceptions above, also added SQL metrics. This may or may not be wrong. Any ideas on how to handle SQL queries better would be awesome.
Basic idea:
The logger holds a log of all queries. Solution: just use the size of the array, which will return count.

sorressean’s picture

StatusFileSize
new61.83 KB

Added the correct profiling patch this time...

sorressean’s picture

Status: Needs work » Needs review

The last submitted patch, 43: profiling-10027765-21.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 44: profiling-10027765-43.patch, failed testing.

dawehner’s picture

  1. +++ b/core/profiles/profiling/modules/profiler/src/Compiler/ProfilerPass.php
    @@ -0,0 +1,38 @@
    +/**
    + * Class ProfilerPass
    + */
    +class ProfilerPass implements CompilerPassInterface {
    

    Let's adds some form of docs

  2. +++ b/core/profiles/profiling/src/SQLDataCollector.php
    @@ -0,0 +1,34 @@
    +        return sizeof($log);
    

    Let's use count() everything else is confusing, IMHO

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new68.21 KB
new8.08 KB

#43 introduced a separate scenario to profile SQL. I don't think this makes sense: it requires us to render the exact same pages again. Why not collect that the first time around? This makes the test significantly slower.

In fact, it looks like this SQL data collector that you added made tests several orders of magnitude slower. It seems to send tests into some sort of recursive installation loop. I think because it's collecting the function call stack as well.


#19 mentioned 3 things: peak memory, SQL queries and initialized services. To help get this patch back on track, I'm adding the initialized services part back (it already was in #1), freshly copy/pasted from the webprofiler module.

I've added the DatabaseDataCollector from Web Profiler instead of the custom written SQL data collector. I'm not yet using that data collector in this patch though, because there's something interesting about it. See the next reroll.

I've also added a warm cache scenario, which shows how this was intended to be used.


  1. +++ b/core/profiles/profiling/profiling.install
    --- /dev/null
    +++ b/core/profiles/profiling/src/SQLDataCollector.php
    
    +++ b/core/profiles/profiling/src/SQLDataCollector.php
    @@ -0,0 +1,34 @@
    +namespace Drupal\profiling;
    

    This does not belong in the install profile, but in \Drupal\Core\Profiler, like my patch in #1 already did.

  2. +++ b/core/profiles/profiling/profiling.info.yml
    @@ -0,0 +1,44 @@
    +dependencies:
    +  - simpletest
    +  - node
    +  - history
    +  - block
    +  - breakpoint
    +  - ckeditor
    +  - color
    +  - config
    +  - comment
    +  - contextual
    +  - contact
    +  - datetime
    +  - block_content
    +  - quickedit
    +  - editor
    +  - entity_reference
    +  - help
    +  - image
    +  - menu_ui
    +  - menu_link_content
    +  - options
    +  - path
    +  - taxonomy
    +  - dblog
    +  - search
    +  - shortcut
    +  - toolbar
    +  - field_ui
    +  - file
    +  - rdf
    +  - views
    +  - views_ui
    +  - tour
    +  - text
    

    How did you end up with this list of modules to install? This is not at all a copy of the Standard install profile, and hence it's not actually testing something we want to be testing.

    The simpletest module for example never makes sense to enable when profiling.

    And the page_cache module is conspicuously missing, without explanation. (It should not be enabled because it is a middleware, during which no events are fired, and hence no profiling is possible. But that reason for its absence should be explicitly documented, and now it is.)

  3. +++ b/core/profiles/profiling/modules/profiler/profiler.info.yml
    @@ -0,0 +1,6 @@
    +description: 'Will profilele variousus scenarios in the future, currently only peak memory. Stores the results in files.'
    

    s/profilele/profile/
    s/variousus/various/

    And generally outdated.

    Plus, factually wrong, because this does not profile scenarios. A module can never profile scenarios, only a test can. This is a module that simply gathers profiling data.

wim leers’s picture

StatusFileSize
new70.33 KB
new4.31 KB

And now with query counts being measured. Note how the peak memory numbers increase drastically (from ~6% more than the expected number of 32 MB, to ~34% more). This makes sense: collecting all that query information causes the memory usage to increase.

I don't think there's a way around this. But … that really, really sucks. It's because the measuring logic and the measured logic both are part of the same problem. I don't think we can solve this.

Either we have some heuristic to subtract some number of memory bytes based on the number of logged queries, or we simply cannot measure database queries. (Or we need to update the DB API to provide a much more efficient way of measuring the number of queries.)

wim leers’s picture

StatusFileSize
new70.68 KB
new2.52 KB

And here's some clean-up plus todos.

The last submitted patch, 49: profiling-2308683-49.patch, failed testing.

The last submitted patch, 50: profiling-2308683-50.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 51: profiling-2308683-51.patch, failed testing.

sorressean’s picture

StatusFileSize
new5.42 KB

Updated todos, thanks Wim. We should be set now short of the metrics.

sorressean’s picture

StatusFileSize
new67.45 KB
sorressean’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 56: profiling-2308683-55.patch, failed testing.

sorressean’s picture

StatusFileSize
new546 bytes
sorressean’s picture

StatusFileSize
new67.45 KB
sorressean’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 60: profiling-2308683-59.patch, failed testing.

wim leers’s picture

@sorressean: Can you please post the interdiff together with the patch next time? d.o supports attaching multiple files per comment. Or is there an accessibility problem with that perhaps?


So, all expected numbers have changed, which is why there are 6 exceptions. That's an easy fix :)

But, what's concerning is that the numbers are so vastly different. Let's first get a green patch, and let's then upload the same patch ten times in a single comment. That'll run the same tests ten times, so we'll see if the measured numbers are sufficiently consistent for this test to be reliable.

sorressean’s picture

StatusFileSize
new1.36 KB
new67.48 KB

Thanks, wim. I'm not really sure what the problem was (accessibility or not). I found a workaround to the problem though, hopefully.

In this patch:
* Updated values to match with the test.
* Added an identifier to the format, so that 75.5 would show as 75.5M.
* 64 is a magic number (this comment), lets hope this works!

sorressean’s picture

Status: Needs work » Needs review
sorressean’s picture

Insuring that we get the same results.

sorressean’s picture

The last submitted patch, 66: profiling-2308683-64.patch, failed testing.

The last submitted patch, 66: profiling-2308683-64.patch, failed testing.

The last submitted patch, 66: profiling-2308683-64.patch, failed testing.

The last submitted patch, 66: profiling-2308683-64.patch, failed testing.

The last submitted patch, 66: profiling-2308683-64.patch, failed testing.

The last submitted patch, 66: profiling-2308683-64.patch, failed testing.

The last submitted patch, 66: profiling-2308683-64.patch, failed testing.

The last submitted patch, 66: profiling-2308683-64.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 66: profiling-2308683-64.patch, failed testing.

sorressean’s picture

These show that there is not a solid query count that can be confirmed. Although there may be a variant of a small percentage (lets say 5), higher deltas (~20+%) suggest that there might be caching issues that need to be examined.

The only other issues seem to be deltas in memory usage, although those seem to be within small ranges, so I would hazard to mark those as acceptable.

I'd also really like to find a good direction to go with this from someone who knows a lot more. Thoughts?

wim leers’s picture

On a call with @effulgentsia, @sorressean and I, we decided to decrease the scope that was defined in #19: rather than doing peak mem, DB queries and initialized services, do only initialized services. That's still a great first step, still very valuable, but doesn't suffer from any of the problems with peak mem and DB queries.

(Problems with A) peak mem: opcache differences between testbots make it very unpredictable, infra improvements necessary, B) measuring DB queries, and the way that that is supported by doing extensive logging, significantly affects peak mem.)

sorressean’s picture

StatusFileSize
new64.72 KB
new4.33 KB

Here's a patch without the extra collection of data--only service count:

sorressean’s picture

Status: Needs work » Needs review

Go testbot go!

Status: Needs review » Needs work

The last submitted patch, 79: profiling-2308683-79.patch, failed testing.

The last submitted patch, 79: profiling-2308683-79.patch, failed testing.

sorressean’s picture

StatusFileSize
new64.41 KB
new2.19 KB

Removed services (oops), also did some basic cleanup to make the patch apply cleanly.

sorressean’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 84: profiling-2308683-84.patch, failed testing.

sorressean’s picture

StatusFileSize
new64.41 KB
new822 bytes

I was stupid and forgot to decrease the expected service count...

sorressean’s picture

Status: Needs work » Needs review
wim leers’s picture

Status: Needs review » Needs work
  1. +++ b/core/core.services.yml
    @@ -1508,3 +1508,10 @@ services:
    +  # Data collector for profiling.
    

    This comment is rather pointless, since the service tag already indicates that. Let's remove it.

  2. +++ b/core/lib/Drupal/Core/Profiler/ServiceDataCollector.php
    @@ -0,0 +1,78 @@
    +<?php
    +
    +namespace Drupal\Core\Profiler;
    

    Missing docs.

  3. +++ b/core/lib/Drupal/Core/Profiler/ServiceDataCollector.php
    @@ -0,0 +1,78 @@
    +class ServiceDataCollector extends DataCollector {
    

    Missing docs.

  4. +++ b/core/lib/Drupal/Core/Profiler/ServiceDataCollector.php
    @@ -0,0 +1,78 @@
    +  /**
    +   * @var \Symfony\Component\DependencyInjection\IntrospectableContainerInterface
    +   */
    +  private $container;
    +
    +  /**
    +   * @param \Symfony\Component\DependencyInjection\IntrospectableContainerInterface $container
    +   */
    +  public function __construct(IntrospectableContainerInterface $container) {
    

    Incomplete docs.

  5. +++ b/core/lib/Drupal/Core/Profiler/ServiceDataCollector.php
    @@ -0,0 +1,78 @@
    +  /**
    +   * @return int
    +   */
    +  public function getInitializedServicesWithoutProfilerServices() {
    

    Incomplete docs.

  6. +++ b/core/profiles/profiling/modules/profiler/profiler.info.yml
    @@ -0,0 +1,6 @@
    +description: 'Gathers simple profiling data (peak memory, number of initialized services, and so on).'
    

    Outdated.

  7. +++ b/core/profiles/profiling/modules/profiler/src/Compiler/ProfilerPass.php
    @@ -0,0 +1,38 @@
    +<?php
    +
    +namespace Drupal\profiler\Compiler;
    

    Missing docs.

  8. +++ b/core/profiles/profiling/modules/profiler/src/Compiler/ProfilerPass.php
    @@ -0,0 +1,38 @@
    +/**
    + * Class ProfilerPass
    + */
    +class ProfilerPass implements CompilerPassInterface {
    

    Actual docs needed.

  9. +++ b/core/profiles/profiling/modules/profiler/src/Compiler/ProfilerPass.php
    @@ -0,0 +1,38 @@
    +  /**
    +   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    +   *
    +   * @throws \InvalidArgumentException
    +   */
    +  public function process(ContainerBuilder $container) {
    

    Incomplete docblock.

  10. +++ b/core/profiles/profiling/profiling.info.yml
    @@ -0,0 +1,46 @@
    +description: 'Profile for profiling (measuring performance). currently Includes one scenario to track Drupal performance.'
    

    s/currenty Includes/Currently includes/

  11. +++ b/core/profiles/profiling/src/Tests/ProfilingTest.php
    @@ -0,0 +1,166 @@
    +/**
    + * Holds profiling data.
    + * In a basic form, this class encapsolates an array, but does not allow
    + * setting values once they have been initialized.
    + *
    + */
    +class ExpectedProfilingData {
    +  public $service_count;
    +  function __construct($service_count) {
    +    $this->service_count = $service_count;
    +  }
    +}
    

    This needs to be moved into a separate file and needs some serious cleanup.

  12. +++ b/core/profiles/profiling/src/Tests/ProfilingTest.php
    @@ -0,0 +1,166 @@
    +    // @todo Add more scenarios, e.g. the ones in the first patch.
    

    Let's do this now.

  13. +++ b/core/profiles/profiling/src/Tests/ProfilingTest.php
    @@ -0,0 +1,166 @@
    +  /***
    +   * Formats a specified number.
    +   * Valid values are G|M|K.
    +   */
    +  protected function formatValue($value, $format = 'G') {
    

    Incomplete docblock.

  14. +++ b/core/profiles/profiling/src/Tests/ProfilingTest.php
    @@ -0,0 +1,166 @@
    +  /**
    +   * Like ::assertEqual(), but informs us about a regression or improvement.
    +      * variation_percentage: the amount given data can
    +   * change above or below.
    +   * Format is one of G|M|K|P.
    +   * (gigabyte, megabyte, kilobyte or plane).
    +   * For all values excluding plane, the result will be formatted.
    +   *
    +   */
    

    Incomplete docblock and indentation problems.

    Also: s/plane/plain/?

  15. +++ b/core/profiles/profiling/src/Tests/ProfilingTest.php
    @@ -0,0 +1,166 @@
    +    $args = array(
    

    Let's use PHP 5.5 short notation array syntax.

sorressean’s picture

StatusFileSize
new65.73 KB
new6.63 KB

I got into some bad habbits and just kept hacking on this without bothering to update any docs. Thanks for the embarrassingly long list.
I left out scenarios since we want to keep this simple. Those can easily be added later.
Hopefully this is a lot closer to what was needed!

sorressean’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 90: profiling-2308683-90.patch, failed testing.

moshe weitzman’s picture

We discussed this in the D8 Perf call today. In order to get this to green, we scoped this patch down to counting just service instantiations. Thanks especially to @sorressean for getting us there. The patch adds a near duplicate of standard profile, with all its config files. Thats going to be a bit painful to maintain. When this profile can start delivering more counters than service instantiations, lets consider committing it.

In some other issue, the register_tick_function was suggested as a way to do function counting without an external extension. Is worth exploring here.

cosmicdreams’s picture

Did this miss the boat or are we still going to ship this installation profile with Drupal 8.0.0?

moshe weitzman’s picture

Version: 8.0.x-dev » 8.1.x-dev

Missed the 8.0.0 boat. Could be added in a minor release.

sorressean’s picture

I missed this so much I'm going to start going after it again... :) It obviously missed the d8 release, but this could still be useful. to my thinking, we need to do a couple things; the code is failing although I think I fixed all of the stuff Wim pointed out. So we just need a few more metrics. I think the hangup on this was that the testbots were reporting drastically different issues; at one point testbot was noting memory errors while QA was noticing querie errors so we started getting some weird results out of this.

moshe weitzman’s picture

Glad to hear it, @sorresean!

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.