diff --git a/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php b/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php index 6598576660c1e30ed7129718dd9b6b287f9fbe6f..ddd9bc7d684e3070d89ff9988ec0fc04fa6cc310 100644 --- a/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php +++ b/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php @@ -74,7 +74,21 @@ public function testLogin(): void { $expected = [ 'QueryCount' => 4, 'CacheGetCount' => 61, + 'CacheGetCountByBin' => [ + 'config' => 10, + 'data' => 6, + 'access_policy' => 1, + 'bootstrap' => 6, + 'dynamic_page_cache' => 2, + 'discovery' => 11, + 'render' => 23, + 'menu' => 1, + 'default' => 1, + ], 'CacheSetCount' => 2, + 'CacheSetCountByBin' => [ + 'dynamic_page_cache' => 2, + ], 'CacheDeleteCount' => 0, 'CacheTagChecksumCount' => 3, 'CacheTagIsValidCount' => 31, @@ -85,6 +99,7 @@ public function testLogin(): void { 'StylesheetBytes' => 92000, ]; $this->assertMetrics($expected, $performance_data); + $this->assertSame(['core.extension.list.module'], $performance_data->getCacheOperations()['get']['default']); // Check that the navigation toolbar is cached without any high-cardinality // cache contexts (user, route, query parameters etc.). diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php index df52db42bfaf2bc7ad262f0ffb7b29fd6145bab4..f0a6312984af82287951bbcb55062d365d446665 100644 --- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php +++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php @@ -49,11 +49,20 @@ public function testFrontPageAuthenticatedWarmCache(): void { $expected = [ 'QueryCount' => 4, 'CacheGetCount' => 40, + 'CacheGetCountByBin' => [ + 'config' => 20, + 'discovery' => 5, + 'access_policy' => 2, + 'data' => 7, + 'bootstrap' => 4, + 'dynamic_page_cache' => 2, + ], 'CacheSetCount' => 0, 'CacheDeleteCount' => 0, 'CacheTagChecksumCount' => 0, 'CacheTagIsValidCount' => 11, 'CacheTagInvalidationCount' => 0, + 'CacheTagLookupQueryCount' => 6, 'ScriptCount' => 1, 'ScriptBytes' => 123850, 'StylesheetCount' => 2, diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php index ee3d61d033a44f0cd1069e41f93d86e086e9c6b9..ff5d5c79ea5ac7d79e4629c84cc98363ea4d2f21 100644 --- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php +++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php @@ -76,6 +76,7 @@ protected function testFrontPageHotCache(): void { 'CacheTagChecksumCount' => 0, 'CacheTagIsValidCount' => 1, 'CacheTagInvalidationCount' => 0, + 'CacheTagLookupQueryCount' => 1, 'ScriptCount' => 1, 'ScriptBytes' => 11850, 'StylesheetCount' => 2, diff --git a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php index c425f516695e6eec7e216507bbba6c706bea0c25..9026bca418ae28c7eab36085f128ac9d9c47db5b 100644 --- a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php +++ b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php @@ -128,15 +128,68 @@ protected function testAnonymous(): void { $expected = [ 'QueryCount' => 36, 'CacheGetCount' => 122, + 'CacheGetCountByBin' => [ + 'page' => 1, + 'config' => 20, + 'data' => 8, + 'access_policy' => 1, + 'bootstrap' => 8, + 'dynamic_page_cache' => 2, + 'discovery' => 38, + 'render' => 35, + 'default' => 5, + 'entity' => 2, + 'menu' => 2, + ], 'CacheSetCount' => 45, 'CacheDeleteCount' => 0, 'CacheTagChecksumCount' => 37, 'CacheTagIsValidCount' => 43, 'CacheTagInvalidationCount' => 0, + 'CacheTagLookupQueryCount' => 29, + 'CacheTagGroupedLookups' => [ + ['route_match'], + ['access_policies', 'config:user.role.anonymous'], + ['routes'], + ['entity_types'], + ['config:views.view.frontpage'], + ['config:core.extension', 'views_data'], + ['entity_field_info'], + ['entity_bundles'], + ['node_values'], + ['node:1', 'node_list'], + ['user_values'], + ['rendered', 'user:0', 'user_view'], + ['config:filter.format.restricted_html', 'node_view'], + ['block_view', 'config:block.block.stark_site_branding', 'config:system.site'], + ['CACHE_MISS_IF_UNCACHEABLE_HTTP_METHOD:form', 'config:block.block.stark_search_form_narrow', 'config:search.settings'], + ['config:block.block.stark_main_menu', 'config:system.menu.main'], + ['config:block.block.stark_search_form_wide'], + ['config:block.block.stark_account_menu', 'config:system.menu.account'], + ['config:block.block.stark_breadcrumbs'], + ['config:block.block.stark_primary_admin_actions'], + ['config:block.block.stark_messages'], + ['local_task'], + ['config:block.block.stark_primary_local_tasks'], + ['config:block.block.stark_secondary_local_tasks'], + ['config:block.block.stark_help'], + ['config:block.block.stark_powered'], + ['config:block.block.stark_syndicate'], + ['config:block.block.stark_content', 'config:block.block.stark_page_title', 'config:block_list', 'http_response'], + ['library_info'], + ], 'StylesheetCount' => 1, 'StylesheetBytes' => 3450, ]; $this->assertMetrics($expected, $performance_data); + $expected_default_cache_cids = [ + 'views_data:node_field_data:en', + 'views_data:en', + 'views_data:views:en', + 'views_data:node:en', + 'theme_registry:stark', + ]; + $this->assertSame($expected_default_cache_cids, $performance_data->getCacheOperations()['get']['default']); // Test node page. $performance_data = $this->collectPerformanceData(function () { @@ -166,6 +219,34 @@ protected function testAnonymous(): void { 'CacheSetCount' => 16, 'CacheDeleteCount' => 0, 'CacheTagInvalidationCount' => 0, + 'CacheTagLookupQueryCount' => 25, + 'CacheTagGroupedLookups' => [ + ['route_match'], + ['entity_types'], + ['entity_field_info', 'node_values'], + ['access_policies', 'config:user.role.anonymous'], + ['routes'], + ['entity_bundles'], + ['user_values'], + ['rendered', 'user:0', 'user_view'], + ['config:filter.format.restricted_html', 'node:1', 'node_view'], + ['block_view', 'config:block.block.stark_site_branding', 'config:system.site'], + ['CACHE_MISS_IF_UNCACHEABLE_HTTP_METHOD:form', 'config:block.block.stark_search_form_narrow', 'config:search.settings'], + ['config:block.block.stark_main_menu', 'config:system.menu.main'], + ['config:block.block.stark_search_form_wide'], + ['config:block.block.stark_account_menu', 'config:system.menu.account'], + ['config:block.block.stark_breadcrumbs'], + ['config:block.block.stark_primary_admin_actions'], + ['config:block.block.stark_messages'], + ['local_task'], + ['config:block.block.stark_primary_local_tasks'], + ['config:block.block.stark_secondary_local_tasks'], + ['config:block.block.stark_help'], + ['config:block.block.stark_powered'], + ['config:block.block.stark_syndicate'], + ['config:block.block.stark_content', 'config:block.block.stark_page_title', 'config:block_list', 'http_response'], + ['library_info'], + ], 'StylesheetCount' => 1, 'StylesheetBytes' => 3150, ]; @@ -204,6 +285,7 @@ protected function testAnonymous(): void { 'CacheTagChecksumCount' => 23, 'CacheTagIsValidCount' => 32, 'CacheTagInvalidationCount' => 0, + 'CacheTagLookupQueryCount' => 24, 'StylesheetCount' => 1, 'StylesheetBytes' => 3150, ]; @@ -260,6 +342,39 @@ protected function testLogin(): void { 'CacheTagChecksumCount' => 1, 'CacheTagIsValidCount' => 37, 'CacheTagInvalidationCount' => 0, + 'CacheTagLookupQueryCount' => 28, + 'CacheTagGroupedLookups' => [ + // Form submission and login. + ['route_match'], + ['routes'], + ['entity_types'], + ['access_policies', 'config:user.role.anonymous'], + ['entity_field_info', 'user_values'], + // The user page after the redirect. + ['route_match'], + ['entity_types'], + ['entity_field_info'], + ['entity_bundles'], + ['user_values'], + ['access_policies', 'config:user.role.authenticated'], + ['routes'], + ['rendered', 'user:2', 'user_view'], + ['block_view', 'config:block.block.stark_site_branding', 'config:system.site'], + ['CACHE_MISS_IF_UNCACHEABLE_HTTP_METHOD:form', 'config:block.block.stark_search_form_narrow', 'config:search.settings'], + ['config:system.menu.account', 'config:system.menu.main'], + ['config:block.block.stark_main_menu'], + ['config:block.block.stark_search_form_wide'], + ['config:block.block.stark_account_menu'], + ['config:block.block.stark_breadcrumbs'], + ['config:block.block.stark_primary_admin_actions'], + ['config:block.block.stark_messages'], + ['config:block.block.stark_primary_local_tasks', 'local_task'], + ['config:block.block.stark_secondary_local_tasks'], + ['config:block.block.stark_help'], + ['config:block.block.stark_powered'], + ['config:block.block.stark_syndicate'], + ['library_info'], + ], ]; $this->assertMetrics($expected, $performance_data); $this->drupalLogout(); @@ -317,6 +432,7 @@ protected function testLoginBlock(): void { 'CacheTagChecksumCount' => 1, 'CacheTagIsValidCount' => 43, 'CacheTagInvalidationCount' => 0, + 'CacheTagLookupQueryCount' => 29, ]; $this->assertMetrics($expected, $performance_data); } diff --git a/core/tests/Drupal/Tests/PerformanceData.php b/core/tests/Drupal/Tests/PerformanceData.php index 8e75654c9b2528db9ab237fc6d99aecd47ccfd7b..e26c31b939cac04d5c174dfde70a31ad5ba90b2d 100644 --- a/core/tests/Drupal/Tests/PerformanceData.php +++ b/core/tests/Drupal/Tests/PerformanceData.php @@ -7,7 +7,7 @@ /** * Value object to store performance information collected from requests. * - * @see Drupal\Tests\PerformanceTestTrait::collectPerformanceData(). + * @see \Drupal\Tests\PerformanceTestTrait::collectPerformanceData(). */ class PerformanceData { @@ -56,6 +56,13 @@ class PerformanceData { */ protected int $cacheDeleteCount = 0; + /** + * List of cids keyed by operation and bin. + * + * @var string[][] + */ + protected array $cacheOperations = []; + /** * The number of cache tag checksum checks. */ @@ -71,6 +78,13 @@ class PerformanceData { */ protected int $cacheTagInvalidationCount = 0; + /** + * The grouped cache tag lookups. + * + * @var string[] + */ + protected array $cacheTagGroupedLookups = []; + /** * The original return value. */ @@ -207,6 +221,58 @@ public function getCacheGetCount(): int { return $this->cacheGetCount; } + /** + * Sets the cache operations. + * + * @param string[][] $cacheOperations + * List of cids keyed by operation and bin. + * + * @return void + */ + public function setCacheOperations(array $cacheOperations): void { + $this->cacheOperations = $cacheOperations; + } + + /** + * Gets the cache operations. + * + * @return string[][] + * List of cids keyed by operation and bin. + */ + public function getCacheOperations(): array { + return $this->cacheOperations; + } + + /** + * Returns the cache get operation count grouped by bin. + * + * @return int[] + * Count of cache get operations keyed by bin. + */ + public function getCacheGetCountByBin(): array { + return array_map(fn (array $cids) => count($cids), $this->cacheOperations['get'] ?? []); + } + + /** + * Returns the cache set operation count grouped by bin. + * + * @return int[] + * Count of cache set operations keyed by bin. + */ + public function getCacheSetCountByBin(): array { + return array_map(fn (array $cids) => count($cids), $this->cacheOperations['set'] ?? []); + } + + /** + * Returns the cache delete operation count grouped by bin. + * + * @return int[] + * Count of cache delete operations keyed by bin. + */ + public function getCacheDeleteCountByBin(): array { + return array_map(fn (array $cids) => count($cids), $this->cacheOperations['delete'] ?? []); + } + /** * Sets the cache set count. * @@ -307,6 +373,36 @@ public function getCacheTagInvalidationCount(): int { return $this->cacheTagInvalidationCount; } + /** + * Sets the grouped cache tag lookups. + * + * @param string[] $groupedLookups + * Grouped cache tag lookups by query. + */ + public function setCacheTagGroupedLookups(array $groupedLookups): void { + $this->cacheTagGroupedLookups = $groupedLookups; + } + + /** + * Gets the grouped cache tag lookups. + * + * @@return string[] + * Grouped cache tag lookups by query. + */ + public function getCacheTagGroupedLookups(): array { + return $this->cacheTagGroupedLookups; + } + + /** + * Gets the cache tag lookup query count. + * + * @return int + * The number of cache tag lookup queries recorded. + */ + public function getCacheTagLookupQueryCount(): int { + return count($this->cacheTagGroupedLookups); + } + /** * Sets the original return value. * diff --git a/core/tests/Drupal/Tests/PerformanceTestTrait.php b/core/tests/Drupal/Tests/PerformanceTestTrait.php index d7b780f3aa50b035fbd1cddc2d007ff19de2ecec..39b08b68b75c0e8c8f7460cfdbd857813eb07c41 100644 --- a/core/tests/Drupal/Tests/PerformanceTestTrait.php +++ b/core/tests/Drupal/Tests/PerformanceTestTrait.php @@ -132,26 +132,41 @@ public function collectPerformanceData(callable $callable, ?string $service_name $cache_tag_is_valid_count = 0; $cache_tag_invalidation_count = 0; $cache_tag_checksum_count = 0; + $cache_tag_lookup_query_args = []; foreach ($performance_test_data['database_events'] as $event) { + $normalized_query = static::normalizeQuery($event->queryString, $this->databasePrefix); + // Don't log queries from the database cache backend because they're // logged separately as cache operations. if (!static::isDatabaseCache($event)) { - // Make the query easier to read and log it. - static::logQuery( - $performance_data, - str_replace([$this->databasePrefix, "\r\n", "\r", "\n"], ['', ' ', ' ', ' '], $event->queryString), - $event->args - ); + static::logQuery($performance_data, $normalized_query, $event->args); + } + // Keep track of cache tag lookup queries. + elseif (str_starts_with($normalized_query, 'SELECT "tag", "invalidations" FROM "cachetags"')) { + $cache_tag_lookup_query_args[] = array_values($event->args); } } + $cache_operations = []; foreach ($performance_test_data['cache_operations'] as $operation) { if (in_array($operation['operation'], ['get', 'getMultiple'], TRUE)) { + if (!isset($cache_operations['get'][$operation['bin']])) { + $cache_operations['get'][$operation['bin']] = []; + } + $cache_operations['get'][$operation['bin']][] = $operation['cids']; $cache_get_count++; } elseif (in_array($operation['operation'], ['set', 'setMultiple'], TRUE)) { + if (!isset($cache_operations['get'][$operation['bin']])) { + $cache_operations['set'][$operation['bin']] = []; + } + $cache_operations['set'][$operation['bin']][] = $operation['cids']; $cache_set_count++; } elseif (in_array($operation['operation'], ['delete', 'deleteMultiple'], TRUE)) { + if (!isset($cache_operations['delete'][$operation['bin']])) { + $cache_operations['delete'][$operation['bin']] = []; + } + $cache_operations['delete'][$operation['bin']][] = $operation['cids']; $cache_delete_count++; } } @@ -168,6 +183,8 @@ public function collectPerformanceData(callable $callable, ?string $service_name $performance_data->setCacheTagChecksumCount($cache_tag_checksum_count); $performance_data->setCacheTagIsValidCount($cache_tag_is_valid_count); $performance_data->setCacheTagInvalidationCount($cache_tag_invalidation_count); + $performance_data->setCacheOperations($cache_operations); + $performance_data->setCacheTagGroupedLookups($cache_tag_lookup_query_args); } return $performance_data; @@ -681,4 +698,19 @@ protected function getMetrics(PerformanceData $performance_data): array { ]; } + /** + * Normalizes a query by removing the database prefix and newlines. + * + * @param string $query_string + * The query string to normalize. + * @param string $database_prefix + * The database prefix to remove from the query. + * + * @return string + * The normalized query string. + */ + protected static function normalizeQuery(string $query_string, string $database_prefix): string { + return str_replace([$database_prefix, "\r\n", "\r", "\n"], ['', ' ', ' ', ' '], $query_string); + } + }