From cc44e691f9b6e30f6ad3f996d956109b7af20c98 Mon Sep 17 00:00:00 2001 From: catch <catch@35733.no-reply.drupal.org> Date: Wed, 21 Sep 2022 14:49:58 +0100 Subject: [PATCH] =?UTF-8?q?Issue=20#2381797=20by=20Tom=20Verhaeghe,=20slas?= =?UTF-8?q?hrsm,=20johnwebdev,=20chr.fritsch,=20ankithashetty,=20Krzysztof?= =?UTF-8?q?=20Doma=C5=84ski,=20nevergone,=20tobiasb,=20yogeshmpawar,=20anm?= =?UTF-8?q?olgoyal74,=20dhirendra.mishra,=20Wim=20Leers,=20longwave,=20Fab?= =?UTF-8?q?ianx,=20andypost,=20joachim,=20alexpott:=20Add=20render=5Fcache?= =?UTF-8?q?=20debug=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scaffold/files/default.services.yml | 8 ++ core/core.services.yml | 1 + core/lib/Drupal/Core/Render/Renderer.php | 79 +++++++++++++++++ .../Tests/Core/Render/RendererDebugTest.php | 87 +++++++++++++++++++ .../Tests/Core/Render/RendererTestBase.php | 1 + sites/default/default.services.yml | 8 ++ 6 files changed, 184 insertions(+) create mode 100644 core/tests/Drupal/Tests/Core/Render/RendererDebugTest.php diff --git a/core/assets/scaffold/files/default.services.yml b/core/assets/scaffold/files/default.services.yml index ff6797d954cc..8c7f05dcfd4b 100644 --- a/core/assets/scaffold/files/default.services.yml +++ b/core/assets/scaffold/files/default.services.yml @@ -132,6 +132,14 @@ parameters: # # @default [] tags: [] + # Renderer cache debug: + # + # Allows cache debugging output for each rendered element. + # + # Enabling render cache debugging is not recommended in production + # environments. + # @default false + debug: false # Cacheability debugging: # # Responses with cacheability metadata (CacheableResponseInterface instances) diff --git a/core/core.services.yml b/core/core.services.yml index 1631d5c1a2b4..c5a0fbbfc522 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -21,6 +21,7 @@ parameters: max-age: 0 contexts: ['session', 'user'] tags: [] + debug: false factory.keyvalue: default: keyvalue.database http.response.debug_cacheability_headers: false diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index f95116e5f77e..da19e3281f0e 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -120,6 +120,9 @@ public function __construct(ControllerResolverInterface $controller_resolver, Th $this->elementInfo = $element_info; $this->placeholderGenerator = $placeholder_generator; $this->renderCache = $render_cache; + if (!isset($renderer_config['debug'])) { + $renderer_config['debug'] = FALSE; + } $this->rendererConfig = $renderer_config; $this->requestStack = $request_stack; @@ -215,6 +218,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) { return ''; } + if ($this->rendererConfig['debug'] === TRUE) { + $render_start = microtime(TRUE); + } + if (!isset($elements['#access']) && isset($elements['#access_callback'])) { $elements['#access'] = $this->doCallback('#access_callback', $elements['#access_callback'], [$elements]); } @@ -276,6 +283,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) { if (is_string($elements['#markup'])) { $elements['#markup'] = Markup::create($elements['#markup']); } + // Add debug output to the renderable array on cache hit. + if ($this->rendererConfig['debug'] === TRUE) { + $elements = $this->addDebugOutput($elements, TRUE); + } // The render cache item contains all the bubbleable rendering metadata // for the subtree. $context->update($elements); @@ -513,6 +524,11 @@ protected function doRender(&$elements, $is_root_call = FALSE) { throw new \LogicException('Cache keys may not be changed after initial setup. Use the contexts property instead to bubble additional metadata.'); } $this->renderCache->set($elements, $pre_bubbling_elements); + // Add debug output to the renderable array on cache miss. + if ($this->rendererConfig['debug'] === TRUE) { + $render_stop = microtime(TRUE); + $elements = $this->addDebugOutput($elements, FALSE, $pre_bubbling_elements, $render_stop - $render_start); + } // Update the render context; the render cache implementation may update // the element, and it may have different bubbleable metadata now. // @see \Drupal\Core\Render\PlaceholderingRenderCache::set() @@ -772,4 +788,67 @@ protected function doCallback($callback_type, $callback, array $args) { return $this->doTrustedCallback($callback, $args, $message, TrustedCallbackInterface::THROW_EXCEPTION, RenderCallbackInterface::class); } + /** + * Add cache debug information to the render array. + * + * @param array $elements + * The renderable array that must be wrapped with the cache debug output. + * @param bool $is_cache_hit + * A flag indicating that the cache is hit or miss. + * @param array $pre_bubbling_elements + * The renderable array for pre-bubbling elements. + * @param float $render_time + * The rendering time. + * + * @return array + * The renderable array. + */ + protected function addDebugOutput(array $elements, bool $is_cache_hit, array $pre_bubbling_elements = [], float $render_time = 0) { + if (empty($elements['#markup'])) { + return $elements; + } + + $debug_items = [ + 'CACHE' => &$elements, + 'PRE-BUBBLING CACHE' => &$pre_bubbling_elements, + ]; + $prefix = "<!-- START RENDERER -->"; + $prefix .= "\n<!-- CACHE-HIT: " . ($is_cache_hit ? 'Yes' : 'No') . " -->"; + foreach ($debug_items as $name_prefix => $debug_item) { + if (!empty($debug_item['#cache']['tags'])) { + $prefix .= "\n<!-- " . $name_prefix . " TAGS:"; + foreach ($debug_item['#cache']['tags'] as $tag) { + $prefix .= "\n * " . $tag; + } + $prefix .= "\n-->"; + } + if (!empty($debug_item['#cache']['contexts'])) { + $prefix .= "\n<!-- " . $name_prefix . " CONTEXTS:"; + foreach ($debug_item['#cache']['contexts'] as $context) { + $prefix .= "\n * " . $context; + } + $prefix .= "\n-->"; + } + if (!empty($debug_item['#cache']['keys'])) { + $prefix .= "\n<!-- " . $name_prefix . " KEYS:"; + foreach ($debug_item['#cache']['keys'] as $key) { + $prefix .= "\n * " . $key; + } + $prefix .= "\n-->"; + } + if (!empty($debug_item['#cache']['max-age'])) { + $prefix .= "\n<!-- " . $name_prefix . " MAX-AGE: " . $debug_item['#cache']['max-age'] . " -->"; + } + } + + if (!empty($render_time)) { + $prefix .= "\n<!-- RENDERING TIME: " . number_format($render_time, 9) . " -->"; + } + $suffix = "<!-- END RENDERER -->"; + + $elements['#markup'] = Markup::create("$prefix\n" . $elements['#markup'] . "\n$suffix"); + + return $elements; + } + } diff --git a/core/tests/Drupal/Tests/Core/Render/RendererDebugTest.php b/core/tests/Drupal/Tests/Core/Render/RendererDebugTest.php new file mode 100644 index 000000000000..a433acd188d8 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Render/RendererDebugTest.php @@ -0,0 +1,87 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\Core\Render; + +use function preg_replace; + +/** + * @coversDefaultClass \Drupal\Core\Render\Renderer + * @group Render + */ +class RendererDebugTest extends RendererTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + $this->rendererConfig['debug'] = TRUE; + + parent::setUp(); + } + + /** + * Test render debug output. + */ + public function testDebugOutput() { + $this->setUpRequest(); + $this->setupMemoryCache(); + + $element = [ + '#cache' => [ + 'keys' => ['render_cache_test_key'], + 'tags' => ['render_cache_test_tag', 'render_cache_test_tag1'], + 'max-age' => 10, + ], + '#markup' => 'Test 1', + ]; + $markup = $this->renderer->renderRoot($element); + + $expected = <<<EOF +<!-- START RENDERER --> +<!-- CACHE-HIT: No --> +<!-- CACHE TAGS: + * render_cache_test_tag + * render_cache_test_tag1 +--> +<!-- CACHE CONTEXTS: + * languages:language_interface + * theme +--> +<!-- CACHE KEYS: + * render_cache_test_key +--> +<!-- CACHE MAX-AGE: 10 --> +<!-- PRE-BUBBLING CACHE TAGS: + * render_cache_test_tag + * render_cache_test_tag1 +--> +<!-- PRE-BUBBLING CACHE CONTEXTS: + * languages:language_interface + * theme +--> +<!-- PRE-BUBBLING CACHE KEYS: + * render_cache_test_key +--> +<!-- PRE-BUBBLING CACHE MAX-AGE: 10 --> +<!-- RENDERING TIME: 0.123456789 --> +Test 1 +<!-- END RENDERER --> +EOF; + $this->assertSame($expected, preg_replace('/RENDERING TIME: \d{1}.\d{9}/', 'RENDERING TIME: 0.123456789', $markup->__toString())); + + $element = [ + '#cache' => [ + 'keys' => ['render_cache_test_key'], + 'tags' => ['render_cache_test_tag', 'render_cache_test_tag1'], + 'max-age' => 10, + ], + '#markup' => 'Test 1', + ]; + $markup = $this->renderer->renderRoot($element); + + $this->assertStringContainsString('CACHE-HIT: Yes', $markup->__toString()); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php index cb3cc7892a1f..70346b79259c 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php @@ -109,6 +109,7 @@ abstract class RendererTestBase extends UnitTestCase { 'contexts' => ['session', 'user'], 'tags' => ['current-temperature'], ], + 'debug' => FALSE, ]; /** diff --git a/sites/default/default.services.yml b/sites/default/default.services.yml index ff6797d954cc..8c7f05dcfd4b 100644 --- a/sites/default/default.services.yml +++ b/sites/default/default.services.yml @@ -132,6 +132,14 @@ parameters: # # @default [] tags: [] + # Renderer cache debug: + # + # Allows cache debugging output for each rendered element. + # + # Enabling render cache debugging is not recommended in production + # environments. + # @default false + debug: false # Cacheability debugging: # # Responses with cacheability metadata (CacheableResponseInterface instances) -- GitLab