Commit cc44e691 authored by catch's avatar catch
Browse files

Issue #2381797 by Tom Verhaeghe, slashrsm, johnwebdev, chr.fritsch,...

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
parent 9ff2c846
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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)
+1 −0
Original line number Diff line number Diff line
@@ -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
+79 −0
Original line number Diff line number Diff line
@@ -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;
  }

}
+87 −0
Original line number Diff line number Diff line
<?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());
  }

}
+1 −0
Original line number Diff line number Diff line
@@ -109,6 +109,7 @@ abstract class RendererTestBase extends UnitTestCase {
      'contexts' => ['session', 'user'],
      'tags' => ['current-temperature'],
    ],
    'debug' => FALSE,
  ];

  /**
Loading