Issue #2381797 by Tom Verhaeghe, slashrsm, johnwebdev, chr.fritsch, ankithashetty, Krzysztof Domański, nevergone, tobiasb, yogeshmpawar, anmolgoyal74, dhirendra.mishra, Wim Leers, longwave, Fabianx, andypost, joachim, alexpott: Add render_cache debug output

merge-requests/2780/head
catch 2022-09-21 14:49:58 +01:00
parent 9ff2c846dc
commit cc44e691f9
6 changed files with 184 additions and 0 deletions

View File

@ -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)

View File

@ -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

View File

@ -120,6 +120,9 @@ class Renderer implements RendererInterface {
$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 @@ class Renderer implements RendererInterface {
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 @@ class Renderer implements RendererInterface {
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 @@ class Renderer implements RendererInterface {
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 @@ class Renderer implements RendererInterface {
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;
}
}

View File

@ -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());
}
}

View File

@ -109,6 +109,7 @@ abstract class RendererTestBase extends UnitTestCase {
'contexts' => ['session', 'user'],
'tags' => ['current-temperature'],
],
'debug' => FALSE,
];
/**

View File

@ -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)