Skip to content

Commit 1b78c20

Browse files
authored
feat(explain-aggregations): aggregations in explain-helper COMPASS-5786 (#3088)
1 parent 2f4bec7 commit 1b78c20

23 files changed

+4481
-51
lines changed

Diff for: packages/compass-explain-plan/src/components/explain-body/explain-body.jsx

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ class ExplainBody extends Component {
2828
indexType: PropTypes.oneOf(INDEX_TYPES).isRequired,
2929
index: PropTypes.object,
3030
viewType: PropTypes.string.isRequired,
31-
rawExplainObject: PropTypes.object.isRequired,
3231
originalExplainData: PropTypes.object.isRequired,
3332
}),
3433
treeStages: PropTypes.object.isRequired,

Diff for: packages/compass-explain-plan/src/components/explain-body/explain-body.spec.jsx

-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ describe('ExplainBody [Component]', function () {
1414
let component;
1515
const explain = {
1616
viewType: EXPLAIN_VIEWS.tree,
17-
rawExplainObject: {},
1817
nReturned: 0,
1918
totalKeysExamined: 0,
2019
totalDocsExamined: 0,
@@ -45,7 +44,6 @@ describe('ExplainBody [Component]', function () {
4544
let component;
4645
const explain = {
4746
viewType: EXPLAIN_VIEWS.json,
48-
rawExplainObject: {},
4947
nReturned: 0,
5048
totalKeysExamined: 0,
5149
totalDocsExamined: 0,
@@ -79,7 +77,6 @@ describe('ExplainBody [Component]', function () {
7977
let component;
8078
const explain = {
8179
viewType: EXPLAIN_VIEWS.tree,
82-
rawExplainObject: {},
8380
nReturned: 0,
8481
totalKeysExamined: 0,
8582
totalDocsExamined: 0,

Diff for: packages/compass-explain-plan/src/components/explain-json/explain-json.spec.jsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ describe('ExplainJSON [Component]', function () {
1414
const appRegistry = new AppRegistry();
1515

1616
beforeEach(function () {
17-
component = mount(<ExplainJSON rawExplainObject={originalExplainData} />);
17+
component = mount(
18+
<ExplainJSON originalExplainData={originalExplainData} />
19+
);
1820
});
1921

2022
afterEach(function () {

Diff for: packages/compass-explain-plan/src/components/explain-states/explain-states.jsx

-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ class ExplainStates extends Component {
6262
indexType: PropTypes.oneOf(INDEX_TYPES).isRequired,
6363
index: PropTypes.object,
6464
viewType: PropTypes.string.isRequired,
65-
rawExplainObject: PropTypes.object.isRequired,
6665
originalExplainData: PropTypes.object.isRequired,
6766
explainState: PropTypes.string.isRequired,
6867
error: PropTypes.object,

Diff for: packages/compass-explain-plan/src/components/explain-states/explain-states.spec.jsx

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ describe('ExplainStates [Component]', function () {
2020
const isEditable = false;
2121
const explain = {
2222
viewType: 'tree',
23-
rawExplainObject: {},
2423
nReturned: 0,
2524
totalKeysExamined: 0,
2625
totalDocsExamined: 0,

Diff for: packages/compass-explain-plan/src/modules/explain.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const INITIAL_STATE = {
5555
namespace: '',
5656
numShards: 0,
5757
parsedQuery: {},
58-
rawExplainObject: {},
58+
executionStats: {},
5959
originalExplainData: {},
6060
totalDocsExamined: 0,
6161
totalKeysExamined: 0,
@@ -228,7 +228,7 @@ const parseExplainPlan = (explain, data) => {
228228
executionTimeMillis,
229229
totalKeysExamined,
230230
totalDocsExamined,
231-
rawExplainObject,
231+
executionStats,
232232
originalExplainData,
233233
usedIndexes,
234234
isCovered,
@@ -248,7 +248,7 @@ const parseExplainPlan = (explain, data) => {
248248
executionTimeMillis,
249249
totalKeysExamined,
250250
totalDocsExamined,
251-
rawExplainObject,
251+
executionStats,
252252
originalExplainData,
253253
usedIndexes,
254254
isCovered,

Diff for: packages/compass-explain-plan/src/modules/explain.spec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const explainExample = {
3030
namespace: 'db.coll',
3131
numShards: 0,
3232
parsedQuery: {},
33-
rawExplainObject: {},
33+
executionStats: {},
3434
originalExplainData: {},
3535
totalDocsExamined: 18801,
3636
totalKeysExamined: 0,
@@ -150,7 +150,7 @@ describe('explain module', function () {
150150
namespace: '',
151151
numShards: 0,
152152
parsedQuery: {},
153-
rawExplainObject: {},
153+
executionStats: {},
154154
originalExplainData: {},
155155
totalDocsExamined: 0,
156156
totalKeysExamined: 0,

Diff for: packages/compass-explain-plan/src/modules/tree-stages.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,11 @@ const computeExecTimes = (node) => {
145145
const changeTreeStages = (state, action) => {
146146
const explain = action.explain;
147147

148-
if (!has(explain.rawExplainObject, 'executionStats.executionStages')) {
148+
if (!has(explain.executionStats, 'executionStages')) {
149149
return INITIAL_STATE;
150150
}
151151

152-
const parsedExplain = parseExplain(
153-
explain.rawExplainObject.executionStats.executionStages
154-
);
152+
const parsedExplain = parseExplain(explain.executionStats.executionStages);
155153

156154
const tree = d3.layout
157155
.flextree()

Diff for: packages/compass-explain-plan/src/modules/tree-stages.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const explainExample = {
1818
namespace: 'db.coll',
1919
numShards: 0,
2020
parsedQuery: {},
21-
rawExplainObject: {},
21+
executionStats: {},
2222
totalDocsExamined: 18801,
2323
totalKeysExamined: 0,
2424
usedIndexes: [],

Diff for: packages/compass-explain-plan/src/stores/store.spec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ describe('Explain Plan Store', function () {
142142
namespace: 'db.coll',
143143
numShards: 0,
144144
parsedQuery: {},
145-
rawExplainObject: {},
145+
executionStats: {},
146146
originalExplainData: {},
147147
totalDocsExamined: 18801,
148148
totalKeysExamined: 0,
@@ -181,7 +181,7 @@ describe('Explain Plan Store', function () {
181181
namespace: 'db.coll',
182182
numShards: 0,
183183
parsedQuery: {},
184-
rawExplainObject: {},
184+
executionStats: {},
185185
totalDocsExamined: 18801,
186186
totalKeysExamined: 0,
187187
usedIndexes: [],
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {
2+
isAggregationExplain,
3+
isShardedAggregationExplain,
4+
getStageCursorKey,
5+
} from 'mongodb-explain-compat';
6+
7+
import type { Stage } from './index';
8+
9+
export type ExecutionStats = {
10+
executionSuccess: boolean;
11+
nReturned: number;
12+
executionTimeMillis: number;
13+
totalKeysExamined: number;
14+
totalDocsExamined: number;
15+
executionStages: Stage;
16+
allPlansExecution: unknown[];
17+
};
18+
19+
export const getExecutionStats = (explain: Stage): ExecutionStats => {
20+
const executionStats = isAggregationExplain(explain)
21+
? _getAggregationStats(explain)
22+
: _getFindStats(explain);
23+
return executionStats;
24+
};
25+
const _getAggregationStats = (explain: Stage): ExecutionStats => {
26+
return isShardedAggregationExplain(explain)
27+
? _getShardedAggregationStats(explain)
28+
: _getUnshardedAggregationStats(explain);
29+
};
30+
const _getUnshardedAggregationStats = (explain: Stage): ExecutionStats => {
31+
const firstStage = explain.stages[0];
32+
const cursorKey = getStageCursorKey(firstStage);
33+
if (!cursorKey) {
34+
throw new Error('Can not find a cursor stage.');
35+
}
36+
37+
const lastStage = explain.stages[explain.stages.length - 1];
38+
39+
const stats = _getFindStats(firstStage[cursorKey]);
40+
stats.nReturned = lastStage.nReturned;
41+
stats.executionTimeMillis = sumArrayProp(
42+
explain.stages,
43+
'executionTimeMillisEstimate'
44+
);
45+
return stats;
46+
};
47+
const _getShardedAggregationStats = (explain: Stage): ExecutionStats => {
48+
const shardStats = [];
49+
for (const shardName in explain.shards) {
50+
const stats = explain.shards[shardName].stages
51+
? _getUnshardedAggregationStats(explain.shards[shardName])
52+
: _getFindStats(explain.shards[shardName]);
53+
54+
shardStats.push({
55+
shardName,
56+
...stats,
57+
});
58+
}
59+
60+
const nReturned = sumArrayProp(shardStats, 'nReturned');
61+
const executionTimeMillis = sumArrayProp(shardStats, 'executionTimeMillis');
62+
const totalKeysExamined = sumArrayProp(shardStats, 'totalKeysExamined');
63+
const totalDocsExamined = sumArrayProp(shardStats, 'totalDocsExamined');
64+
const response = {
65+
nReturned,
66+
executionTimeMillis,
67+
totalKeysExamined,
68+
totalDocsExamined,
69+
allPlansExecution: [],
70+
executionSuccess: true,
71+
executionStages: {
72+
stage: shardStats.length === 1 ? 'SINGLE_SHARD' : 'SHARD_MERGE',
73+
nReturned,
74+
executionTimeMillis,
75+
totalKeysExamined,
76+
totalDocsExamined,
77+
shards: shardStats,
78+
},
79+
} as unknown as ExecutionStats;
80+
81+
return response;
82+
};
83+
const _getFindStats = (explain: Stage): ExecutionStats => {
84+
return explain.executionStats;
85+
};
86+
87+
function sumArrayProp<T>(arr: T[], prop: keyof T): number {
88+
return arr.reduce((acc, x) => acc + Number(x[prop] ?? 0), 0);
89+
}

Diff for: packages/explain-plan-helper/src/get-planner-info.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {
2+
isAggregationExplain,
3+
isShardedAggregationExplain,
4+
isShardedFindExplain,
5+
getStageCursorKey,
6+
} from 'mongodb-explain-compat';
7+
8+
import type { Stage } from './index';
9+
10+
type PlannerInfo = {
11+
namespace: string;
12+
parsedQuery: Stage;
13+
};
14+
15+
export const getPlannerInfo = (explain: Stage): PlannerInfo => {
16+
const queryPlanner = isAggregationExplain(explain)
17+
? _getAggregationPlanner(explain)
18+
: _getFindPlanner(explain);
19+
return {
20+
namespace: queryPlanner.namespace,
21+
parsedQuery: queryPlanner.parsedQuery,
22+
};
23+
};
24+
const _getAggregationPlanner = (explain: Stage): Stage => {
25+
return isShardedAggregationExplain(explain)
26+
? _getShardedAggregationPlanner(explain)
27+
: _getUnshardedAggregationPlanner(explain);
28+
};
29+
const _getUnshardedAggregationPlanner = (explain: Stage): Stage => {
30+
const firstStage = explain.stages[0];
31+
const cursorKey = getStageCursorKey(firstStage);
32+
if (!cursorKey) {
33+
throw new Error('Can not find a cursor stage.');
34+
}
35+
return _getUnshardedFindPlanner(firstStage[cursorKey]);
36+
};
37+
const _getShardedAggregationPlanner = (explain: Stage): Stage => {
38+
// The first shard
39+
const firstShardName = Object.keys(explain.shards)[0];
40+
const firstShard = explain.shards[firstShardName];
41+
if (firstShard.stages) {
42+
return _getUnshardedAggregationPlanner(firstShard);
43+
}
44+
return _getUnshardedFindPlanner(firstShard);
45+
};
46+
const _getFindPlanner = (explain: Stage): Stage => {
47+
return isShardedFindExplain(explain)
48+
? _getShardedFindPlanner(explain)
49+
: _getUnshardedFindPlanner(explain);
50+
};
51+
const _getUnshardedFindPlanner = (explain: Stage): Stage => {
52+
return explain.queryPlanner;
53+
};
54+
const _getShardedFindPlanner = (explain: Stage): Stage => {
55+
return explain.queryPlanner.winningPlan.shards[0];
56+
};

0 commit comments

Comments
 (0)