Skip to content
Snippets Groups Projects
Commit 51b09b25 authored by catch's avatar catch
Browse files

Issue #3421164 by kristiaanvandeneynde, catch: Log every individual query in performance tests

parent 5c30729a
No related branches found
No related tags found
30 merge requests!11131[10.4.x-only-DO-NOT-MERGE]: Issue ##2842525 Ajax attached to Views exposed filter form does not trigger callbacks,!9470[10.3.x-only-DO-NOT-MERGE]: #3331771 Fix file_get_contents(): Passing null to parameter,!8540Issue #3457061: Bootstrap Modal dialog Not closing after 10.3.0 Update,!8528Issue #3456871 by Tim Bozeman: Support NULL services,!8373Issue #3427374 by danflanagan8, Vighneshh: taxonomy_tid ViewsArgumentDefault...,!7526Expose roles in response,!7352Draft: Resolve #3203489 "Set filename as",!6791Issue #3163299: Ajax exposed filters not working for multiple instances of the same Views block placed on one page,!3878Removed unused condition head title for views,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3668Resolve #3347842 "Deprecate the trusted",!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3133core/modules/system/css/components/hidden.module.css,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2794Issue #3100732: Allow specifying `meta` data on JSON:API objects,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!877Issue #2708101: Default value for link text is not saved,!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493
Pipeline #103958 canceled
Pipeline: drupal

