Skip to content

Commit 75be535

Browse files
committed
SERVER-24462 Record stats for the "top" command in findAndModify.
1 parent 556453a commit 75be535

File tree

3 files changed

+64
-16
lines changed

3 files changed

+64
-16
lines changed

jstests/core/operation_latency_histogram.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,7 @@
118118

119119
// FindAndModify
120120
testColl.findAndModify({query: {}, update: {pt: {type: "Point", coordinates: [0, 0]}}});
121-
// TODO SERVER-24462: findAndModify is not currently counted in Top.
122-
lastHistogram = checkHistogramDiff(0, 0, 0);
121+
lastHistogram = checkHistogramDiff(0, 1, 0);
123122

124123
// CreateIndex
125124
assert.commandWorked(testColl.createIndex({pt: "2dsphere"}));

jstests/core/top.js

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ var name = "toptest";
66

77
var testDB = db.getSiblingDB(name);
88
var testColl = testDB[name + "coll"];
9-
10-
// Ensure an empty collection exists for first top command
119
testColl.drop();
12-
testColl.insert({x: 0});
13-
testColl.remove({x: 0});
10+
11+
// Perform an operation on the collection so that it is present in the "top" command's output.
12+
assert.eq(testColl.find({}).itcount(), 0);
1413

1514
// get top statistics for the test collection
1615
function getTop() {
@@ -23,7 +22,7 @@ var lastTop = getTop();
2322
// return the number of operations since the last call to diffTop for the specified key
2423
function diffTop(key) {
2524
var thisTop = getTop();
26-
difference = {
25+
var difference = {
2726
time: thisTop[key].time - lastTop[key].time,
2827
count: thisTop[key].count - lastTop[key].count
2928
};
@@ -33,7 +32,7 @@ function diffTop(key) {
3332
assert.gte(difference.time, 0, "non-decreasing time");
3433

3534
// Time should advance iff operations were performed
36-
assert.eq(difference.count != 0, difference.time > 0, "non-zero time iff non-zero count");
35+
assert.eq(difference.count !== 0, difference.time > 0, "non-zero time iff non-zero count");
3736
return difference;
3837
}
3938

@@ -48,15 +47,15 @@ function checkStats(key, expected) {
4847
}
4948

5049
// Insert
51-
for (i = 0; i < numRecords; i++) {
52-
testColl.insert({_id: i});
50+
for (var i = 0; i < numRecords; i++) {
51+
assert.writeOK(testColl.insert({_id: i}));
5352
}
5453
checkStats("insert", numRecords);
5554
checkStats("writeLock", numRecords);
5655

5756
// Update
5857
for (i = 0; i < numRecords; i++) {
59-
testColl.update({_id: i}, {x: i});
58+
assert.writeOK(testColl.update({_id: i}, {x: i}));
6059
}
6160
checkStats("update", numRecords);
6261

@@ -79,25 +78,52 @@ checkStats("getmore", numRecords);
7978

8079
// Remove
8180
for (i = 0; i < numRecords; i++) {
82-
testColl.remove({_id: 1});
81+
assert.writeOK(testColl.remove({_id: 1}));
8382
}
8483
checkStats("remove", numRecords);
8584

8685
// Upsert, note that these are counted as updates, not inserts
8786
for (i = 0; i < numRecords; i++) {
88-
testColl.update({_id: i}, {x: i}, {upsert: 1});
87+
assert.writeOK(testColl.update({_id: i}, {x: i}, {upsert: 1}));
8988
}
9089
checkStats("update", numRecords);
9190

9291
// Commands
92+
var res;
93+
94+
// "count" command
9395
diffTop("commands"); // ignore any commands before this
9496
for (i = 0; i < numRecords; i++) {
95-
assert.eq(testDB.runCommand({count: "toptestcoll"}).n, numRecords);
97+
res = assert.commandWorked(testDB.runCommand({count: testColl.getName()}));
98+
assert.eq(res.n, numRecords, tojson(res));
99+
}
100+
checkStats("commands", numRecords);
101+
102+
// "findAndModify" command
103+
diffTop("commands");
104+
for (i = 0; i < numRecords; i++) {
105+
res = assert.commandWorked(testDB.runCommand({
106+
findAndModify: testColl.getName(),
107+
query: {_id: i},
108+
update: {$inc: {x: 1}},
109+
}));
110+
assert.eq(res.value.x, i, tojson(res));
111+
}
112+
checkStats("commands", numRecords);
113+
114+
diffTop("commands");
115+
for (i = 0; i < numRecords; i++) {
116+
res = assert.commandWorked(testDB.runCommand({
117+
findAndModify: testColl.getName(),
118+
query: {_id: i},
119+
remove: true,
120+
}));
121+
assert.eq(res.value.x, i + 1, tojson(res));
96122
}
97123
checkStats("commands", numRecords);
98124

99-
for (key in lastTop) {
100-
if (!(key in checked)) {
125+
for (var key of Object.keys(lastTop)) {
126+
if (!checked.hasOwnProperty(key)) {
101127
printjson({key: key, stats: diffTop(key)});
102128
}
103129
}

src/mongo/db/commands/find_and_modify.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#include "mongo/db/repl/repl_client_info.h"
6464
#include "mongo/db/repl/replication_coordinator_global.h"
6565
#include "mongo/db/s/collection_sharding_state.h"
66+
#include "mongo/db/stats/top.h"
6667
#include "mongo/db/write_concern.h"
6768
#include "mongo/util/log.h"
6869
#include "mongo/util/scopeguard.h"
@@ -200,6 +201,20 @@ Status checkCanAcceptWritesForDatabase(const NamespaceString& nsString) {
200201
return Status::OK();
201202
}
202203

204+
void recordStatsForTopCommand(OperationContext* txn) {
205+
auto curOp = CurOp::get(txn);
206+
const int writeLocked = 1;
207+
208+
Top::get(txn->getClient()->getServiceContext())
209+
.record(txn,
210+
curOp->getNS(),
211+
curOp->getLogicalOp(),
212+
writeLocked,
213+
curOp->elapsedMicros(),
214+
curOp->isCommand(),
215+
curOp->getReadWriteType());
216+
}
217+
203218
} // namespace
204219

205220
/* Find and Modify an object returning either the old (default) or new value*/
@@ -416,6 +431,9 @@ class CmdFindAndModify : public Command {
416431
if (!advanceStatus.isOK()) {
417432
return appendCommandStatus(result, advanceStatus.getStatus());
418433
}
434+
// Nothing after advancing the plan executor should throw a WriteConflictException,
435+
// so the following bookkeeping with execution stats won't end up being done
436+
// multiple times.
419437

420438
PlanSummaryStats summaryStats;
421439
Explain::getSummaryStats(*exec, &summaryStats);
@@ -432,6 +450,7 @@ class CmdFindAndModify : public Command {
432450
Explain::getWinningPlanStats(exec.get(), &execStatsBob);
433451
curOp->debug().execStats = execStatsBob.obj();
434452
}
453+
recordStatsForTopCommand(txn);
435454

436455
boost::optional<BSONObj> value = advanceStatus.getValue();
437456
appendCommandResponse(exec.get(), args.isRemove(), value, result);
@@ -513,6 +532,9 @@ class CmdFindAndModify : public Command {
513532
if (!advanceStatus.isOK()) {
514533
return appendCommandStatus(result, advanceStatus.getStatus());
515534
}
535+
// Nothing after advancing the plan executor should throw a WriteConflictException,
536+
// so the following bookkeeping with execution stats won't end up being done
537+
// multiple times.
516538

517539
PlanSummaryStats summaryStats;
518540
Explain::getSummaryStats(*exec, &summaryStats);
@@ -527,6 +549,7 @@ class CmdFindAndModify : public Command {
527549
Explain::getWinningPlanStats(exec.get(), &execStatsBob);
528550
curOp->debug().execStats = execStatsBob.obj();
529551
}
552+
recordStatsForTopCommand(txn);
530553

531554
boost::optional<BSONObj> value = advanceStatus.getValue();
532555
appendCommandResponse(exec.get(), args.isRemove(), value, result);

0 commit comments

Comments
 (0)