summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/queryStats/query_stats_server_status_metrics.js
blob: 2bc7d8088982a33548cf4f969f0741b498f66dea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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);
}());