#103961

    ......@@ -35,7 +35,21 @@ public function testFrontPageAuthenticatedWarmCache(): void {
    $performance_data = $this->collectPerformanceData(function () {
    $this->drupalGet('<front>');
    }, 'authenticatedFrontPage');
    $this->assertCountBetween(9, 11, $performance_data->getQueryCount());
    $expected_queries = [
    'SELECT "session" FROM "sessions" WHERE "sid" = "SESSION_ID" LIMIT 0, 1',
    'SELECT * FROM "users_field_data" "u" WHERE "u"."uid" = "8" AND "u"."default_langcode" = 1',
    'SELECT "roles_target_id" FROM "user__roles" WHERE "entity_id" = "8"',
    'SELECT "config"."name" AS "name" FROM "config" "config" WHERE ("collection" = "") AND ("name" LIKE "language.entity.%" ESCAPE ' . "'\\\\'" . ') ORDER BY "collection" ASC, "name" ASC',
    'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "system.maintenance_mode" ) AND "collection" = "state"',
    'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "twig_extension_hash_prefix" ) AND "collection" = "state"',
    'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "asset.css_js_query_string" ) AND "collection" = "state"',
    'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "drupal.test_wait_terminate" ) AND "collection" = "state"',
    'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "system.cron_last" ) AND "collection" = "state"',
    ];
    $recorded_queries = $performance_data->getQueries();
    $this->assertSame($expected_queries, $recorded_queries);
    $this->assertSame(9, $performance_data->getQueryCount());
    $this->assertSame(45, $performance_data->getCacheGetCount());
    $this->assertSame(0, $performance_data->getCacheSetCount());
    $this->assertSame(0, $performance_data->getCacheDeleteCount());
    ......
    ......@@ -52,6 +52,10 @@ public function testFrontPageHotCache() {
    $this->drupalGet('<front>');
    }, 'umamiFrontPageHotCache');
    $this->assertSession()->pageTextContains('Umami');
    $expected_queries = [];
    $recorded_queries = $performance_data->getQueries();
    $this->assertSame($expected_queries, $recorded_queries);
    $this->assertSame(0, $performance_data->getQueryCount());
    $this->assertSame(1, $performance_data->getCacheGetCount());
    $this->assertSame(0, $performance_data->getCacheSetCount());
    ......
    ......@@ -26,6 +26,11 @@ class PerformanceData {
    */
    protected int $queryCount = 0;
    /**
    * The individual database queries recorded.
    */
    protected array $queries = [];
    /**
    * The number of cache gets recorded.
    */
    ......@@ -102,13 +107,24 @@ public function getScriptCount(): int {
    }
    /**
    * Sets the query count.
    * Logs a database query.
    *
    * @param int $count
    * The number of database queries recorded.
    * @param string $query
    * The database query recorded.
    */
    public function logQuery(string $query): void {
    $this->queries[] = $query;
    $this->queryCount++;
    }
    /**
    * Gets the queries.
    *
    * @return string[]
    * The database queries recorded.
    */
    public function setQueryCount(int $count): void {
    $this->queryCount = $count;
    public function getQueries(): array {
    return $this->queries;
    }
    /**
    ......
    ......@@ -119,9 +119,13 @@ public function collectPerformanceData(callable $callable, ?string $service_name
    $performance_test_data = $collection->get('performance_test_data');
    if ($performance_test_data) {
    // This property is set by \Drupal\Core\Test\TestSetupTrait and is needed.
    if (!isset($this->databasePrefix)) {
    throw new \Exception('Cannot log queries without knowing the database prefix.');
    }
    // Separate queries into two buckets, one for queries from the cache
    // backend, and one for everything else (including those for cache tags).
    $query_count = 0;
    $cache_get_count = 0;
    $cache_set_count = 0;
    $cache_delete_count = 0;
    ......@@ -132,7 +136,12 @@ public function collectPerformanceData(callable $callable, ?string $service_name
    // Don't log queries from the database cache backend because they're
    // logged separately as cache operations.
    if (!static::isDatabaseCache($event)) {
    $query_count++;
    // 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
    );
    }
    }
    foreach ($performance_test_data['cache_operations'] as $operation) {
    ......@@ -153,7 +162,6 @@ public function collectPerformanceData(callable $callable, ?string $service_name
    CacheTagOperation::invalidateTags => $cache_tag_invalidation_count++,
    };
    }
    $performance_data->setQueryCount($query_count);
    $performance_data->setCacheGetCount($cache_get_count);
    $performance_data->setCacheSetCount($cache_set_count);
    $performance_data->setCacheDeleteCount($cache_delete_count);
    ......@@ -165,6 +173,96 @@ public function collectPerformanceData(callable $callable, ?string $service_name
    return $performance_data;
    }
    /**
    * Logs a query in the performance data.
    *
    * @param \Drupal\Tests\PerformanceData $performance_data
    * The performance data object to log the query on.
    * @param string $query
    * The raw query.
    * @param array $args
    * The query arguments.
    */
    protected static function logQuery(PerformanceData $performance_data, string $query, array $args): void {
    // Make queries with random variables invariable.
    if (str_starts_with($query, 'INSERT INTO "semaphore"')) {
    $args[':db_insert_placeholder_1'] = 'LOCK_ID';
    $args[':db_insert_placeholder_2'] = 'EXPIRE';
    }
    elseif (str_starts_with($query, 'DELETE FROM "semaphore"')) {
    $args[':db_condition_placeholder_1'] = 'LOCK_ID';
    }
    elseif (str_starts_with($query, 'SELECT "base_table"."uid" AS "uid", "base_table"."uid" AS "base_table_uid" FROM "users"')) {
    $args[':db_condition_placeholder_0'] = 'ACCOUNT_NAME';
    }
    elseif (str_starts_with($query, 'SELECT COUNT(*) AS "expression" FROM (SELECT 1 AS "expression" FROM "flood" "f"')) {
    $args[':db_condition_placeholder_1'] = 'CLIENT_IP';
    $args[':db_condition_placeholder_2'] = 'TIMESTAMP';
    }
    elseif (str_starts_with($query, 'UPDATE "users_field_data" SET "login"')) {
    $args[':db_update_placeholder_0'] = 'TIMESTAMP';
    }
    elseif (str_starts_with($query, 'INSERT INTO "sessions"')) {
    $args[':db_insert_placeholder_0'] = 'SESSION_ID';
    $args[':db_insert_placeholder_2'] = 'CLIENT_IP';
    $args[':db_insert_placeholder_3'] = 'SESSION_DATA';
    $args[':db_insert_placeholder_4'] = 'TIMESTAMP';
    }
    elseif (str_starts_with($query, 'SELECT "session" FROM "sessions"')) {
    $args[':sid'] = 'SESSION_ID';
    }
    elseif (str_starts_with($query, 'SELECT 1 AS "expression" FROM "sessions"')) {
    $args[':db_condition_placeholder_0'] = 'SESSION_ID';
    }
    elseif (str_starts_with($query, 'DELETE FROM "sessions"')) {
    $args[':db_condition_placeholder_0'] = 'TIMESTAMP';
    }
    elseif (str_starts_with($query, 'INSERT INTO "watchdog"')) {
    $args[':db_insert_placeholder_3'] = 'WATCHDOG_DATA';
    $args[':db_insert_placeholder_6'] = 'LOCATION';
    $args[':db_insert_placeholder_7'] = 'REFERER';
    $args[':db_insert_placeholder_8'] = 'CLIENT_IP';
    $args[':db_insert_placeholder_9'] = 'TIMESTAMP';
    }
    elseif (str_starts_with($query, 'SELECT "name", "route", "fit" FROM "router"')) {
    if (preg_match('@/sites/simpletest/(\d{8})/files/css/(.*)@', $args[':patterns__0'], $matches)) {
    $search = [$matches[1], $matches[2]];
    $replace = ['TEST_ID', 'CSS_FILE'];
    foreach ($args as $name => $arg) {
    if (!is_string($arg)) {
    continue;
    }
    $args[$name] = str_replace($search, $replace, $arg);
    }
    }
    }
    elseif (str_starts_with($query, 'SELECT "base_table"."id" AS "id", "base_table"."path" AS "path", "base_table"."alias" AS "alias", "base_table"."langcode" AS "langcode" FROM "path_alias" "base_table"')) {
    if (str_contains($args[':db_condition_placeholder_1'], 'files/css')) {
    $args[':db_condition_placeholder_1'] = 'CSS_FILE';
    }
    }
    // Inline query arguments and log the query.
    $query = str_replace(array_keys($args), array_values(static::quoteQueryArgs($args)), $query);
    $performance_data->logQuery($query);
    }
    /**
    * Wraps query arguments in double quotes if they're a string.
    *
    * @param array $args
    * The raw query arguments.
    *
    * @return array
    * The conditionally quoted query arguments.
    */
    protected static function quoteQueryArgs(array $args): array {
    $conditionalQuote = function ($arg) {
    return is_int($arg) || is_float($arg) ? $arg : '"' . $arg . '"';
    };
    return array_map($conditionalQuote, $args);
    }
    /**
    * Gets the chromedriver performance log and extracts metrics from it.
    *
    ......
    • catch @catch

      mentioned in commit ab2ab261

      ·

      mentioned in commit ab2ab261

      Toggle commit list
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment