diff options
Diffstat (limited to 'src/mongo/scripting/bench.cpp')
| -rw-r--r-- | src/mongo/scripting/bench.cpp | 884 |
1 files changed, 884 insertions, 0 deletions
diff --git a/src/mongo/scripting/bench.cpp b/src/mongo/scripting/bench.cpp new file mode 100644 index 00000000000..db3ca7351a7 --- /dev/null +++ b/src/mongo/scripting/bench.cpp @@ -0,0 +1,884 @@ +/** @file bench.cpp */ + +/* + * Copyright (C) 2010 10gen 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/>. + */ + + + +#include "pch.h" + +#include "mongo/scripting/bench.h" + +#include <boost/thread/thread.hpp> + +#include "mongo/client/dbclientcursor.h" +#include "mongo/scripting/engine.h" +#include "mongo/util/md5.h" +#include "mongo/util/timer.h" +#include "mongo/util/version.h" + + +// --------------------------------- +// ---- benchmarking system -------- +// --------------------------------- + +// TODO: Maybe extract as library to avoid code duplication? +namespace { + inline pcrecpp::RE_Options flags2options(const char* flags) { + pcrecpp::RE_Options options; + options.set_utf8(true); + while ( flags && *flags ) { + if ( *flags == 'i' ) + options.set_caseless(true); + else if ( *flags == 'm' ) + options.set_multiline(true); + else if ( *flags == 'x' ) + options.set_extended(true); + flags++; + } + return options; + } +} + +namespace mongo { + + BenchRunEventCounter::BenchRunEventCounter() { + reset(); + } + + BenchRunEventCounter::~BenchRunEventCounter() {} + + void BenchRunEventCounter::reset() { + _numEvents = 0; + _totalTimeMicros = 0; + } + + void BenchRunEventCounter::updateFrom(const BenchRunEventCounter &other) { + _numEvents += other._numEvents; + _totalTimeMicros += other._totalTimeMicros; + } + + BenchRunStats::BenchRunStats() { + reset(); + } + + BenchRunStats::~BenchRunStats() {} + + void BenchRunStats::reset() { + error = false; + errCount = 0; + + findOneCounter.reset(); + updateCounter.reset(); + insertCounter.reset(); + deleteCounter.reset(); + queryCounter.reset(); + + trappedErrors.clear(); + } + + void BenchRunStats::updateFrom(const BenchRunStats &other) { + if (other.error) + error = true; + errCount += other.errCount; + + findOneCounter.updateFrom(other.findOneCounter); + updateCounter.updateFrom(other.updateCounter); + insertCounter.updateFrom(other.insertCounter); + deleteCounter.updateFrom(other.deleteCounter); + queryCounter.updateFrom(other.queryCounter); + + for (size_t i = 0; i < other.trappedErrors.size(); ++i) + trappedErrors.push_back(other.trappedErrors[i]); + } + + BenchRunConfig::BenchRunConfig() { + initializeToDefaults(); + } + + void BenchRunConfig::initializeToDefaults() { + host = "localhost"; + db = "test"; + username = ""; + password = ""; + + parallel = 1; + seconds = 1; + hideResults = true; + handleErrors = false; + hideErrors = false; + + trapPattern.reset(); + noTrapPattern.reset(); + watchPattern.reset(); + noWatchPattern.reset(); + + ops = BSONObj(); + + throwGLE = false; + breakOnTrap = true; + } + + BenchRunConfig *BenchRunConfig::createFromBson( const BSONObj &args ) { + BenchRunConfig *config = new BenchRunConfig(); + config->initializeFromBson( args ); + return config; + } + + void BenchRunConfig::initializeFromBson( const BSONObj &args ) { + initializeToDefaults(); + + if ( args["host"].type() == String ) + this->host = args["host"].String(); + if ( args["db"].type() == String ) + this->db = args["db"].String(); + if ( args["username"].type() == String ) + this->username = args["username"].String(); + if ( args["password"].type() == String ) + this->db = args["password"].String(); + + if ( args["parallel"].isNumber() ) + this->parallel = args["parallel"].numberInt(); + if ( args["seconds"].isNumber() ) + this->seconds = args["seconds"].number(); + if ( ! args["hideResults"].eoo() ) + this->hideResults = args["hideResults"].trueValue(); + if ( ! args["handleErrors"].eoo() ) + this->handleErrors = args["handleErrors"].trueValue(); + if ( ! args["hideErrors"].eoo() ) + this->hideErrors = args["hideErrors"].trueValue(); + if ( ! args["throwGLE"].eoo() ) + this->throwGLE = args["throwGLE"].trueValue(); + if ( ! args["breakOnTrap"].eoo() ) + this->breakOnTrap = args["breakOnTrap"].trueValue(); + + uassert(16164, "loopCommands config not supported", args["loopCommands"].eoo()); + + if ( ! args["trapPattern"].eoo() ){ + const char* regex = args["trapPattern"].regex(); + const char* flags = args["trapPattern"].regexFlags(); + this->trapPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) ); + } + + if ( ! args["noTrapPattern"].eoo() ){ + const char* regex = args["noTrapPattern"].regex(); + const char* flags = args["noTrapPattern"].regexFlags(); + this->noTrapPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) ); + } + + if ( ! args["watchPattern"].eoo() ){ + const char* regex = args["watchPattern"].regex(); + const char* flags = args["watchPattern"].regexFlags(); + this->watchPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) ); + } + + if ( ! args["noWatchPattern"].eoo() ){ + const char* regex = args["noWatchPattern"].regex(); + const char* flags = args["noWatchPattern"].regexFlags(); + this->noWatchPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) ); + } + + this->ops = args["ops"].Obj().getOwned(); + } + + DBClientBase *BenchRunConfig::createConnection() const { + std::string errorMessage; + ConnectionString connectionString = ConnectionString::parse( host, errorMessage ); + uassert( 16157, errorMessage, connectionString.isValid() ); + DBClientBase *connection = connectionString.connect(errorMessage); + uassert( 16158, errorMessage, connection != NULL ); + return connection; + } + + BenchRunState::BenchRunState( unsigned numWorkers ) + : _mutex(), + _numUnstartedWorkers( numWorkers ), + _numActiveWorkers( 0 ), + _isShuttingDown( 0 ) { + } + + BenchRunState::~BenchRunState() { + wassert(_numActiveWorkers == 0 && _numUnstartedWorkers == 0); + } + + void BenchRunState::waitForState(State awaitedState) { + boost::mutex::scoped_lock lk(_mutex); + + switch ( awaitedState ) { + case BRS_RUNNING: + while ( _numUnstartedWorkers > 0 ) { + massert( 16147, "Already finished.", _numUnstartedWorkers + _numActiveWorkers > 0 ); + _stateChangeCondition.wait( _mutex ); + } + break; + case BRS_FINISHED: + while ( _numUnstartedWorkers + _numActiveWorkers > 0 ) { + _stateChangeCondition.wait( _mutex ); + } + break; + default: + msgasserted(16152, mongoutils::str::stream() << "Cannot wait for state " << awaitedState); + } + } + + void BenchRunState::tellWorkersToFinish() { + _isShuttingDown.set( 1 ); + } + + void BenchRunState::assertFinished() { + boost::mutex::scoped_lock lk(_mutex); + verify(0 == _numUnstartedWorkers + _numActiveWorkers); + } + + bool BenchRunState::shouldWorkerFinish() { + return bool(_isShuttingDown.get()); + } + + void BenchRunState::onWorkerStarted() { + boost::mutex::scoped_lock lk(_mutex); + verify( _numUnstartedWorkers > 0 ); + --_numUnstartedWorkers; + ++_numActiveWorkers; + if (_numUnstartedWorkers == 0) { + _stateChangeCondition.notify_all(); + } + } + + void BenchRunState::onWorkerFinished() { + boost::mutex::scoped_lock lk(_mutex); + verify( _numActiveWorkers > 0 ); + --_numActiveWorkers; + if (_numActiveWorkers + _numUnstartedWorkers == 0) { + _stateChangeCondition.notify_all(); + } + } + + BSONObj benchStart( const BSONObj& , void* ); + BSONObj benchFinish( const BSONObj& , void* ); + + static bool _hasSpecial( const BSONObj& obj ) { + BSONObjIterator i( obj ); + while ( i.more() ) { + BSONElement e = i.next(); + if ( e.fieldName()[0] == '#' ) + return true; + + if ( ! e.isABSONObj() ) + continue; + + if ( _hasSpecial( e.Obj() ) ) + return true; + } + return false; + } + + static void _fixField( BSONObjBuilder& b , const BSONElement& e ) { + verify( e.type() == Object ); + + BSONObj sub = e.Obj(); + verify( sub.nFields() == 1 ); + + BSONElement f = sub.firstElement(); + if ( str::equals( "#RAND_INT" , f.fieldName() ) ) { + BSONObjIterator i( f.Obj() ); + int min = i.next().numberInt(); + int max = i.next().numberInt(); + + int x = min + ( rand() % ( max - min ) ); + + if ( i.more() ) + x *= i.next().numberInt(); + + b.append( e.fieldName() , x ); + } + else { + uasserted( 14811 , str::stream() << "invalid bench dynamic piece: " << f.fieldName() ); + } + + } + + static void fixQuery( BSONObjBuilder& b , const BSONObj& obj ) { + BSONObjIterator i( obj ); + while ( i.more() ) { + BSONElement e = i.next(); + + if ( ! e.isABSONObj() ) { + b.append( e ); + continue; + } + + BSONObj sub = e.Obj(); + if ( sub.firstElement().fieldName()[0] == '#' ) { + _fixField( b , e ); + } + else { + BSONObjBuilder xx( e.type() == Object ? b.subobjStart( e.fieldName() ) : b.subarrayStart( e.fieldName() ) ); + fixQuery( xx , sub ); + xx.done(); + } + + } + } + + static BSONObj fixQuery( const BSONObj& obj ) { + + if ( ! _hasSpecial( obj ) ) + return obj; + + BSONObjBuilder b( obj.objsize() + 128 ); + fixQuery( b , obj ); + return b.obj(); + } + + BenchRunWorker::BenchRunWorker(const BenchRunConfig *config, BenchRunState *brState) + : _config(config), _brState(brState) { + } + + BenchRunWorker::~BenchRunWorker() {} + + void BenchRunWorker::start() { + boost::thread(boost::bind(&BenchRunWorker::run, this)); + } + + bool BenchRunWorker::shouldStop() const { + return _brState->shouldWorkerFinish(); + } + + void doNothing(const BSONObj&) { } + + void BenchRunWorker::generateLoadOnConnection( DBClientBase* conn ) { + verify( conn ); + long long count = 0; + mongo::Timer timer; + + while ( !shouldStop() ) { + BSONObjIterator i( _config->ops ); + while ( i.more() ) { + + if ( shouldStop() ) break; + + BSONElement e = i.next(); + + string ns = e["ns"].String(); + string op = e["op"].String(); + + int delay = e["delay"].eoo() ? 0 : e["delay"].Int(); + + BSONObj context = e["context"].eoo() ? BSONObj() : e["context"].Obj(); + + auto_ptr<Scope> scope; + ScriptingFunction scopeFunc = 0; + BSONObj scopeObj; + + if (_config->username != "") { + string errmsg; + if (!conn->auth(_config->db, _config->username, _config->password, errmsg)) { + uasserted(15931, "Authenticating to connection for _benchThread failed: " + errmsg); + } + } + + bool check = ! e["check"].eoo(); + if( check ){ + if ( e["check"].type() == CodeWScope || e["check"].type() == Code || e["check"].type() == String ) { + scope = globalScriptEngine->getPooledScope( ns ); + verify( scope.get() ); + + if ( e.type() == CodeWScope ) { + scopeFunc = scope->createFunction( e["check"].codeWScopeCode() ); + scopeObj = BSONObj( e.codeWScopeScopeDataUnsafe() ); + } + else { + scopeFunc = scope->createFunction( e["check"].valuestr() ); + } + + scope->init( &scopeObj ); + verify( scopeFunc ); + } + else { + warning() << "Invalid check type detected in benchRun op : " << e << endl; + check = false; + } + } + + try { + if ( op == "findOne" ) { + + BSONObj result; + { + BenchRunEventTrace _bret(&_stats.findOneCounter); + result = conn->findOne( ns , fixQuery( e["query"].Obj() ) ); + } + + if( check ){ + int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [findOne]" << causedBy( scope->getError() ) << endl; + + _stats.errCount++; + + return; + } + } + + if( ! _config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [findOne] : " << result << endl; + + } + else if ( op == "command" ) { + + BSONObj result; + // TODO + /* bool ok = */ conn->runCommand( ns , fixQuery( e["command"].Obj() ), result, e["options"].numberInt() ); + + if( check ){ + int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [command]" << causedBy( scope->getError() ) << endl; + + _stats.errCount++; + + return; + } + } + + if( ! _config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [command] : " << result << endl; + + } + else if( op == "find" || op == "query" ) { + + int limit = e["limit"].eoo() ? 0 : e["limit"].numberInt(); + int skip = e["skip"].eoo() ? 0 : e["skip"].Int(); + int options = e["options"].eoo() ? 0 : e["options"].Int(); + int batchSize = e["batchSize"].eoo() ? 0 : e["batchSize"].Int(); + BSONObj filter = e["filter"].eoo() ? BSONObj() : e["filter"].Obj(); + int expected = e["expected"].eoo() ? -1 : e["expected"].Int(); + + auto_ptr<DBClientCursor> cursor; + int count; + + BSONObj fixedQuery = fixQuery(e["query"].Obj()); + + // use special query function for exhaust query option + if (options & QueryOption_Exhaust) { + BenchRunEventTrace _bret(&_stats.queryCounter); + boost::function<void (const BSONObj&)> castedDoNothing(doNothing); + count = conn->query(castedDoNothing, ns, fixedQuery, &filter, options); + } + else { + BenchRunEventTrace _bret(&_stats.queryCounter); + cursor = conn->query( ns, fixedQuery, limit, skip, &filter, options, batchSize ); + count = cursor->itcount(); + } + + if ( expected >= 0 && count != expected ) { + cout << "bench query on: " << ns << " expected: " << expected << " got: " << count << endl; + verify(false); + } + + if( check ){ + BSONObj thisValue = BSON( "count" << count << "context" << context ); + int err = scope->invoke( scopeFunc , 0 , &thisValue, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [find]" << causedBy( scope->getError() ) << endl; + + _stats.errCount++; + + return; + } + } + + if( ! _config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [query] : " << count << endl; + + } + else if( op == "update" ) { + + bool multi = e["multi"].trueValue(); + bool upsert = e["upsert"].trueValue(); + BSONObj query = e["query"].eoo() ? BSONObj() : e["query"].Obj(); + BSONObj update = e["update"].Obj(); + BSONObj result; + bool safe = e["safe"].trueValue(); + + { + BenchRunEventTrace _bret(&_stats.updateCounter); + conn->update( ns, fixQuery( query ), update, upsert , multi ); + if (safe) + result = conn->getLastErrorDetailed(); + } + + if( safe ){ + if( check ){ + int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [update]" << causedBy( scope->getError() ) << endl; + + _stats.errCount++; + + return; + } + } + + if( ! _config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [safe update] : " << result << endl; + + if( ! result["err"].eoo() && result["err"].type() == String && ( _config->throwGLE || e["throwGLE"].trueValue() ) ) + throw DBException( (string)"From benchRun GLE" + causedBy( result["err"].String() ), + result["code"].eoo() ? 0 : result["code"].Int() ); + } + } + else if( op == "insert" ) { + bool safe = e["safe"].trueValue(); + BSONObj result; + { + BenchRunEventTrace _bret(&_stats.insertCounter); + conn->insert( ns, fixQuery( e["doc"].Obj() ) ); + if (safe) + result = conn->getLastErrorDetailed(); + } + + if( safe ){ + if( check ){ + int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [insert]" << causedBy( scope->getError() ) << endl; + + _stats.errCount++; + + return; + } + } + + if( ! _config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [safe insert] : " << result << endl; + + if( ! result["err"].eoo() && result["err"].type() == String && ( _config->throwGLE || e["throwGLE"].trueValue() ) ) + throw DBException( (string)"From benchRun GLE" + causedBy( result["err"].String() ), + result["code"].eoo() ? 0 : result["code"].Int() ); + } + } + else if( op == "delete" || op == "remove" ) { + + bool multi = e["multi"].eoo() ? true : e["multi"].trueValue(); + BSONObj query = e["query"].eoo() ? BSONObj() : e["query"].Obj(); + bool safe = e["safe"].trueValue(); + BSONObj result; + + { + BenchRunEventTrace _bret(&_stats.deleteCounter); + conn->remove( ns, fixQuery( query ), ! multi ); + if (safe) + result = conn->getLastErrorDetailed(); + } + + if( safe ){ + if( check ){ + int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [delete]" << causedBy( scope->getError() ) << endl; + + _stats.errCount++; + + return; + } + } + + if( ! _config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [safe remove] : " << result << endl; + + if( ! result["err"].eoo() && result["err"].type() == String && ( _config->throwGLE || e["throwGLE"].trueValue() ) ) + throw DBException( (string)"From benchRun GLE " + causedBy( result["err"].String() ), + result["code"].eoo() ? 0 : result["code"].Int() ); + } + } + else if ( op == "createIndex" ) { + conn->ensureIndex( ns , e["key"].Obj() , false , "" , false ); + } + else if ( op == "dropIndex" ) { + conn->dropIndex( ns , e["key"].Obj() ); + } + else { + log() << "don't understand op: " << op << endl; + _stats.error = true; + return; + } + } + catch( DBException& ex ){ + if( ! _config->hideErrors || e["showError"].trueValue() ){ + + bool yesWatch = ( _config->watchPattern && _config->watchPattern->FullMatch( ex.what() ) ); + bool noWatch = ( _config->noWatchPattern && _config->noWatchPattern->FullMatch( ex.what() ) ); + + if( ( ! _config->watchPattern && _config->noWatchPattern && ! noWatch ) || // If we're just ignoring things + ( ! _config->noWatchPattern && _config->watchPattern && yesWatch ) || // If we're just watching things + ( _config->watchPattern && _config->noWatchPattern && yesWatch && ! noWatch ) ) + log() << "Error in benchRun thread for op " << e << causedBy( ex ) << endl; + } + + bool yesTrap = ( _config->trapPattern && _config->trapPattern->FullMatch( ex.what() ) ); + bool noTrap = ( _config->noTrapPattern && _config->noTrapPattern->FullMatch( ex.what() ) ); + + if( ( ! _config->trapPattern && _config->noTrapPattern && ! noTrap ) || + ( ! _config->noTrapPattern && _config->trapPattern && yesTrap ) || + ( _config->trapPattern && _config->noTrapPattern && yesTrap && ! noTrap ) ){ + { + _stats.trappedErrors.push_back( BSON( "error" << ex.what() << "op" << e << "count" << count ) ); + } + if( _config->breakOnTrap ) return; + } + if( ! _config->handleErrors && ! e["handleError"].trueValue() ) return; + + _stats.errCount++; + } + catch( ... ){ + if( ! _config->hideErrors || e["showError"].trueValue() ) log() << "Error in benchRun thread caused by unknown error for op " << e << endl; + if( ! _config->handleErrors && ! e["handleError"].trueValue() ) return; + + _stats.errCount++; + } + + if ( ++count % 100 == 0 ) { + conn->getLastError(); + } + + sleepmillis( delay ); + } + } + + conn->getLastError(); + } + + namespace { + class BenchRunWorkerStateGuard : private boost::noncopyable { + public: + explicit BenchRunWorkerStateGuard( BenchRunState *brState ) : _brState( brState ) { + _brState->onWorkerStarted(); + } + + ~BenchRunWorkerStateGuard() { + _brState->onWorkerFinished(); + } + + private: + BenchRunState *_brState; + }; + } // namespace + + void BenchRunWorker::run() { + BenchRunWorkerStateGuard _workerStateGuard( _brState ); + + boost::scoped_ptr<DBClientBase> conn( _config->createConnection() ); + + try { + if ( !_config->username.empty() ) { + string errmsg; + if (!conn->auth(_config->db, _config->username, _config->password, errmsg)) { + uasserted(15932, "Authenticating to connection for benchThread failed: " + errmsg); + } + } + generateLoadOnConnection( conn.get() ); + } + catch( DBException& e ){ + error() << "DBException not handled in benchRun thread" << causedBy( e ) << endl; + } + catch( std::exception& e ){ + error() << "std::exception not handled in benchRun thread" << causedBy( e ) << endl; + } + catch( ... ){ + error() << "Unknown exception not handled in benchRun thread." << endl; + } + } + + BenchRunner::BenchRunner( BenchRunConfig *config ) + : _brState(config->parallel), + _config(config) { + + _oid.init(); + boost::mutex::scoped_lock lk(_staticMutex); + _activeRuns[_oid] = this; + } + + BenchRunner::~BenchRunner() { + for (size_t i = 0; i < _workers.size(); ++i) + delete _workers[i]; + } + + void BenchRunner::start( ) { + + + { + boost::scoped_ptr<DBClientBase> conn( _config->createConnection() ); + // Get initial stats + conn->simpleCommand( "admin" , &before , "serverStatus" ); + before = before.getOwned(); + } + + // Start threads + for ( unsigned i = 0; i < _config->parallel; i++ ) { + BenchRunWorker *worker = new BenchRunWorker(_config.get(), &_brState); + worker->start(); + _workers.push_back(worker); + } + + _brState.waitForState(BenchRunState::BRS_RUNNING); + } + + void BenchRunner::stop() { + _brState.tellWorkersToFinish(); + _brState.waitForState(BenchRunState::BRS_FINISHED); + + { + boost::scoped_ptr<DBClientBase> conn( _config->createConnection() ); + // Get final stats + conn->simpleCommand( "admin" , &after , "serverStatus" ); + after = after.getOwned(); + } + + { + boost::mutex::scoped_lock lk(_staticMutex); + _activeRuns.erase( _oid ); + } + } + + BenchRunner* BenchRunner::createWithConfig( const BSONObj &configArgs ) { + BenchRunConfig *config = BenchRunConfig::createFromBson( configArgs ); + return new BenchRunner(config); + } + + BenchRunner* BenchRunner::get( OID oid ) { + boost::mutex::scoped_lock lk(_staticMutex); + return _activeRuns[ oid ]; + } + + void BenchRunner::populateStats( BenchRunStats *stats ) { + _brState.assertFinished(); + stats->reset(); + for ( size_t i = 0; i < _workers.size(); ++i ) + stats->updateFrom( _workers[i]->stats() ); + BSONObj before = this->before["opcounters"].Obj(); + BSONObj after = this->after["opcounters"].Obj(); + { + BSONObjIterator i( after ); + while ( i.more() ) { + BSONElement e = i.next(); + long long delta = e.numberLong(); + delta -= before[e.fieldName()].numberLong(); + stats->opcounters[e.fieldName()] = delta; + } + } + } + + static void appendAverageMicrosIfAvailable( + BSONObjBuilder &buf, const std::string &name, const BenchRunEventCounter &counter) { + + if (counter.getNumEvents() > 0) + buf.append(name, + static_cast<double>(counter.getTotalTimeMicros()) / counter.getNumEvents()); + } + + BSONObj BenchRunner::finish( BenchRunner* runner ) { + + runner->stop(); + + BenchRunStats stats; + runner->populateStats(&stats); + + // vector<BSONOBj> errors = runner->config.errors; + bool error = stats.error; + + if ( error ) + return BSON( "err" << 1 ); + + // compute actual ops/sec + BSONObj before = runner->before["opcounters"].Obj(); + BSONObj after = runner->after["opcounters"].Obj(); + + BSONObjBuilder buf; + buf.append( "note" , "values per second" ); + buf.append( "errCount", (long long) stats.errCount ); + buf.append( "trapped", "error: not implemented" ); + appendAverageMicrosIfAvailable(buf, "findOneLatencyAverageMicros", stats.findOneCounter); + appendAverageMicrosIfAvailable(buf, "insertLatencyAverageMicros", stats.insertCounter); + appendAverageMicrosIfAvailable(buf, "deleteLatencyAverageMicros", stats.deleteCounter); + appendAverageMicrosIfAvailable(buf, "updateLatencyAverageMicros", stats.updateCounter); + appendAverageMicrosIfAvailable(buf, "queryLatencyAverageMicros", stats.queryCounter); + + { + BSONObjIterator i( after ); + while ( i.more() ) { + BSONElement e = i.next(); + double x = e.number(); + x -= before[e.fieldName()].number(); + buf.append( e.fieldName() , x / runner->_config->seconds ); + } + } + + BSONObj zoo = buf.obj(); + + delete runner; + return zoo; + } + + boost::mutex BenchRunner::_staticMutex; + map< OID, BenchRunner* > BenchRunner::_activeRuns; + + /** + * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 } + */ + BSONObj benchRunSync( const BSONObj& argsFake, void* data ) { + + BSONObj start = benchStart( argsFake, data ); + + OID oid = OID( start.firstElement().String() ); + BenchRunner* runner = BenchRunner::get( oid ); + sleepmillis( (int)(1000.0 * runner->config().seconds) ); + + return benchFinish( start, data ); + } + + /** + * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 } + */ + BSONObj benchStart( const BSONObj& argsFake, void* data ) { + + verify( argsFake.firstElement().isABSONObj() ); + BSONObj args = argsFake.firstElement().Obj(); + + // Get new BenchRunner object + BenchRunner* runner = BenchRunner::createWithConfig( args ); + + runner->start(); + return BSON( "" << runner->oid().toString() ); + } + + /** + * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 } + */ + BSONObj benchFinish( const BSONObj& argsFake, void* data ) { + + OID oid = OID( argsFake.firstElement().String() ); + + // Get new BenchRunner object + BenchRunner* runner = BenchRunner::get( oid ); + + BSONObj finalObj = BenchRunner::finish( runner ); + + return BSON( "" << finalObj ); + } + + void installBenchmarkSystem( Scope& scope ) { + scope.injectNative( "benchRun" , benchRunSync ); + scope.injectNative( "benchRunSync" , benchRunSync ); + scope.injectNative( "benchStart" , benchStart ); + scope.injectNative( "benchFinish" , benchFinish ); + } + +} |
