Verified Commit 9c618223 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3396196 by catch, kristiaanvandeneynde, Kingdutch: Separate cache...

Issue #3396196 by catch, kristiaanvandeneynde, Kingdutch: Separate cache operations from database queries in OpenTelemetry and assertions
parent 77888cf5
Loading
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -7,3 +7,8 @@ services:
    arguments: ['@database']
    tags:
      - { name: http_middleware, priority: 1000, responder: true }
  performance_test.cache_factory:
    class: Drupal\performance_test\Cache\CacheFactoryDecorator
    public: false
    decorates: cache_factory
    arguments: ['@performance_test.cache_factory.inner', '@Drupal\performance_test\PerformanceDataCollector']
+171 −0
Original line number Diff line number Diff line
<?php

declare(strict_types = 1);

namespace Drupal\performance_test\Cache;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\performance_test\PerformanceDataCollector;

/**
 * Wraps an existing cache backend to track calls to the cache backend.
 */
class CacheBackendDecorator implements CacheBackendInterface, CacheTagsInvalidatorInterface {

  public function __construct(protected readonly PerformanceDataCollector $performanceDataCollector, protected readonly CacheBackendInterface $cacheBackend, protected readonly string $bin) {}

  /**
   * Logs a cache operation.
   *
   * @param string|array $cids
   *   The cache IDs.
   * @param float $start
   *   The start microtime.
   * @param float $stop
   *   The stop microtime.
   * @param string $operation
   *   The type of operation being logged.
   *
   * @return void
   */
  protected function logCacheOperation(string|array $cids, float $start, float $stop, string $operation): void {
    $this->performanceDataCollector->addCacheOperation([
      'operation' => $operation,
      'cids' => implode(', ', (array) $cids),
      'bin' => $this->bin,
      'start' => $start,
      'stop' => $stop,
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function get($cid, $allow_invalid = FALSE): object|bool {
    $start = microtime(TRUE);
    $cache = $this->cacheBackend->get($cid, $allow_invalid);
    $stop = microtime(TRUE);
    $this->logCacheOperation($cid, $start, $stop, 'get');
    return $cache;
  }

  /**
   * {@inheritdoc}
   */
  public function getMultiple(&$cids, $allow_invalid = FALSE): array {
    $cids_copy = $cids;
    $start = microtime(TRUE);
    $cache = $this->cacheBackend->getMultiple($cids, $allow_invalid);
    $stop = microtime(TRUE);
    $this->logCacheOperation($cids_copy, $start, $stop, 'getMultiple');

    return $cache;
  }

  /**
   * {@inheritdoc}
   */
  public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
    $start = microtime(TRUE);
    $this->cacheBackend->set($cid, $data, $expire, $tags);
    $stop = microtime(TRUE);
    $this->logCacheOperation($cid, $start, $stop, 'set');
  }

  /**
   * {@inheritdoc}
   */
  public function setMultiple(array $items) {
    $cids = array_keys($items);
    $start = microtime(TRUE);
    $this->cacheBackend->setMultiple($items);
    $stop = microtime(TRUE);
    $this->logCacheOperation($cids, $start, $stop, 'setMultiple');
  }

  /**
   * {@inheritdoc}
   */
  public function delete($cid) {
    $start = microtime(TRUE);
    $this->cacheBackend->delete($cid);
    $stop = microtime(TRUE);
    $this->logCacheOperation($cid, $start, $stop, 'delete');
  }

  /**
   * {@inheritdoc}
   */
  public function deleteMultiple(array $cids) {
    $start = microtime(TRUE);
    $this->cacheBackend->deleteMultiple($cids);
    $stop = microtime(TRUE);
    $this->logCacheOperation($cids, $start, $stop, 'deleteMultiple');
  }

  /**
   * {@inheritdoc}
   */
  public function deleteAll() {
    $start = microtime(TRUE);
    $this->cacheBackend->deleteAll();
    $stop = microtime(TRUE);
    $this->logCacheOperation([], $start, $stop, 'deleteAll');
  }

  /**
   * {@inheritdoc}
   */
  public function invalidate($cid) {
    $start = microtime(TRUE);
    $this->cacheBackend->invalidate($cid);
    $stop = microtime(TRUE);
    $this->logCacheOperation($cid, $start, $stop, 'invalidate');
  }

  /**
   * {@inheritdoc}
   */
  public function invalidateMultiple(array $cids) {
    $start = microtime(TRUE);
    $this->cacheBackend->invalidateMultiple($cids);
    $stop = microtime(TRUE);
    $this->logCacheOperation($cids, $start, $stop, 'invalidateMultiple');
  }

  /**
   * {@inheritdoc}
   */
  public function invalidateTags(array $tags) {
    if ($this->cacheBackend instanceof CacheTagsInvalidatorInterface) {
      $this->cacheBackend->invalidateTags($tags);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function invalidateAll() {
    $start = microtime(TRUE);
    $this->cacheBackend->invalidateAll();
    $stop = microtime(TRUE);
    $this->logCacheOperation([], $start, $stop, 'invalidateAll');
  }

  /**
   * {@inheritdoc}
   */
  public function garbageCollection() {
    $this->cacheBackend->garbageCollection();
  }

  /**
   * {@inheritdoc}
   */
  public function removeBin() {
    $this->cacheBackend->removeBin();
  }

}
+45 −0
Original line number Diff line number Diff line
<?php

declare(strict_types = 1);

namespace Drupal\performance_test\Cache;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheFactoryInterface;
use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
use Drupal\Core\Cache\MemoryBackend;
use Drupal\performance_test\PerformanceDataCollector;

/**
 * Decorates a cache factory to register all calls to the cache system.
 */
class CacheFactoryDecorator implements CacheFactoryInterface {

  /**
   * All wrapped cache backends.
   *
   * @var \Drupal\performance_data\Cache\CacheBackendDecorator[]
   */
  protected array $cacheBackends = [];

  public function __construct(protected readonly CacheFactoryInterface $cacheFactory, protected readonly PerformanceDataCollector $performanceDataCollector) {}

  /**
   * {@inheritdoc}
   */
  public function get($bin): CacheBackendInterface {
    if (!isset($this->cacheBackends[$bin])) {
      $cache_backend = $this->cacheFactory->get($bin);
      // Don't log memory cache operations.
      if (!$cache_backend instanceof MemoryCacheInterface && !$cache_backend instanceof MemoryBackend) {
        $this->cacheBackends[$bin] = new CacheBackendDecorator($this->performanceDataCollector, $cache_backend, $bin);
      }
      else {
        $this->cacheBackends[$bin] = $cache_backend;
      }
    }

    return $this->cacheBackends[$bin];
  }

}
+27 −3
Original line number Diff line number Diff line
@@ -15,6 +15,11 @@ class PerformanceDataCollector implements EventSubscriberInterface, Destructable
   */
  protected array $databaseEvents = [];

  /**
   * Cache operations collected during the request.
   */
  protected array $cacheOperations = [];

  /**
   * {@inheritdoc}
   */
@@ -32,6 +37,13 @@ public function onStatementExecutionEnd(StatementExecutionEndEvent $event): void
    $this->databaseEvents[] = $event;
  }

  /**
   * Adds a cache operation.
   */
  public function addCacheOperation(array $operation) {
    $this->cacheOperations[] = $operation;
  }

  /**
   * {@inheritdoc}
   */
@@ -40,12 +52,24 @@ public function destruct(): void {
    // logging does not become part of the recorded data.
    $database_events = $this->databaseEvents;

    // Deliberately do not use an injected key value service to avoid any
    // overhead up until this point.
    // Deliberately do not use an injected key value or lock service to avoid
    // any overhead up until this point.
    $lock = \Drupal::lock();

    // This loop should be safe because we know a very finite number of requests
    // will be trying to acquire a lock at any one time.
    while (!$lock->acquire('performance_test')) {
      $lock->wait();
    }
    $collection = \Drupal::keyValue('performance_test');
    $existing_data = $collection->get('performance_test_data') ?? ['database_events' => []];
    $existing_data = $collection->get('performance_test_data') ?? [
      'database_events' => [],
      'cache_operations' => [],
    ];
    $existing_data['database_events'] = array_merge($existing_data['database_events'], $database_events);
    $existing_data['cache_operations'] = array_merge($existing_data['cache_operations'], $this->cacheOperations);
    $collection->set('performance_test_data', $existing_data);
    $lock->release('performance_test');
  }

}
+2 −3
Original line number Diff line number Diff line
@@ -35,9 +35,8 @@ public function testFrontPageAuthenticatedWarmCache(): void {
    $performance_data = $this->collectPerformanceData(function () {
      $this->drupalGet('<front>');
    }, 'authenticatedFrontPage');
    $this->assertLessThanOrEqual(16, $performance_data->getQueryCount());
    $this->assertGreaterThanOrEqual(15, $performance_data->getQueryCount());
    $this->assertSame(15, $performance_data->getCacheGetCount());
    $this->assertSame(15, $performance_data->getQueryCount());
    $this->assertSame(43, $performance_data->getCacheGetCount());
    $this->assertSame(0, $performance_data->getCacheSetCount());
    $this->assertSame(0, $performance_data->getCacheDeleteCount());
  }
Loading