92 lines
3.5 KiB
JavaScript
92 lines
3.5 KiB
JavaScript
/**
|
|
* Utility methods for reading planCache counters
|
|
*/
|
|
|
|
import {getCachedPlan, getPlanStage} from "jstests/libs/analyze_plan.js";
|
|
import {getLatestProfilerEntry} from "jstests/libs/profiler.js";
|
|
|
|
export function getPlanCacheSize(db) {
|
|
return db.serverStatus().metrics.query.planCache.totalSizeEstimateBytes;
|
|
}
|
|
|
|
export function getPlanCacheNumEntries(db) {
|
|
return db.serverStatus().metrics.query.planCache.totalQueryShapes;
|
|
}
|
|
|
|
function getPlansForCacheEntry(coll, match) {
|
|
const matchingCacheEntries = coll.getPlanCache().list([{$match: match}]);
|
|
assert.eq(matchingCacheEntries.length, 1, coll.getPlanCache().list());
|
|
return matchingCacheEntries[0];
|
|
}
|
|
|
|
function planHasIxScanStageForIndex(planStats, indexName) {
|
|
const stage = getPlanStage(planStats, "IXSCAN");
|
|
return (stage === null) ? false : indexName === stage.indexName;
|
|
}
|
|
|
|
export function assertCacheUsage(coll,
|
|
pipeline,
|
|
fromMultiPlanning,
|
|
cacheEntryVersion,
|
|
cacheEntryIsActive,
|
|
cachedIndexName,
|
|
aggOptions = {}) {
|
|
const profileObj = getLatestProfilerEntry(
|
|
coll.getDB(), {op: {$in: ["command", "getmore"]}, ns: coll.getFullName()});
|
|
const queryHash = profileObj.queryHash;
|
|
const planCacheKey = profileObj.planCacheKey;
|
|
assert.eq(fromMultiPlanning, !!profileObj.fromMultiPlanner, profileObj);
|
|
|
|
const entry = getPlansForCacheEntry(coll, {queryHash: queryHash});
|
|
assert.eq(cacheEntryVersion, entry.version, entry);
|
|
assert.eq(cacheEntryIsActive, entry.isActive, entry);
|
|
|
|
// If the entry is active, we should have a plan cache key.
|
|
if (entry.isActive) {
|
|
assert(entry.planCacheKey);
|
|
}
|
|
if (planCacheKey) {
|
|
assert.eq(entry.planCacheKey, planCacheKey);
|
|
const explain = coll.explain().aggregate(pipeline, aggOptions);
|
|
const explainKey = explain.hasOwnProperty("queryPlanner")
|
|
? explain.queryPlanner.planCacheKey
|
|
: explain.stages[0].$cursor.queryPlanner.planCacheKey;
|
|
assert.eq(explainKey, entry.planCacheKey);
|
|
}
|
|
if (cacheEntryVersion === 2) {
|
|
assert(entry.cachedPlan.stages.includes(cachedIndexName), entry);
|
|
} else {
|
|
assert(planHasIxScanStageForIndex(getCachedPlan(entry.cachedPlan), cachedIndexName), entry);
|
|
}
|
|
}
|
|
|
|
export function setUpActiveCacheEntry(
|
|
coll, pipeline, cacheEntryVersion, cachedIndexName, assertFn) {
|
|
// For the first run, the query should go through multiplanning and create inactive cache entry.
|
|
assertFn(coll.aggregate(pipeline));
|
|
assertCacheUsage(coll,
|
|
pipeline,
|
|
true /*multiPlanning*/,
|
|
cacheEntryVersion,
|
|
false /*cacheEntryIsActive*/,
|
|
cachedIndexName);
|
|
|
|
// After the second run, the inactive cache entry should be promoted to an active entry.
|
|
assertFn(coll.aggregate(pipeline));
|
|
assertCacheUsage(coll,
|
|
pipeline,
|
|
true /*multiPlanning*/,
|
|
cacheEntryVersion,
|
|
true /*cacheEntryIsActive*/,
|
|
cachedIndexName);
|
|
|
|
// For the third run, the active cached query should be used.
|
|
assertFn(coll.aggregate(pipeline));
|
|
assertCacheUsage(coll,
|
|
pipeline,
|
|
false /*multiPlanning*/,
|
|
cacheEntryVersion,
|
|
true /*cacheEntryIsActive*/,
|
|
cachedIndexName);
|
|
}
|