/*
* 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 .
*
* 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
#include
#include
#include
#include
#include
#include "mongo/client/dbclientinterface.h"
#include "mongo/db/jsobj.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/util/timer.h"
namespace pcrecpp {
class RE;
} // namespace pcrecpp;
namespace mongo {
/**
* Configuration object describing a bench run activity.
*/
class BenchRunConfig : private boost::noncopyable {
public:
/**
* Create a new BenchRunConfig object, and initialize it from the BSON
* document, "args".
*
* Caller owns the returned object, and is responsible for its deletion.
*/
static BenchRunConfig *createFromBson( const BSONObj &args );
BenchRunConfig();
void initializeFromBson( const BSONObj &args );
// Create a new connection to the mongo instance specified by this configuration.
DBClientBase *createConnection() const;
/**
* Connection std::string describing the host to which to connect.
*/
std::string host;
/**
* Name of the database on which to operate.
*/
std::string db;
/**
* Optional username for authenticating to the database.
*/
std::string username;
/**
* Optional password for authenticating to the database.
*
* Only useful if username is non-empty.
*/
std::string password;
/**
* Number of parallel threads to perform the bench run activity.
*/
unsigned parallel;
/**
* Desired duration of the bench run activity, in seconds.
*
* NOTE: Only used by the javascript benchRun() and benchRunSync() functions.
*/
double seconds;
/// Base random seed for threads
int64_t randomSeed;
bool hideResults;
bool handleErrors;
bool hideErrors;
boost::shared_ptr< pcrecpp::RE > trapPattern;
boost::shared_ptr< pcrecpp::RE > noTrapPattern;
boost::shared_ptr< pcrecpp::RE > watchPattern;
boost::shared_ptr< pcrecpp::RE > noWatchPattern;
/**
* Operation description. A BSON array of objects, each describing a single
* operation.
*
* Every thread in a benchRun job will perform these operations in sequence, restarting at
* the beginning when the end is reached, until the job is stopped.
*
* TODO: Document the operation objects.
*
* TODO: Introduce support for performing each operation exactly N times.
*/
BSONObj ops;
bool throwGLE;
bool breakOnTrap;
private:
/// Initialize a config object to its default values.
void initializeToDefaults();
};
/**
* An event counter for events that have an associated duration.
*
* Not thread safe. Expected use is one instance per thread during parallel execution.
*/
class BenchRunEventCounter : private boost::noncopyable {
public:
/// Constructs a zeroed out counter.
BenchRunEventCounter();
~BenchRunEventCounter();
/**
* Zero out the counter.
*/
void reset();
/**
* Conceptually the equivalent of "+=". Adds "other" into this.
*/
void updateFrom( const BenchRunEventCounter &other );
/**
* Count one instance of the event, which took "timeMicros" microseconds.
*/
void countOne(long long timeMicros) {
++_numEvents;
_totalTimeMicros += timeMicros;
}
/**
* Get the total number of microseconds ellapsed during all observed events.
*/
unsigned long long getTotalTimeMicros() const { return _totalTimeMicros; }
/**
* Get the number of observed events.
*/
unsigned long long getNumEvents() const { return _numEvents; }
private:
unsigned long long _numEvents;
long long _totalTimeMicros;
};
/**
* RAII object for tracing an event.
*
* Construct an instance of this at the beginning of an event, and have it go out of scope at
* the end, to facilitate tracking events.
*
* This type can be used to separately count failures and successes by passing two event
* counters to the BenchRunEventCounter constructor, and calling "succeed()" on the object at
* the end of a successful event. If an exception is thrown, the fail counter will receive the
* event, and otherwise, the succes counter will.
*
* In all cases, the counter objects must outlive the trace object.
*/
class BenchRunEventTrace : private boost::noncopyable {
public:
explicit BenchRunEventTrace(BenchRunEventCounter *eventCounter) {
initialize(eventCounter, eventCounter, false);
}
BenchRunEventTrace(BenchRunEventCounter *successCounter,
BenchRunEventCounter *failCounter,
bool defaultToFailure=true) {
initialize(successCounter, failCounter, defaultToFailure);
}
~BenchRunEventTrace() {
(_succeeded ? _successCounter : _failCounter)->countOne(_timer.micros());
}
void succeed() { _succeeded = true; }
void fail() { _succeeded = false; }
private:
void initialize(BenchRunEventCounter *successCounter,
BenchRunEventCounter *failCounter,
bool defaultToFailure) {
_successCounter = successCounter;
_failCounter = failCounter;
_succeeded = !defaultToFailure;
}
Timer _timer;
BenchRunEventCounter *_successCounter;
BenchRunEventCounter *_failCounter;
bool _succeeded;
};
/**
* Statistics object representing the result of a bench run activity.
*/
class BenchRunStats : private boost::noncopyable {
public:
BenchRunStats();
~BenchRunStats();
void reset();
void updateFrom( const BenchRunStats &other );
bool error;
unsigned long long errCount;
BenchRunEventCounter findOneCounter;
BenchRunEventCounter updateCounter;
BenchRunEventCounter insertCounter;
BenchRunEventCounter deleteCounter;
BenchRunEventCounter queryCounter;
std::map opcounters;
std::vector trappedErrors;
};
/**
* State of a BenchRun activity.
*
* Logically, the states are "starting up", "running" and "finished."
*/
class BenchRunState : private boost::noncopyable {
public:
enum State { BRS_STARTING_UP, BRS_RUNNING, BRS_FINISHED };
explicit BenchRunState(unsigned numWorkers);
~BenchRunState();
//
// Functions called by the job-controlling thread, through an instance of BenchRunner.
//
/**
* Block until the current state is "awaitedState."
*
* massert() (uassert()?) if "awaitedState" is unreachable from
* the current state.
*/
void waitForState(State awaitedState);
/**
* Notify the worker threads to wrap up. Does not block.
*/
void tellWorkersToFinish();
/// Check that the current state is BRS_FINISHED.
void assertFinished();
//
// Functions called by the worker threads, through instances of BenchRunWorker.
//
/**
* Predicate that workers call to see if they should finish (as a result of a call
* to tellWorkersToFinish()).
*/
bool shouldWorkerFinish();
/**
* Called by each BenchRunWorker from within its thread context, immediately before it
* starts sending requests to the configured mongo instance.
*/
void onWorkerStarted();
/**
* Called by each BenchRunWorker from within its thread context, shortly after it finishes
* sending requests to the configured mongo instance.
*/
void onWorkerFinished();
private:
boost::mutex _mutex;
boost::condition _stateChangeCondition;
unsigned _numUnstartedWorkers;
unsigned _numActiveWorkers;
AtomicUInt32 _isShuttingDown;
};
/**
* A single worker in the bench run activity.
*
* Represents the behavior of one thread working in a bench run activity.
*/
class BenchRunWorker : private boost::noncopyable {
public:
/**
* Create a new worker, performing one thread's worth of the activity described in
* "config", and part of the larger activity with state "brState". Both "config"
* and "brState" must exist for the life of this object.
*
* "id" is a positive integer which should uniquely identify the worker.
*/
BenchRunWorker(size_t id, const BenchRunConfig *config,
BenchRunState *brState, int64_t randomSeed);
~BenchRunWorker();
/**
* Start performing the "work" behavior in a new thread.
*/
void start();
/**
* Get the run statistics for a worker.
*
* Should only be observed _after_ the worker has signaled its completion by calling
* onWorkerFinished() on the BenchRunState passed into its constructor.
*/
const BenchRunStats &stats() const { return _stats; }
private:
/// The main method of the worker, executed inside the thread launched by start().
void run();
/// The function that actually sets about generating the load described in "_config".
void generateLoadOnConnection( DBClientBase *conn );
/// Predicate, used to decide whether or not it's time to terminate the worker.
bool shouldStop() const;
size_t _id;
const BenchRunConfig *_config;
BenchRunState *_brState;
BenchRunStats _stats;
int64_t _randomSeed;
};
/**
* Object representing a "bench run" activity.
*/
class BenchRunner : private boost::noncopyable {
public:
/**
* Utility method to create a new bench runner from a BSONObj representation
* of a configuration.
*
* TODO: This is only really for the use of the javascript benchRun() methods,
* and should probably move out of the BenchRunner class.
*/
static BenchRunner* createWithConfig( const BSONObj &configArgs );
/**
* Look up a bench runner object by OID.
*
* TODO: Same todo as for "createWithConfig".
*/
static BenchRunner* get( OID oid );
/**
* Stop a running "runner", and return a BSON representation of its resultant
* BenchRunStats.
*
* TODO: Same as for "createWithConfig".
*/
static BSONObj finish( BenchRunner* runner );
/**
* Create a new bench runner, to perform the activity described by "*config."
*
* Takes ownership of "config", and will delete it.
*/
explicit BenchRunner( BenchRunConfig *config );
~BenchRunner();
/**
* Start the activity. Only call once per instance of BenchRunner.
*/
void start();
/**
* Stop the activity. Block until the activitiy has stopped.
*/
void stop();
/**
* Store the collected event data from a completed bench run activity into "stats."
*
* Illegal to call until after stop() returns.
*/
void populateStats(BenchRunStats *stats);
OID oid() const { return _oid; }
const BenchRunConfig &config() const { return *_config; } // TODO: Remove this function.
// JS bindings
static BSONObj benchFinish(const BSONObj& argsFake, void* data);
static BSONObj benchStart(const BSONObj& argsFake, void* data);
static BSONObj benchRunSync(const BSONObj& argsFake, void* data);
private:
// TODO: Same as for createWithConfig.
static boost::mutex _staticMutex;
static std::map< OID, BenchRunner* > _activeRuns;
OID _oid;
BenchRunState _brState;
Timer *_brTimer;
unsigned long long _microsElapsed;
boost::scoped_ptr _config;
std::vector _workers;
BSONObj before;
BSONObj after;
};
} // namespace mongo