From ae65ecae5514adc99d60b7396137a1acf2b44335 Mon Sep 17 00:00:00 2001 From: Will Buerger Date: Wed, 17 May 2023 11:49:47 +0000 Subject: SERVER-76427 Rename $telemetry to $queryStats --- .../query_stats_server_status_metrics.js | 181 +++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 jstests/noPassthrough/queryStats/query_stats_server_status_metrics.js (limited to 'jstests/noPassthrough/queryStats/query_stats_server_status_metrics.js') diff --git a/jstests/noPassthrough/queryStats/query_stats_server_status_metrics.js b/jstests/noPassthrough/queryStats/query_stats_server_status_metrics.js new file mode 100644 index 00000000000..2bc7d808898 --- /dev/null +++ b/jstests/noPassthrough/queryStats/query_stats_server_status_metrics.js @@ -0,0 +1,181 @@ +/** + * Test the telemetry related serverStatus metrics. + * @tags: [featureFlagQueryStats] + */ +load('jstests/libs/analyze_plan.js'); + +(function() { +"use strict"; + +function runTestWithMongodOptions(mongodOptions, test, testOptions) { + const conn = MongoRunner.runMongod(mongodOptions); + const testDB = conn.getDB('test'); + const coll = testDB[jsTestName()]; + + test(conn, testDB, coll, testOptions); + + MongoRunner.stopMongod(conn); +} + +/** + * Test serverStatus metric which counts the number of evicted entries. + * + * testOptions must include `resetCacheSize` bool field; e.g., { resetCacheSize : true } + */ +function evictionTest(conn, testDB, coll, testOptions) { + const evictedBefore = testDB.serverStatus().metrics.queryStats.numEvicted; + assert.eq(evictedBefore, 0); + for (var i = 0; i < 4000; i++) { + let query = {}; + query["foo" + i] = "bar"; + coll.aggregate([{$match: query}]).itcount(); + } + if (!testOptions.resetCacheSize) { + const evictedAfter = testDB.serverStatus().metrics.queryStats.numEvicted; + assert.gt(evictedAfter, 0); + return; + } + // Make sure number of evicted entries increases when the cache size is reset, which forces out + // least recently used entries to meet the new, smaller size requirement. + assert.eq(testDB.serverStatus().metrics.queryStats.numEvicted, 0); + assert.commandWorked( + testDB.adminCommand({setParameter: 1, internalQueryStatsCacheSize: "1MB"})); + const evictedAfter = testDB.serverStatus().metrics.queryStats.numEvicted; + assert.gt(evictedAfter, 0); +} + +/** + * Test serverStatus metric which counts the number of requests for which telemetry is not collected + * due to rate-limiting. + * + * testOptions must include `samplingRate` and `numRequests` number fields; + * e.g., { samplingRate: 2147483647, numRequests: 20 } + */ +function countRateLimitedRequestsTest(conn, testDB, coll, testOptions) { + const numRateLimitedRequestsBefore = + testDB.serverStatus().metrics.queryStats.numRateLimitedRequests; + assert.eq(numRateLimitedRequestsBefore, 0); + + coll.insert({a: 0}); + + // Running numRequests / 2 times since we dispatch two requests per iteration + for (var i = 0; i < testOptions.numRequests / 2; i++) { + coll.find({a: 0}).toArray(); + coll.aggregate([{$match: {a: 1}}]); + } + + const numRateLimitedRequestsAfter = + testDB.serverStatus().metrics.queryStats.numRateLimitedRequests; + + if (testOptions.samplingRate === 0) { + // Telemetry should not be collected for any requests. + assert.eq(numRateLimitedRequestsAfter, testOptions.numRequests); + } else if (testOptions.samplingRate >= testOptions.numRequests) { + // Telemetry should be collected for all requests. + assert.eq(numRateLimitedRequestsAfter, 0); + } else { + // Telemetry should be collected for some but not all requests. + assert.gt(numRateLimitedRequestsAfter, 0); + assert.lt(numRateLimitedRequestsAfter, testOptions.numRequests); + } +} + +function telemetryStoreSizeEstimateTest(conn, testDB, coll, testOptions) { + assert.eq(testDB.serverStatus().metrics.queryStats.queryStatsStoreSizeEstimateBytes, 0); + let halfWayPointSize; + // Only using three digit numbers (eg 100, 101) means the string length will be the same for all + // entries and therefore the key size will be the same for all entries, which makes predicting + // the total size of the store clean and easy. + for (var i = 100; i < 200; i++) { + coll.aggregate([{$match: {["foo" + i]: "bar"}}]).itcount(); + if (i == 150) { + halfWayPointSize = + testDB.serverStatus().metrics.queryStats.queryStatsStoreSizeEstimateBytes; + } + } + // Confirm that telemetry store has grown and size is non-zero. + assert.gt(halfWayPointSize, 0); + const fullSize = testDB.serverStatus().metrics.queryStats.queryStatsStoreSizeEstimateBytes; + assert.gt(fullSize, 0); + // Make sure the final telemetry store size is twice as much as the halfway point size (+/- 5%) + assert(fullSize >= halfWayPointSize * 1.95 && fullSize <= halfWayPointSize * 2.05, + tojson({fullSize, halfWayPointSize})); +} + +function telemetryStoreWriteErrorsTest(conn, testDB, coll, testOptions) { + const debugBuild = testDB.adminCommand('buildInfo').debug; + if (debugBuild) { + jsTestLog("Skipping telemetry store write errors test because debug build will tassert."); + return; + } + + const errorsBefore = testDB.serverStatus().metrics.queryStats.numQueryStatsStoreWriteErrors; + assert.eq(errorsBefore, 0); + for (let i = 0; i < 5; i++) { + // Command should succeed and record the error. + let query = {}; + query["foo" + i] = "bar"; + coll.aggregate([{$match: query}]).itcount(); + } + + // Make sure that we recorded a write error for each run. + // TODO SERVER-73152 we attempt to write to the telemetry store twice for each aggregate, which + // seems wrong. + assert.eq(testDB.serverStatus().metrics.queryStats.numQueryStatsStoreWriteErrors, 10); +} + +/** + * In this configuration, we insert enough entries into the telemetry store to trigger LRU + * eviction. + */ +runTestWithMongodOptions({ + setParameter: {internalQueryStatsCacheSize: "1MB", internalQueryStatsSamplingRate: -1}, +}, + evictionTest, + {resetCacheSize: false}); +/** + * In this configuration, eviction is triggered only when the telemetry store size is reset. + * */ +runTestWithMongodOptions({ + setParameter: {internalQueryStatsCacheSize: "4MB", internalQueryStatsSamplingRate: -1}, +}, + evictionTest, + {resetCacheSize: true}); + +/** + * In this configuration, every query is sampled, so no requests should be rate-limited. + */ +runTestWithMongodOptions({ + setParameter: {internalQueryStatsSamplingRate: -1}, +}, + countRateLimitedRequestsTest, + {samplingRate: 2147483647, numRequests: 20}); + +/** + * In this configuration, the sampling rate is set so that some but not all requests are + * rate-limited. + */ +runTestWithMongodOptions({ + setParameter: {internalQueryStatsSamplingRate: 10}, +}, + countRateLimitedRequestsTest, + {samplingRate: 10, numRequests: 20}); + +/** + * Sample all queries and assert that the size of telemetry store is equal to num entries * entry + * size + */ +runTestWithMongodOptions({ + setParameter: {internalQueryStatsSamplingRate: -1}, +}, + telemetryStoreSizeEstimateTest); + +/** + * Use a very small telemetry store size and assert that errors in writing to the telemetry store + * are tracked. + */ +runTestWithMongodOptions({ + setParameter: {internalQueryStatsCacheSize: "0.00001MB", internalQueryStatsSamplingRate: -1}, +}, + telemetryStoreWriteErrorsTest); +}()); -- cgit v1.2.1