diff options
author | Max Hirschhorn <max.hirschhorn@mongodb.com> | 2018-07-10 01:39:36 -0400 |
---|---|---|
committer | Max Hirschhorn <max.hirschhorn@mongodb.com> | 2018-07-10 01:39:36 -0400 |
commit | a750bf210f70dd6e15cd65a15d50aeb8cd75fa3f (patch) | |
tree | 63b3d3213ce60f08625981f087392e0fd314e957 | |
parent | 4c725a11acf11e41e8200500a03d3cec97a25dbe (diff) | |
download | mongo-a750bf210f70dd6e15cd65a15d50aeb8cd75fa3f.tar.gz |
SERVER-35537 Create mongoebench for running benchRun against mobile.
It take a JSON config file with a "pre" section for any setup logic and
an "ops" section for the operations that the benchRun workers should
execute repeatedly.
-rw-r--r-- | buildscripts/bypass_compile_and_fetch_binaries.py | 1 | ||||
-rw-r--r-- | etc/evergreen.yml | 7 | ||||
-rw-r--r-- | jstests/libs/mongoebench.js | 49 | ||||
-rw-r--r-- | jstests/noPassthrough/mongoebench_test.js | 42 | ||||
-rw-r--r-- | src/mongo/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/embedded/SConscript | 12 | ||||
-rw-r--r-- | src/mongo/embedded/embedded_options_helpers.cpp | 62 | ||||
-rw-r--r-- | src/mongo/embedded/embedded_options_helpers.h | 60 | ||||
-rw-r--r-- | src/mongo/embedded/mongoed_main.cpp | 20 | ||||
-rw-r--r-- | src/mongo/shell/SConscript | 13 | ||||
-rw-r--r-- | src/mongo/shell/bench.cpp | 881 | ||||
-rw-r--r-- | src/mongo/shell/bench.h | 33 | ||||
-rw-r--r-- | src/mongo/shell/shell_utils.cpp | 11 | ||||
-rw-r--r-- | src/mongo/tools/SConscript | 26 | ||||
-rw-r--r-- | src/mongo/tools/mongoebench_main.cpp | 161 | ||||
-rw-r--r-- | src/mongo/tools/mongoebench_options.cpp | 137 | ||||
-rw-r--r-- | src/mongo/tools/mongoebench_options.h | 73 | ||||
-rw-r--r-- | src/mongo/tools/mongoebench_options_init.cpp | 54 |
18 files changed, 1124 insertions, 520 deletions
diff --git a/buildscripts/bypass_compile_and_fetch_binaries.py b/buildscripts/bypass_compile_and_fetch_binaries.py index 4bf307c8feb..831d864a582 100644 --- a/buildscripts/bypass_compile_and_fetch_binaries.py +++ b/buildscripts/bypass_compile_and_fetch_binaries.py @@ -269,6 +269,7 @@ def main(): # pylint: disable=too-many-locals,too-many-statements extract_files = [ executable_name("dbtest"), executable_name("mongobridge"), + executable_name("mongoebench"), executable_name("mongoed"), "build/integration_tests.txt", ] diff --git a/etc/evergreen.yml b/etc/evergreen.yml index c0c1977bcd1..f0a0dc05a19 100644 --- a/etc/evergreen.yml +++ b/etc/evergreen.yml @@ -3257,6 +3257,7 @@ tasks: - "./test*" - "./dbtest*" - "./mongobridge*" + - "./mongoebench*" - "./mongoed*" - "buildscripts/**" - "*Example" @@ -12201,7 +12202,7 @@ buildvariants: scons_cache_scope: shared gorootvars: 'PATH="/opt/go1.8/go/bin:/opt/mongodbtoolchain/v2/bin/:$PATH" GOROOT=/opt/go1.8/go' build_mongoreplay: true - additional_targets: mongoed + additional_targets: mongoebench mongoed display_tasks: - *unittests tasks: @@ -12260,7 +12261,7 @@ buildvariants: gorootvars: 'PATH="/opt/go1.8/go/bin:/opt/mongodbtoolchain/v2/bin/:$PATH" GOROOT=/opt/go1.8/go' tooltags: -gccgoflags "$(pkg-config --libs)" build_mongoreplay: true - additional_targets: mongoed + additional_targets: mongoebench mongoed display_tasks: - *unittests tasks: @@ -12344,7 +12345,7 @@ buildvariants: num_jobs_available: 1 gorootvars: 'PATH="/usr/local/go1.8/go/bin:/opt/mongodbtoolchain/v2/bin/:$PATH" GOROOT=/usr/local/go1.8/go CGO_CPPFLAGS=-I/opt/mongodbtoolchain/v2/include CGO_CFLAGS=-mmacosx-version-min=10.10 CGO_LDFLAGS=-mmacosx-version-min=10.10' build_mongoreplay: true - additional_targets: mongoed + additional_targets: mongoebench mongoed display_tasks: - *unittests tasks: diff --git a/jstests/libs/mongoebench.js b/jstests/libs/mongoebench.js new file mode 100644 index 00000000000..f6feb4eb9f0 --- /dev/null +++ b/jstests/libs/mongoebench.js @@ -0,0 +1,49 @@ +"use strict"; + +var {runMongoeBench} = (function() { + + /** + * Spawns a mongoebench process with the specified options. + * + * If a plain JavaScript object is specified as the 'config' parameter, then it is serialized to + * a file as a JSON string which is then specified as the config file for the mongoebench + * process. + */ + function runMongoeBench(config, options = {}) { + const args = ["mongoebench"]; + + if (typeof config === "object") { + const filename = MongoRunner.dataPath + "mongoebench_config.json"; + writeFile(filename, tojson(config)); + args.push(filename); + } else if (typeof config === "string") { + args.push(config); + } else { + throw new Error("'config' parameter must be a string or an object"); + } + + if (!options.hasOwnProperty("dbpath")) { + options.dbpath = MongoRunner.dataDir; + } + + for (let key of Object.keys(options)) { + const value = options[key]; + if (value === null || value === undefined) { + throw new Error( + "Value '" + value + "' for '" + key + + "' option is ambiguous; specify {flag: ''} to add --flag command line" + + " options'"); + } + + args.push("--" + key); + if (value !== "") { + args.push(value.toString()); + } + } + + const exitCode = _runMongoProgram(...args); + assert.eq(0, exitCode, "encountered an error in mongoebench"); + } + + return {runMongoeBench}; +})(); diff --git a/jstests/noPassthrough/mongoebench_test.js b/jstests/noPassthrough/mongoebench_test.js new file mode 100644 index 00000000000..4e9c1fdc14e --- /dev/null +++ b/jstests/noPassthrough/mongoebench_test.js @@ -0,0 +1,42 @@ +/** + * Tests for the mongoebench executable. + */ +(function() { + "use strict"; + + load("jstests/libs/mongoebench.js"); // for runMongoeBench + + if (jsTest.options().storageEngine !== "mobile") { + print("Skipping test because storage engine isn't mobile"); + return; + } + + const dbpath = MongoRunner.dataPath + "mongoebench_test"; + resetDbpath(dbpath); + + // Test that the operations in the "pre" section of the configuration are run exactly once. + runMongoeBench( // Force clang-format to break this line. + { + pre: [{ + op: "insert", + ns: "test.mongoebench_test", + doc: {pre: {"#SEQ_INT": {seq_id: 0, start: 0, step: 1, unique: true}}} + }], + ops: [{ + op: "update", + ns: "test.mongoebench_test", + update: {$inc: {ops: 1}}, + multi: true, + }] + }, + {dbpath}); + + const conn = MongoRunner.runMongod({dbpath, noCleanData: true}); + assert.neq(null, conn, "failed to start mongod after running mongoebench"); + + const db = conn.getDB("test"); + const count = db.mongoebench_test.find().itcount(); + assert.eq(1, count, "ops in 'pre' section ran more than once or didn't run at all"); + + MongoRunner.stopMongod(conn); +})(); diff --git a/src/mongo/SConscript b/src/mongo/SConscript index f00f59720b7..66e0eb45995 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -463,7 +463,6 @@ if not has_option('noshell') and usemozjs: shell_core_env.Append(CPPDEFINES=["MONGO_SAFE_SHELL"]) shell_core_env.Library("shell_core", source=[ - "shell/bench.cpp", "shell/linenoise.cpp", "shell/mk_wcwidth.cpp", "shell/mongo-server.cpp", @@ -482,6 +481,7 @@ if not has_option('noshell') and usemozjs: 'linenoise_utf8', 'rpc/protocol', 'scripting/scripting', + 'shell/benchrun', 'shell/mongojs', 'transport/message_compressor', 'transport/transport_layer_manager', diff --git a/src/mongo/embedded/SConscript b/src/mongo/embedded/SConscript index 73dc960d1c3..e90f08d6023 100644 --- a/src/mongo/embedded/SConscript +++ b/src/mongo/embedded/SConscript @@ -59,6 +59,17 @@ env.Library( ) env.Library( + target='embedded_integration_helpers', + source=[ + 'embedded_options_helpers.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/util/options_parser/options_parser', + ], +) + +env.Library( target='mongo_embedded_capi', source=[ 'capi.cpp', @@ -119,6 +130,7 @@ mongoed = yamlEnv.Program( '$BUILD_DIR/mongo/transport/transport_layer_manager', '$BUILD_DIR/mongo/util/signal_handlers', 'embedded', + 'embedded_integration_helpers', ], INSTALL_ALIAS=[ 'mobile-test', diff --git a/src/mongo/embedded/embedded_options_helpers.cpp b/src/mongo/embedded/embedded_options_helpers.cpp new file mode 100644 index 00000000000..c58f920bee1 --- /dev/null +++ b/src/mongo/embedded/embedded_options_helpers.cpp @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/embedded/embedded_options_helpers.h" + +#include "mongo/util/options_parser/options_parser.h" +#include "mongo/util/options_parser/startup_options.h" + +#include <iterator> +#include <map> +#include <vector> + +namespace mongo { +namespace embedded_integration_helpers { + +Status parseCommandLineOptions(int argc, + char* argv[], + const optionenvironment::OptionSection& startupOptions) { + // We manually run the options parsing code that's equivalent to the logic in the + // MONGO_INITIALIZERs for mongod. We cannot do this in initializers because embedded uses a + // different options format and we therefore need to have parsed the command line options before + // embedded::initialize() is called. However, as long as we store the options in the same place + // they will be valid for embedded too. + std::vector<std::string> args; + std::map<std::string, std::string> env; + + args.reserve(argc); + std::copy(argv, argv + argc, std::back_inserter(args)); + + optionenvironment::OptionsParser parser; + return parser.run(startupOptions, args, env, &optionenvironment::startupOptionsParsed); +} + +} // namespace embedded_integration_helpers +} // namepsace mongo diff --git a/src/mongo/embedded/embedded_options_helpers.h b/src/mongo/embedded/embedded_options_helpers.h new file mode 100644 index 00000000000..fa46bd59050 --- /dev/null +++ b/src/mongo/embedded/embedded_options_helpers.h @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/base/status.h" + +namespace mongo { + +namespace optionenvironment { + +class OptionSection; + +} // namespace optionenvironment + +namespace embedded_integration_helpers { + +/** + * Parses the command line options represented by 'argc' and 'argv' into + * optionenvironment::startupOptionsParsed. + * + * In order to faciliate adding options which are supported by both embedded and mongod + * (e.g. dbpath), the caller is responsible for adding the options accepted by embedded prior to + * calling this function. + * + * The caller should also take care to ensure 'startupOptions' lives as long as the call to + * embedded::initialize() because optionenvironment::startupOptionsParsed holds references to + * Constraint instances owned by 'startupOptions'. + */ +Status parseCommandLineOptions(int argc, + char* argv[], + const optionenvironment::OptionSection& startupOptions); + +} // namespace embedded_integration_helpers +} // namespace mongo diff --git a/src/mongo/embedded/mongoed_main.cpp b/src/mongo/embedded/mongoed_main.cpp index c43e95e09ef..09ea579fa5f 100644 --- a/src/mongo/embedded/mongoed_main.cpp +++ b/src/mongo/embedded/mongoed_main.cpp @@ -34,6 +34,8 @@ #include "mongo/db/mongod_options.h" #include "mongo/db/service_context.h" #include "mongo/embedded/embedded.h" +#include "mongo/embedded/embedded_options.h" +#include "mongo/embedded/embedded_options_helpers.h" #include "mongo/embedded/service_entry_point_embedded.h" #include "mongo/transport/service_entry_point_impl.h" #include "mongo/transport/transport_layer.h" @@ -102,22 +104,12 @@ int mongoedMain(int argc, char* argv[], char** envp) { try { optionenvironment::OptionSection startupOptions("Options"); + // Adding all options mongod we don't have to maintain a separate set for this executable, + // some will be unused but that's fine as this is just an executable for testing purposes + // anyway. uassertStatusOK(addMongodOptions(&startupOptions)); - - // Manually run the code that's equivalent to the MONGO_INITIALIZERs for mongod. We can't do - // this in initializers because embedded uses a different options format. However as long as - // we store the options in the same place it will be valid for embedded too. Adding all - // options mongod we don't have to maintain a separate set for this executable, some will be - // unused but that's fine as this is just an executable for testing purposes anyway. - std::vector<std::string> args; - std::map<std::string, std::string> env; - - args.reserve(argc); - std::copy(argv, argv + argc, std::back_inserter(args)); - - optionenvironment::OptionsParser parser; uassertStatusOK( - parser.run(startupOptions, args, env, &optionenvironment::startupOptionsParsed)); + embedded_integration_helpers::parseCommandLineOptions(argc, argv, startupOptions)); uassertStatusOK(storeMongodOptions(optionenvironment::startupOptionsParsed)); // Add embedded specific options that's not available in mongod here. diff --git a/src/mongo/shell/SConscript b/src/mongo/shell/SConscript index 3db44b18a52..7bdc2b580a2 100644 --- a/src/mongo/shell/SConscript +++ b/src/mongo/shell/SConscript @@ -4,6 +4,19 @@ Import("env") env = env.Clone() +env.Library( + target='benchrun', + source=[ + 'bench.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/client/clientdriver_minimal', + '$BUILD_DIR/mongo/db/logical_session_id', + '$BUILD_DIR/mongo/scripting/bson_template_evaluator', + ] +) + generateJSErrorCodes = env.Command( target=['error_codes.js'], source=[ diff --git a/src/mongo/shell/bench.cpp b/src/mongo/shell/bench.cpp index 8eef4653730..728c0abd94d 100644 --- a/src/mongo/shell/bench.cpp +++ b/src/mongo/shell/bench.cpp @@ -40,7 +40,6 @@ #include "mongo/db/query/getmore_request.h" #include "mongo/db/query/query_request.h" #include "mongo/scripting/bson_template_evaluator.h" -#include "mongo/scripting/engine.h" #include "mongo/stdx/thread.h" #include "mongo/util/log.h" #include "mongo/util/md5.h" @@ -397,18 +396,6 @@ BenchRunOp opFromBson(const BSONObj& op) { << opType, (opType == "find") || (opType == "query")); myOp.batchSize = arg.numberInt(); - } else if (name == "check") { - // check function gets thrown into a scoped function. Leaving that parsing in main loop. - myOp.useCheck = true; - myOp.check = arg; - uassert( - 34420, - str::stream() - << "Check field requires type CodeWScoe, Code, or String, instead its type is: " - << typeName(myOp.check.type()), - (myOp.check.type() == CodeWScope || myOp.check.type() == Code || - myOp.check.type() == String)); - } else if (name == "command") { // type needs to be command uassert(34398, @@ -754,14 +741,10 @@ void BenchRunConfig::initializeFromBson(const BSONObj& args) { } } -std::unique_ptr<DBClientBase> BenchRunConfig::createConnection() const { - const ConnectionString connectionString = uassertStatusOK(ConnectionString::parse(host)); - - std::string errorMessage; - std::unique_ptr<DBClientBase> connection(connectionString.connect("BenchRun", errorMessage)); - uassert(16158, errorMessage, connection); +MONGO_DEFINE_SHIM(BenchRunConfig::createConnectionImpl); - return connection; +std::unique_ptr<DBClientBase> BenchRunConfig::createConnection() const { + return BenchRunConfig::createConnectionImpl(*this); } BenchRunState::BenchRunState(unsigned numWorkers) @@ -885,498 +868,25 @@ void BenchRunWorker::generateLoadOnConnection(DBClientBase* conn) { LogicalSessionIdToClient::parse(IDLParserErrorContext("lsid"), result["id"].Obj())); } - TxnNumber txnNumber = 0; - bool inProgressMultiStatementTxn = false; + BenchRunOp::State opState(&_rng, &bsonTemplateEvaluator, &_statsBlackHole); ON_BLOCK_EXIT([&] { // Executing the transaction with a new txnNumber would end the previous transaction // automatically, but we have to end the last transaction manually with an abort command. - if (inProgressMultiStatementTxn) { - abortTransaction(conn, lsid, txnNumber); + if (opState.inProgressMultiStatementTxn) { + abortTransaction(conn, lsid, opState.txnNumber); } }); - std::unique_ptr<Scope> scope{getGlobalScriptEngine()->newScopeForCurrentThread()}; - verify(scope.get()); - while (!shouldStop()) { for (const auto& op : _config->ops) { if (shouldStop()) break; - auto& stats = shouldCollectStats() ? _stats : _statsBlackHole; - ScriptingFunction scopeFunc = 0; - BSONObj scopeObj; - if (op.useCheck) { - auto check = op.check; - - if (check.type() == CodeWScope) { - scopeFunc = scope->createFunction(check.codeWScopeCode()); - scopeObj = BSONObj(check.codeWScopeScopeDataUnsafe()); - } else { - scopeFunc = scope->createFunction(check.valuestr()); - } - - scope->init(&scopeObj); - invariant(scopeFunc); - } + opState.stats = shouldCollectStats() ? &_stats : &_statsBlackHole; try { - switch (op.op) { - case OpType::NOP: - break; - case OpType::CPULOAD: { - // perform a tight multiplication loop. The - // performance of this loop should be - // predictable, and this operation can be used - // to test underlying system variability. - long long limit = 10000 * op.cpuFactor; - // volatile used to ensure that loop is not optimized away - volatile uint64_t result = 0; // NOLINT - uint64_t x = 100; - for (long long i = 0; i < limit; i++) { - x *= 13; - } - result = x; - } break; - case OpType::FINDONE: { - BSONObj fixedQuery = fixQuery(op.query, bsonTemplateEvaluator); - BSONObj result; - if (op.useReadCmd) { - auto qr = stdx::make_unique<QueryRequest>(NamespaceString(op.ns)); - qr->setFilter(fixedQuery); - qr->setProj(op.projection); - qr->setLimit(1LL); - qr->setWantMore(false); - if (_config->useSnapshotReads) { - qr->setReadConcern(readConcernSnapshot); - } - invariant(qr->validate()); - - BenchRunEventTrace _bret(&stats.findOneCounter); - boost::optional<TxnNumber> txnNumberForOp; - if (_config->useSnapshotReads) { - ++txnNumber; - txnNumberForOp = txnNumber; - inProgressMultiStatementTxn = true; - } - runQueryWithReadCommands(conn, - lsid, - txnNumberForOp, - std::move(qr), - Milliseconds(0), - &result); - } else { - BenchRunEventTrace _bret(&stats.findOneCounter); - result = conn->findOne(op.ns, - fixedQuery, - nullptr, - DBClientCursor::QueryOptionLocal_forceOpQuery); - } - - if (op.useCheck) { - int err = scope->invoke(scopeFunc, 0, &result, 1000 * 60, false); - if (err) { - log() << "Error checking in benchRun thread [findOne]" - << causedBy(scope->getError()); - - stats.errCount++; - - return; - } - } - - if (!_config->hideResults || op.showResult) - log() << "Result from benchRun thread [findOne] : " << result; - } break; - case OpType::COMMAND: { - bool ok; - BSONObj result; - { - BenchRunEventTrace _bret(&stats.commandCounter); - ok = runCommandWithSession(conn, - op.ns, - fixQuery(op.command, bsonTemplateEvaluator), - op.options, - lsid, - &result); - } - if (!ok) { - stats.errCount++; - } - - if (!result["cursor"].eoo()) { - // The command returned a cursor, so iterate all results. - auto cursorResponse = - uassertStatusOK(CursorResponse::parseFromBSON(result)); - int count = cursorResponse.getBatch().size(); - while (cursorResponse.getCursorId() != 0) { - GetMoreRequest getMoreRequest( - cursorResponse.getNSS(), - cursorResponse.getCursorId(), - boost::none, // batchSize - boost::none, // maxTimeMS - boost::none, // term - boost::none); // lastKnownCommittedOpTime - BSONObj getMoreCommandResult; - uassert(ErrorCodes::CommandFailed, - str::stream() << "getMore command failed; reply was: " - << getMoreCommandResult, - runCommandWithSession(conn, - op.ns, - getMoreRequest.toBSON(), - kNoOptions, - lsid, - &getMoreCommandResult)); - cursorResponse = uassertStatusOK( - CursorResponse::parseFromBSON(getMoreCommandResult)); - count += cursorResponse.getBatch().size(); - } - // Just give the count to the check function. - result = BSON("count" << count << "context" << op.context); - } - - if (op.useCheck) { - int err = scope->invoke(scopeFunc, 0, &result, 1000 * 60, false); - if (err) { - log() << "Error checking in benchRun thread [command]" - << causedBy(scope->getError()); - int err = scope->invoke(scopeFunc, 0, &result, 1000 * 60, false); - if (err) { - log() << "Error checking in benchRun thread [command]" - << causedBy(scope->getError()); - - stats.errCount++; - - return; - } - } - - if (!_config->hideResults || op.showResult) - log() << "Result from benchRun thread [command] : " << result; - } - } break; - case OpType::FIND: { - int count; - - BSONObj fixedQuery = fixQuery(op.query, bsonTemplateEvaluator); - - if (op.useReadCmd) { - uassert(28824, - "cannot use 'options' in combination with read commands", - !op.options); - - auto qr = stdx::make_unique<QueryRequest>(NamespaceString(op.ns)); - qr->setFilter(fixedQuery); - qr->setProj(op.projection); - if (op.skip) { - qr->setSkip(op.skip); - } - if (op.limit) { - qr->setLimit(op.limit); - } - if (op.batchSize) { - qr->setBatchSize(op.batchSize); - } - BSONObjBuilder readConcernBuilder; - if (_config->useSnapshotReads) { - readConcernBuilder.append("level", "snapshot"); - } - if (op.useAClusterTimeWithinPastSeconds > 0) { - invariant(_config->useSnapshotReads); - // Get a random cluster time between the latest time and - // 'useAClusterTimeWithinPastSeconds' in the past. - Timestamp atClusterTime = getAClusterTimeSecondsInThePast( - conn, _rng.nextInt32(op.useAClusterTimeWithinPastSeconds)); - readConcernBuilder.append("atClusterTime", atClusterTime); - } - qr->setReadConcern(readConcernBuilder.obj()); - - invariant(qr->validate()); - - BenchRunEventTrace _bret(&stats.queryCounter); - boost::optional<TxnNumber> txnNumberForOp; - if (_config->useSnapshotReads) { - ++txnNumber; - txnNumberForOp = txnNumber; - inProgressMultiStatementTxn = true; - } - - int delayBeforeGetMore = op.maxRandomMillisecondDelayBeforeGetMore - ? _rng.nextInt32(op.maxRandomMillisecondDelayBeforeGetMore) - : 0; - - count = runQueryWithReadCommands(conn, - lsid, - txnNumberForOp, - std::move(qr), - Milliseconds(delayBeforeGetMore), - nullptr); - } else { - // Use special query function for exhaust query option. - if (op.options & QueryOption_Exhaust) { - BenchRunEventTrace _bret(&stats.queryCounter); - stdx::function<void(const BSONObj&)> castedDoNothing(doNothing); - count = conn->query( - castedDoNothing, - op.ns, - fixedQuery, - &op.projection, - op.options | DBClientCursor::QueryOptionLocal_forceOpQuery); - } else { - BenchRunEventTrace _bret(&stats.queryCounter); - std::unique_ptr<DBClientCursor> cursor(conn->query( - op.ns, - fixedQuery, - op.limit, - op.skip, - &op.projection, - op.options | DBClientCursor::QueryOptionLocal_forceOpQuery, - op.batchSize)); - count = cursor->itcount(); - } - } - - if (op.expected >= 0 && count != op.expected) { - log() << "bench query on: " << op.ns << " expected: " << op.expected - << " got: " << count; - verify(false); - } - - if (op.useCheck) { - BSONObj thisValue = BSON("count" << count << "context" << op.context); - int err = scope->invoke(scopeFunc, 0, &thisValue, 1000 * 60, false); - if (err) { - log() << "Error checking in benchRun thread [find]" - << causedBy(scope->getError()); - - stats.errCount++; - - return; - } - } - - if (!_config->hideResults || op.showResult) - log() << "Result from benchRun thread [query] : " << count; - } break; - case OpType::UPDATE: { - BSONObj result; - { - BenchRunEventTrace _bret(&stats.updateCounter); - BSONObj query = fixQuery(op.query, bsonTemplateEvaluator); - BSONObj update = fixQuery(op.update, bsonTemplateEvaluator); - - if (op.useWriteCmd) { - BSONObjBuilder builder; - builder.append("update", nsToCollectionSubstring(op.ns)); - BSONArrayBuilder docBuilder(builder.subarrayStart("updates")); - docBuilder.append(BSON( - "q" << query << "u" << update << "multi" << op.multi << "upsert" - << op.upsert)); - docBuilder.done(); - builder.append("writeConcern", op.writeConcern); - - boost::optional<TxnNumber> txnNumberForOp; - if (_config->useIdempotentWrites) { - ++txnNumber; - txnNumberForOp = txnNumber; - } - runCommandWithSession(conn, - nsToDatabaseSubstring(op.ns).toString(), - builder.done(), - kNoOptions, - lsid, - txnNumberForOp, - &result); - } else { - auto toSend = - makeUpdateMessage(op.ns, - query, - update, - (op.upsert ? UpdateOption_Upsert : 0) | - (op.multi ? UpdateOption_Multi : 0)); - conn->say(toSend); - if (op.safe) - result = conn->getLastErrorDetailed(); - } - } - - if (op.safe) { - if (op.useCheck) { - int err = scope->invoke(scopeFunc, 0, &result, 1000 * 60, false); - if (err) { - log() << "Error checking in benchRun thread [update]" - << causedBy(scope->getError()); - - stats.errCount++; - - return; - } - } - - if (!_config->hideResults || op.showResult) - log() << "Result from benchRun thread [safe update] : " << result; - - if (!result["err"].eoo() && result["err"].type() == String && - (_config->throwGLE || op.throwGLE)) - uasserted(result["code"].eoo() ? 0 : result["code"].Int(), - (std::string) "From benchRun GLE" + - causedBy(result["err"].String())); - } - } break; - case OpType::INSERT: { - BSONObj result; - { - BenchRunEventTrace _bret(&stats.insertCounter); - - BSONObj insertDoc; - if (op.useWriteCmd) { - BSONObjBuilder builder; - builder.append("insert", nsToCollectionSubstring(op.ns)); - BSONArrayBuilder docBuilder(builder.subarrayStart("documents")); - if (op.isDocAnArray) { - for (const auto& element : op.doc) { - insertDoc = fixQuery(element.Obj(), bsonTemplateEvaluator); - docBuilder.append(insertDoc); - } - } else { - insertDoc = fixQuery(op.doc, bsonTemplateEvaluator); - docBuilder.append(insertDoc); - } - docBuilder.done(); - builder.append("writeConcern", op.writeConcern); - - boost::optional<TxnNumber> txnNumberForOp; - if (_config->useIdempotentWrites) { - ++txnNumber; - txnNumberForOp = txnNumber; - } - runCommandWithSession(conn, - nsToDatabaseSubstring(op.ns).toString(), - builder.done(), - kNoOptions, - lsid, - txnNumberForOp, - &result); - } else { - std::vector<BSONObj> insertArray; - if (op.isDocAnArray) { - for (const auto& element : op.doc) { - BSONObj e = fixQuery(element.Obj(), bsonTemplateEvaluator); - insertArray.push_back(e); - } - } else { - insertArray.push_back(fixQuery(op.doc, bsonTemplateEvaluator)); - } - - auto toSend = makeInsertMessage( - op.ns, insertArray.data(), insertArray.size()); - conn->say(toSend); - - if (op.safe) - result = conn->getLastErrorDetailed(); - } - } - - if (op.safe) { - if (op.useCheck) { - int err = scope->invoke(scopeFunc, 0, &result, 1000 * 60, false); - if (err) { - log() << "Error checking in benchRun thread [insert]" - << causedBy(scope->getError()); - - stats.errCount++; - - return; - } - } - - if (!_config->hideResults || op.showResult) - log() << "Result from benchRun thread [safe insert] : " << result; - - if (!result["err"].eoo() && result["err"].type() == String && - (_config->throwGLE || op.throwGLE)) - uasserted(result["code"].eoo() ? 0 : result["code"].Int(), - (std::string) "From benchRun GLE" + - causedBy(result["err"].String())); - } - } break; - case OpType::REMOVE: { - BSONObj result; - { - BenchRunEventTrace _bret(&stats.deleteCounter); - BSONObj predicate = fixQuery(op.query, bsonTemplateEvaluator); - if (op.useWriteCmd) { - BSONObjBuilder builder; - builder.append("delete", nsToCollectionSubstring(op.ns)); - BSONArrayBuilder docBuilder(builder.subarrayStart("deletes")); - int limit = (op.multi == true) ? 0 : 1; - docBuilder.append(BSON("q" << predicate << "limit" << limit)); - docBuilder.done(); - builder.append("writeConcern", op.writeConcern); - - boost::optional<TxnNumber> txnNumberForOp; - if (_config->useIdempotentWrites) { - ++txnNumber; - txnNumberForOp = txnNumber; - } - runCommandWithSession(conn, - nsToDatabaseSubstring(op.ns).toString(), - builder.done(), - kNoOptions, - lsid, - txnNumberForOp, - &result); - } else { - auto toSend = makeRemoveMessage( - op.ns, predicate, op.multi ? 0 : RemoveOption_JustOne); - conn->say(toSend); - if (op.safe) - result = conn->getLastErrorDetailed(); - } - } - - if (op.safe) { - if (op.useCheck) { - int err = scope->invoke(scopeFunc, 0, &result, 1000 * 60, false); - if (err) { - log() << "Error checking in benchRun thread [delete]" - << causedBy(scope->getError()); - - stats.errCount++; - - return; - } - } - - if (!_config->hideResults || op.showResult) - log() << "Result from benchRun thread [safe remove] : " << result; - - if (!result["err"].eoo() && result["err"].type() == String && - (_config->throwGLE || op.throwGLE)) - uasserted(result["code"].eoo() ? 0 : result["code"].Int(), - (std::string) "From benchRun GLE " + - causedBy(result["err"].String())); - } - } break; - case OpType::CREATEINDEX: - conn->createIndex(op.ns, op.key); - break; - case OpType::DROPINDEX: - conn->dropIndex(op.ns, op.key); - break; - case OpType::LET: { - BSONObjBuilder templateBuilder; - bsonTemplateEvaluator.evaluate(op.value, templateBuilder); - bsonTemplateEvaluator.setVariable(op.target, - templateBuilder.done().firstElement()); - } break; - default: - uassert(34397, "In benchRun loop and got unknown op type", false); - } - - // Count 1 for total ops. Successfully got through the try phrase - stats.opCount++; + op.executeOnce(conn, lsid, *_config, &opState); } catch (const DBException& ex) { if (!_config->hideErrors || op.showError) { bool yesWatch = @@ -1401,7 +911,7 @@ void BenchRunWorker::generateLoadOnConnection(DBClientBase* conn) { (!_config->noTrapPattern && _config->trapPattern && yesTrap) || (_config->trapPattern && _config->noTrapPattern && yesTrap && !noTrap)) { { - stats.trappedErrors.push_back( + opState.stats->trappedErrors.push_back( BSON("error" << ex.what() << "op" << kOpTypeNames.find(op.op)->second << "count" << count)); @@ -1414,7 +924,7 @@ void BenchRunWorker::generateLoadOnConnection(DBClientBase* conn) { sleepFor(_config->delayMillisOnFailedOperation); - stats.errCount++; + ++opState.stats->errCount; } catch (...) { if (!_config->hideErrors || op.showError) log() << "Error in benchRun thread caused by unknown error for op " @@ -1422,7 +932,7 @@ void BenchRunWorker::generateLoadOnConnection(DBClientBase* conn) { if (!_config->handleErrors && !op.handleError) return; - stats.errCount++; + ++opState.stats->errCount; } if (++count % 100 == 0 && !op.useWriteCmd) { @@ -1437,6 +947,375 @@ void BenchRunWorker::generateLoadOnConnection(DBClientBase* conn) { conn->getLastError(); } +void BenchRunOp::executeOnce(DBClientBase* conn, + const boost::optional<LogicalSessionIdToClient>& lsid, + const BenchRunConfig& config, + State* state) const { + switch (this->op) { + case OpType::NOP: + break; + case OpType::CPULOAD: { + // perform a tight multiplication loop. The + // performance of this loop should be + // predictable, and this operation can be used + // to test underlying system variability. + long long limit = 10000 * this->cpuFactor; + // volatile used to ensure that loop is not optimized away + volatile uint64_t result = 0; // NOLINT + uint64_t x = 100; + for (long long i = 0; i < limit; i++) { + x *= 13; + } + result = x; + } break; + case OpType::FINDONE: { + BSONObj fixedQuery = fixQuery(this->query, *state->bsonTemplateEvaluator); + BSONObj result; + if (this->useReadCmd) { + auto qr = stdx::make_unique<QueryRequest>(NamespaceString(this->ns)); + qr->setFilter(fixedQuery); + qr->setProj(this->projection); + qr->setLimit(1LL); + qr->setWantMore(false); + if (config.useSnapshotReads) { + qr->setReadConcern(readConcernSnapshot); + } + invariant(qr->validate()); + + BenchRunEventTrace _bret(&state->stats->findOneCounter); + boost::optional<TxnNumber> txnNumberForOp; + if (config.useSnapshotReads) { + ++state->txnNumber; + txnNumberForOp = state->txnNumber; + state->inProgressMultiStatementTxn = true; + } + runQueryWithReadCommands( + conn, lsid, txnNumberForOp, std::move(qr), Milliseconds(0), &result); + } else { + BenchRunEventTrace _bret(&state->stats->findOneCounter); + result = conn->findOne( + this->ns, fixedQuery, nullptr, DBClientCursor::QueryOptionLocal_forceOpQuery); + } + + if (!config.hideResults || this->showResult) + log() << "Result from benchRun thread [findOne] : " << result; + } break; + case OpType::COMMAND: { + bool ok; + BSONObj result; + { + BenchRunEventTrace _bret(&state->stats->commandCounter); + ok = runCommandWithSession(conn, + this->ns, + fixQuery(this->command, *state->bsonTemplateEvaluator), + this->options, + lsid, + &result); + } + if (!ok) { + ++state->stats->errCount; + } + + if (!result["cursor"].eoo()) { + // The command returned a cursor, so iterate all results. + auto cursorResponse = uassertStatusOK(CursorResponse::parseFromBSON(result)); + int count = cursorResponse.getBatch().size(); + while (cursorResponse.getCursorId() != 0) { + GetMoreRequest getMoreRequest(cursorResponse.getNSS(), + cursorResponse.getCursorId(), + boost::none, // batchSize + boost::none, // maxTimeMS + boost::none, // term + boost::none); // lastKnownCommittedOpTime + BSONObj getMoreCommandResult; + uassert(ErrorCodes::CommandFailed, + str::stream() << "getMore command failed; reply was: " + << getMoreCommandResult, + runCommandWithSession(conn, + this->ns, + getMoreRequest.toBSON(), + kNoOptions, + lsid, + &getMoreCommandResult)); + cursorResponse = + uassertStatusOK(CursorResponse::parseFromBSON(getMoreCommandResult)); + count += cursorResponse.getBatch().size(); + } + // Just give the count to the check function. + result = BSON("count" << count << "context" << this->context); + } + } break; + case OpType::FIND: { + int count; + + BSONObj fixedQuery = fixQuery(this->query, *state->bsonTemplateEvaluator); + + if (this->useReadCmd) { + uassert(28824, + "cannot use 'options' in combination with read commands", + !this->options); + + auto qr = stdx::make_unique<QueryRequest>(NamespaceString(this->ns)); + qr->setFilter(fixedQuery); + qr->setProj(this->projection); + if (this->skip) { + qr->setSkip(this->skip); + } + if (this->limit) { + qr->setLimit(this->limit); + } + if (this->batchSize) { + qr->setBatchSize(this->batchSize); + } + BSONObjBuilder readConcernBuilder; + if (config.useSnapshotReads) { + readConcernBuilder.append("level", "snapshot"); + } + if (this->useAClusterTimeWithinPastSeconds > 0) { + invariant(config.useSnapshotReads); + // Get a random cluster time between the latest time and + // 'useAClusterTimeWithinPastSeconds' in the past. + Timestamp atClusterTime = getAClusterTimeSecondsInThePast( + conn, state->rng->nextInt32(this->useAClusterTimeWithinPastSeconds)); + readConcernBuilder.append("atClusterTime", atClusterTime); + } + qr->setReadConcern(readConcernBuilder.obj()); + + invariant(qr->validate()); + + BenchRunEventTrace _bret(&state->stats->queryCounter); + boost::optional<TxnNumber> txnNumberForOp; + if (config.useSnapshotReads) { + ++state->txnNumber; + txnNumberForOp = state->txnNumber; + state->inProgressMultiStatementTxn = true; + } + + int delayBeforeGetMore = this->maxRandomMillisecondDelayBeforeGetMore + ? state->rng->nextInt32(this->maxRandomMillisecondDelayBeforeGetMore) + : 0; + + count = runQueryWithReadCommands(conn, + lsid, + txnNumberForOp, + std::move(qr), + Milliseconds(delayBeforeGetMore), + nullptr); + } else { + // Use special query function for exhaust query option. + if (this->options & QueryOption_Exhaust) { + BenchRunEventTrace _bret(&state->stats->queryCounter); + stdx::function<void(const BSONObj&)> castedDoNothing(doNothing); + count = + conn->query(castedDoNothing, + this->ns, + fixedQuery, + &this->projection, + this->options | DBClientCursor::QueryOptionLocal_forceOpQuery); + } else { + BenchRunEventTrace _bret(&state->stats->queryCounter); + std::unique_ptr<DBClientCursor> cursor( + conn->query(this->ns, + fixedQuery, + this->limit, + this->skip, + &this->projection, + this->options | DBClientCursor::QueryOptionLocal_forceOpQuery, + this->batchSize)); + count = cursor->itcount(); + } + } + + if (this->expected >= 0 && count != this->expected) { + log() << "bench query on: " << this->ns << " expected: " << this->expected + << " got: " << count; + verify(false); + } + + if (!config.hideResults || this->showResult) + log() << "Result from benchRun thread [query] : " << count; + } break; + case OpType::UPDATE: { + BSONObj result; + { + BenchRunEventTrace _bret(&state->stats->updateCounter); + BSONObj query = fixQuery(this->query, *state->bsonTemplateEvaluator); + BSONObj update = fixQuery(this->update, *state->bsonTemplateEvaluator); + + if (this->useWriteCmd) { + BSONObjBuilder builder; + builder.append("update", nsToCollectionSubstring(this->ns)); + BSONArrayBuilder docBuilder(builder.subarrayStart("updates")); + docBuilder.append(BSON("q" << query << "u" << update << "multi" << this->multi + << "upsert" + << this->upsert)); + docBuilder.done(); + builder.append("writeConcern", this->writeConcern); + + boost::optional<TxnNumber> txnNumberForOp; + if (config.useIdempotentWrites) { + ++state->txnNumber; + txnNumberForOp = state->txnNumber; + } + runCommandWithSession(conn, + nsToDatabaseSubstring(this->ns).toString(), + builder.done(), + kNoOptions, + lsid, + txnNumberForOp, + &result); + } else { + auto toSend = makeUpdateMessage(this->ns, + query, + update, + (this->upsert ? UpdateOption_Upsert : 0) | + (this->multi ? UpdateOption_Multi : 0)); + conn->say(toSend); + if (this->safe) + result = conn->getLastErrorDetailed(); + } + } + + if (this->safe) { + if (!config.hideResults || this->showResult) + log() << "Result from benchRun thread [safe update] : " << result; + + if (!result["err"].eoo() && result["err"].type() == String && + (config.throwGLE || this->throwGLE)) + uasserted(result["code"].eoo() ? 0 : result["code"].Int(), + (std::string) "From benchRun GLE" + causedBy(result["err"].String())); + } + } break; + case OpType::INSERT: { + BSONObj result; + { + BenchRunEventTrace _bret(&state->stats->insertCounter); + + BSONObj insertDoc; + if (this->useWriteCmd) { + BSONObjBuilder builder; + builder.append("insert", nsToCollectionSubstring(this->ns)); + BSONArrayBuilder docBuilder(builder.subarrayStart("documents")); + if (this->isDocAnArray) { + for (const auto& element : this->doc) { + insertDoc = fixQuery(element.Obj(), *state->bsonTemplateEvaluator); + docBuilder.append(insertDoc); + } + } else { + insertDoc = fixQuery(this->doc, *state->bsonTemplateEvaluator); + docBuilder.append(insertDoc); + } + docBuilder.done(); + builder.append("writeConcern", this->writeConcern); + + boost::optional<TxnNumber> txnNumberForOp; + if (config.useIdempotentWrites) { + ++state->txnNumber; + txnNumberForOp = state->txnNumber; + } + runCommandWithSession(conn, + nsToDatabaseSubstring(this->ns).toString(), + builder.done(), + kNoOptions, + lsid, + txnNumberForOp, + &result); + } else { + std::vector<BSONObj> insertArray; + if (this->isDocAnArray) { + for (const auto& element : this->doc) { + BSONObj e = fixQuery(element.Obj(), *state->bsonTemplateEvaluator); + insertArray.push_back(e); + } + } else { + insertArray.push_back(fixQuery(this->doc, *state->bsonTemplateEvaluator)); + } + + auto toSend = + makeInsertMessage(this->ns, insertArray.data(), insertArray.size()); + conn->say(toSend); + + if (this->safe) + result = conn->getLastErrorDetailed(); + } + } + + if (this->safe) { + if (!config.hideResults || this->showResult) + log() << "Result from benchRun thread [safe insert] : " << result; + + if (!result["err"].eoo() && result["err"].type() == String && + (config.throwGLE || this->throwGLE)) + uasserted(result["code"].eoo() ? 0 : result["code"].Int(), + (std::string) "From benchRun GLE" + causedBy(result["err"].String())); + } + } break; + case OpType::REMOVE: { + BSONObj result; + { + BenchRunEventTrace _bret(&state->stats->deleteCounter); + BSONObj predicate = fixQuery(this->query, *state->bsonTemplateEvaluator); + if (this->useWriteCmd) { + BSONObjBuilder builder; + builder.append("delete", nsToCollectionSubstring(this->ns)); + BSONArrayBuilder docBuilder(builder.subarrayStart("deletes")); + int limit = (this->multi == true) ? 0 : 1; + docBuilder.append(BSON("q" << predicate << "limit" << limit)); + docBuilder.done(); + builder.append("writeConcern", this->writeConcern); + + boost::optional<TxnNumber> txnNumberForOp; + if (config.useIdempotentWrites) { + ++state->txnNumber; + txnNumberForOp = state->txnNumber; + } + runCommandWithSession(conn, + nsToDatabaseSubstring(this->ns).toString(), + builder.done(), + kNoOptions, + lsid, + txnNumberForOp, + &result); + } else { + auto toSend = makeRemoveMessage( + this->ns, predicate, this->multi ? 0 : RemoveOption_JustOne); + conn->say(toSend); + if (this->safe) + result = conn->getLastErrorDetailed(); + } + } + + if (this->safe) { + if (!config.hideResults || this->showResult) + log() << "Result from benchRun thread [safe remove] : " << result; + + if (!result["err"].eoo() && result["err"].type() == String && + (config.throwGLE || this->throwGLE)) + uasserted(result["code"].eoo() ? 0 : result["code"].Int(), + (std::string) "From benchRun GLE " + + causedBy(result["err"].String())); + } + } break; + case OpType::CREATEINDEX: + conn->createIndex(this->ns, this->key); + break; + case OpType::DROPINDEX: + conn->dropIndex(this->ns, this->key); + break; + case OpType::LET: { + BSONObjBuilder templateBuilder; + state->bsonTemplateEvaluator->evaluate(this->value, templateBuilder); + state->bsonTemplateEvaluator->setVariable(this->target, + templateBuilder.done().firstElement()); + } break; + default: + uassert(34397, "In benchRun loop and got unknown op type", false); + } + + // Count 1 for total ops. Successfully got through the try phrase + ++state->stats->opCount; +} + void BenchRunWorker::run() { try { auto conn(_config->createConnection()); diff --git a/src/mongo/shell/bench.h b/src/mongo/shell/bench.h index c335af687a1..88314da99e2 100644 --- a/src/mongo/shell/bench.h +++ b/src/mongo/shell/bench.h @@ -31,8 +31,10 @@ #include <boost/optional.hpp> #include <string> +#include "mongo/base/shim.h" #include "mongo/client/dbclient_base.h" #include "mongo/db/jsobj.h" +#include "mongo/db/logical_session_id.h" #include "mongo/platform/atomic_word.h" #include "mongo/stdx/condition_variable.h" #include "mongo/stdx/mutex.h" @@ -59,11 +61,35 @@ enum class OpType { CPULOAD }; +class BenchRunConfig; +struct BenchRunStats; +class BsonTemplateEvaluator; +class LogicalSessionIdToClient; + /** * Object representing one operation passed to benchRun */ struct BenchRunOp { -public: + struct State { + State(PseudoRandom* rng_, + BsonTemplateEvaluator* bsonTemplateEvaluator_, + BenchRunStats* stats_) + : rng(rng_), bsonTemplateEvaluator(bsonTemplateEvaluator_), stats(stats_) {} + + PseudoRandom* rng; + BsonTemplateEvaluator* bsonTemplateEvaluator; + BenchRunStats* stats; + + // Transaction state + TxnNumber txnNumber = 0; + bool inProgressMultiStatementTxn = false; + }; + + void executeOnce(DBClientBase* conn, + const boost::optional<LogicalSessionIdToClient>& lsid, + const BenchRunConfig& config, + State* state) const; + int batchSize = 0; BSONElement check; BSONObj command; @@ -129,6 +155,9 @@ public: */ static BenchRunConfig* createFromBson(const BSONObj& args); + static MONGO_DECLARE_SHIM((const BenchRunConfig&)->std::unique_ptr<DBClientBase>) + createConnectionImpl; + BenchRunConfig(); void initializeFromBson(const BSONObj& args); @@ -218,6 +247,8 @@ public: bool breakOnTrap; private: + static std::function<std::unique_ptr<DBClientBase>(const BenchRunConfig&)> _factory; + /// Initialize a config object to its default values. void initializeToDefaults(); }; diff --git a/src/mongo/shell/shell_utils.cpp b/src/mongo/shell/shell_utils.cpp index 0d4b118dc70..77b1b6d9d99 100644 --- a/src/mongo/shell/shell_utils.cpp +++ b/src/mongo/shell/shell_utils.cpp @@ -62,6 +62,17 @@ extern const JSFile replsettest; extern const JSFile bridge; } +MONGO_REGISTER_SHIM(BenchRunConfig::createConnectionImpl) +(const BenchRunConfig& config)->std::unique_ptr<DBClientBase> { + const ConnectionString connectionString = uassertStatusOK(ConnectionString::parse(config.host)); + + std::string errorMessage; + std::unique_ptr<DBClientBase> connection(connectionString.connect("BenchRun", errorMessage)); + uassert(16158, errorMessage, connection); + + return connection; +} + namespace shell_utils { std::string _dbConnect; diff --git a/src/mongo/tools/SConscript b/src/mongo/tools/SConscript index dc4bfe408c7..f2226009f85 100644 --- a/src/mongo/tools/SConscript +++ b/src/mongo/tools/SConscript @@ -5,6 +5,9 @@ Import('get_option') env = env.Clone() +yamlEnv = env.Clone() +yamlEnv.InjectThirdPartyIncludePaths(libraries=['yaml']) + mongobridge = env.Program( target="mongobridge", source=[ @@ -29,6 +32,29 @@ mongobridge = env.Program( ], ) +mongoebench = yamlEnv.Program( + target='mongoebench', + source=[ + 'mongoebench_main.cpp', + 'mongoebench_options.cpp', + 'mongoebench_options_init.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/db/dbdirectclient', + '$BUILD_DIR/mongo/embedded/embedded', + '$BUILD_DIR/mongo/embedded/embedded_integration_helpers', + '$BUILD_DIR/mongo/shell/benchrun', + '$BUILD_DIR/mongo/util/signal_handlers', + ], + INSTALL_ALIAS=[ + 'mobile-test', + 'tools', + ], +) + hygienic = get_option('install-mode') == 'hygienic' if not hygienic: env.Install("#/", mongobridge) + env.Install("#/", mongoebench) + +env.Alias('all', mongoebench) # This ensures it compiles and links, but doesn't copy it anywhere. diff --git a/src/mongo/tools/mongoebench_main.cpp b/src/mongo/tools/mongoebench_main.cpp new file mode 100644 index 00000000000..ef00204a0b4 --- /dev/null +++ b/src/mongo/tools/mongoebench_main.cpp @@ -0,0 +1,161 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault + +#include "mongo/platform/basic.h" + +#include "mongo/base/init.h" +#include "mongo/db/dbdirectclient.h" +#include "mongo/db/service_context.h" +#include "mongo/embedded/embedded.h" +#include "mongo/embedded/embedded_options.h" +#include "mongo/embedded/embedded_options_helpers.h" +#include "mongo/scripting/bson_template_evaluator.h" +#include "mongo/shell/bench.h" +#include "mongo/tools/mongoebench_options.h" +#include "mongo/util/exit.h" +#include "mongo/util/log.h" +#include "mongo/util/options_parser/option_section.h" +#include "mongo/util/signal_handlers.h" +#include "mongo/util/text.h" + +namespace mongo { +namespace { + +/** + * DBDirectClientWithOwnOpCtx is a version of DBDirectClient that owns its own OperationContext. + * + * Since benchRun originally existed only in the mongo shell and was used with only + * DBClientConnections, there isn't a part of the BenchRunConfig or BenchRunWorker interfaces that + * is aware of having an OperationContext. In particular, the mongo shell lacks a ServiceContext and + * therefore also lacks the rest of the Client and OperationContext hierarchy. We shove an + * OperationContext onto the DBDirectClient to work around this limitation for mongoebench to work. + */ +class DBDirectClientWithOwnOpCtx : public DBDirectClient { +public: + DBDirectClientWithOwnOpCtx() : DBDirectClient(nullptr) { + Client::initThreadIfNotAlready(); + _opCtx = cc().makeOperationContext(); + setOpCtx(_opCtx.get()); + } + +private: + ServiceContext::UniqueOperationContext _opCtx; +}; + +MONGO_INITIALIZER_WITH_PREREQUISITES(SignalProcessingStartup, ("ThreadNameInitializer")) +(InitializerContext*) { + // Make sure we call this as soon as possible but before any other threads are started. Before + // embedded::initialize is too early and after is too late. So instead we hook in during the + // global initialization at the right place. + startSignalProcessingThread(); + return Status::OK(); +} + +int mongoeBenchMain(int argc, char* argv[], char** envp) { + ServiceContext* serviceContext = nullptr; + + registerShutdownTask([&]() { + if (serviceContext) { + embedded::shutdown(serviceContext); + } + }); + + setupSignalHandlers(); + + log() << "MongoDB embedded benchRun application, for testing purposes only"; + + try { + optionenvironment::OptionSection startupOptions("Options"); + uassertStatusOK(embedded::addOptions(&startupOptions)); + uassertStatusOK(addMongoeBenchOptions(&startupOptions)); + uassertStatusOK( + embedded_integration_helpers::parseCommandLineOptions(argc, argv, startupOptions)); + serviceContext = embedded::initialize(nullptr); + } catch (const std::exception& ex) { + error() << ex.what(); + return EXIT_BADOPTIONS; + } + + // If a "pre" section was present in the benchRun config file, then we run its operations once + // before running the operations from the "ops" section. + if (mongoeBenchGlobalParams.preConfig) { + auto conn = mongoeBenchGlobalParams.preConfig->createConnection(); + boost::optional<LogicalSessionIdToClient> lsid; + + PseudoRandom rng(mongoeBenchGlobalParams.preConfig->randomSeed); + BsonTemplateEvaluator bsonTemplateEvaluator(mongoeBenchGlobalParams.preConfig->randomSeed); + BenchRunStats stats; + BenchRunOp::State state(&rng, &bsonTemplateEvaluator, &stats); + + for (auto&& op : mongoeBenchGlobalParams.preConfig->ops) { + op.executeOnce(conn.get(), lsid, *mongoeBenchGlobalParams.preConfig, &state); + } + } + + // If an "ops" section was present in the benchRun config file, then we repeatedly run its + // operations across the configured number of threads for the configured number of seconds. + if (mongoeBenchGlobalParams.opsConfig) { + const double seconds = mongoeBenchGlobalParams.opsConfig->seconds; + auto runner = std::make_unique<BenchRunner>(mongoeBenchGlobalParams.opsConfig.release()); + runner->start(); + + sleepmillis(static_cast<long long>(seconds * 1000)); + + BSONObj stats = BenchRunner::finish(runner.release()); + log() << "stats: " << stats; + } + + shutdown(EXIT_CLEAN); +} + +} // namespace + +MONGO_REGISTER_SHIM(BenchRunConfig::createConnectionImpl) +(const BenchRunConfig& config)->std::unique_ptr<DBClientBase> { + return std::unique_ptr<DBClientBase>(new DBDirectClientWithOwnOpCtx()); +} + +} // namespace mongo + +#if defined(_WIN32) +// In Windows, wmain() is an alternate entry point for main(), and receives the same parameters as +// main() but encoded in Windows Unicode (UTF-16); "wide" 16-bit wchar_t characters. The +// WindowsCommandLine object converts these wide character strings to a UTF-8 coded equivalent and +// makes them available through the argv() and envp() members. This enables mongoeBenchMain() to +// process UTF-8 encoded arguments and environment variables without regard to platform. +int wmain(int argc, wchar_t* argvW[], wchar_t* envpW[]) { + mongo::WindowsCommandLine wcl(argc, argvW, envpW); + return mongo::mongoeBenchMain(argc, wcl.argv(), wcl.envp()); +} +#else +int main(int argc, char* argv[], char** envp) { + return mongo::mongoeBenchMain(argc, argv, envp); +} +#endif diff --git a/src/mongo/tools/mongoebench_options.cpp b/src/mongo/tools/mongoebench_options.cpp new file mode 100644 index 00000000000..1cfd950fe0d --- /dev/null +++ b/src/mongo/tools/mongoebench_options.cpp @@ -0,0 +1,137 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/tools/mongoebench_options.h" + +#include <algorithm> +#include <fstream> +#include <iostream> +#include <iterator> + +#include "mongo/base/status.h" +#include "mongo/db/storage/storage_options.h" +#include "mongo/platform/random.h" +#include "mongo/shell/bench.h" +#include "mongo/util/mongoutils/str.h" +#include "mongo/util/options_parser/startup_options.h" + +namespace mongo { + +MongoeBenchGlobalParams mongoeBenchGlobalParams; + +Status addMongoeBenchOptions(moe::OptionSection* options) { + options->addOptionChaining("help", "help", moe::Switch, "show this usage information"); + + options + ->addOptionChaining( + "benchRunConfigFile", "benchRunConfigFile", moe::String, "config file for benchRun") + .hidden() + .positional(1, 1); + + options->addOptionChaining("seed", "seed", moe::Long, "random seed to use"); + + options + ->addOptionChaining( + "threads", "threads,t", moe::Unsigned, "number of benchRun worker threads") + .setDefault(moe::Value(1U)); + + options->addOptionChaining("time", "time,s", moe::Double, "seconds to run benchRun for") + .setDefault(moe::Value(1.0)); + + return Status::OK(); +} + +void printMongoeBenchHelp(std::ostream* out) { + *out << "Usage: mongoebench <config file> [options]" << std::endl; + *out << moe::startupOptions.helpString(); + *out << std::flush; +} + +bool handlePreValidationMongoeBenchOptions(const moe::Environment& params) { + if (params.count("help")) { + printMongoeBenchHelp(&std::cout); + return false; + } + return true; +} + +namespace { + +BSONObj getBsonFromJsonFile(const std::string& filename) { + std::ifstream infile(filename.c_str()); + std::string data((std::istreambuf_iterator<char>(infile)), std::istreambuf_iterator<char>()); + return fromjson(data); +} + +} // namespace + +Status storeMongoeBenchOptions(const moe::Environment& params, + const std::vector<std::string>& args) { + if (!params.count("benchRunConfigFile")) { + return {ErrorCodes::BadValue, "No benchRun config file was specified"}; + } + + BSONObj config = getBsonFromJsonFile(params["benchRunConfigFile"].as<std::string>()); + for (auto&& elem : config) { + const auto fieldName = elem.fieldNameStringData(); + if (fieldName == "pre") { + mongoeBenchGlobalParams.preConfig.reset( + BenchRunConfig::createFromBson(elem.wrap("ops"))); + } else if (fieldName == "ops") { + mongoeBenchGlobalParams.opsConfig.reset(BenchRunConfig::createFromBson(elem.wrap())); + } else { + return {ErrorCodes::BadValue, + str::stream() << "Unrecognized key in benchRun config file: " << fieldName}; + } + } + + int64_t seed = params.count("seed") ? static_cast<int64_t>(params["seed"].as<long>()) + : SecureRandom::create()->nextInt64(); + + if (mongoeBenchGlobalParams.preConfig) { + mongoeBenchGlobalParams.preConfig->randomSeed = seed; + } + + if (mongoeBenchGlobalParams.opsConfig) { + mongoeBenchGlobalParams.opsConfig->randomSeed = seed; + + if (params.count("threads")) { + mongoeBenchGlobalParams.opsConfig->parallel = params["threads"].as<unsigned>(); + } + + if (params.count("time")) { + mongoeBenchGlobalParams.opsConfig->seconds = params["time"].as<double>(); + } + } + + return Status::OK(); +} + +} // namespace mongo diff --git a/src/mongo/tools/mongoebench_options.h b/src/mongo/tools/mongoebench_options.h new file mode 100644 index 00000000000..5b92c4d9758 --- /dev/null +++ b/src/mongo/tools/mongoebench_options.h @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include <cstdint> +#include <iosfwd> +#include <memory> +#include <string> +#include <vector> + +#include "mongo/base/status.h" + +namespace mongo { + +class BenchRunConfig; + +namespace optionenvironment { + +class OptionSection; +class Environment; + +} // namespace optionenvironment + +namespace moe = mongo::optionenvironment; + +struct MongoeBenchGlobalParams { + std::unique_ptr<BenchRunConfig> preConfig; + std::unique_ptr<BenchRunConfig> opsConfig; +}; + +extern MongoeBenchGlobalParams mongoeBenchGlobalParams; + +Status addMongoeBenchOptions(moe::OptionSection* options); + +void printMongoeBenchHelp(std::ostream* out); + +/** + * Handle options that should come before validation, such as "help". + * + * Returns false if an option was found that implies we should prematurely exit with success. + */ +bool handlePreValidationMongoeBenchOptions(const moe::Environment& params); + +Status storeMongoeBenchOptions(const moe::Environment& params, + const std::vector<std::string>& args); + +} // namespace mongo diff --git a/src/mongo/tools/mongoebench_options_init.cpp b/src/mongo/tools/mongoebench_options_init.cpp new file mode 100644 index 00000000000..5818f6b916c --- /dev/null +++ b/src/mongo/tools/mongoebench_options_init.cpp @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/tools/mongoebench_options.h" + +#include "mongo/util/options_parser/startup_option_init.h" +#include "mongo/util/options_parser/startup_options.h" +#include "mongo/util/quick_exit.h" + +namespace mongo { +namespace { + +MONGO_GENERAL_STARTUP_OPTIONS_REGISTER(MongoeBenchOptions)(InitializerContext* context) { + return addMongoeBenchOptions(&moe::startupOptions); +} + +GlobalInitializerRegisterer mongoeBenchOptionsStore( + "MongoeBenchOptions_Store", + {"BeginStartupOptionStorage"}, + {"EndStartupOptionStorage"}, + [](InitializerContext* context) { + if (!handlePreValidationMongoeBenchOptions(moe::startupOptionsParsed)) { + quickExit(EXIT_SUCCESS); + } + return storeMongoeBenchOptions(moe::startupOptionsParsed, context->args()); + }); + +} // namespace +} // namespace mongo |