/**
* Copyright (C) 2008 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.
*/
/** @file perftests.cpp.cpp : unit tests relating to performance
The idea herein is tests that run fast and can be part of the normal CI suite. So no
tests herein that take a long time to run. Obviously we need those too, but they will be
separate.
These tests use DBDirectClient; they are a bit white-boxish.
*/
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
#include "mongo/platform/basic.h"
#include
#include
#include
#include
#include
#include
#include "mongo/config.h"
#include "mongo/db/bson/dotted_path_support.h"
#include "mongo/db/client.h"
#include "mongo/db/db.h"
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/lasterror.h"
#include "mongo/db/storage/mmap_v1/dur_stats.h"
#include "mongo/db/storage/mmap_v1/mmap.h"
#include "mongo/db/storage/storage_options.h"
#include "mongo/dbtests/dbtests.h"
#include "mongo/dbtests/framework_options.h"
#include "mongo/stdx/condition_variable.h"
#include "mongo/stdx/thread.h"
#include "mongo/util/log.h"
#include "mongo/util/timer.h"
#include "mongo/util/version.h"
namespace PerfTests {
using std::cout;
using std::endl;
using std::fixed;
using std::left;
using std::min;
using std::right;
using std::setprecision;
using std::setw;
using std::string;
using std::vector;
namespace dps = ::mongo::dotted_path_support;
const bool profiling = false;
class ClientBase {
public:
ClientBase() : _client(&_opCtx) {
mongo::LastError::get(_opCtx.getClient()).reset();
}
virtual ~ClientBase() {
mongo::LastError::get(_opCtx.getClient()).reset();
}
protected:
void insert(const char* ns, BSONObj o) {
_client.insert(ns, o);
}
void update(const char* ns, BSONObj q, BSONObj o, bool upsert = 0) {
_client.update(ns, Query(q), o, upsert);
}
bool error() {
return !_client.getPrevError().getField("err").isNull();
}
DBClientBase* client() {
return &_client;
}
OperationContext* opCtx() {
return &_opCtx;
}
private:
const ServiceContext::UniqueOperationContext _txnPtr = cc().makeOperationContext();
OperationContext& _opCtx = *_txnPtr;
DBDirectClient _client;
};
static std::shared_ptr conn;
static string _perfhostname;
class B : public ClientBase {
string _ns;
protected:
const char* ns() {
return _ns.c_str();
}
// anything you want to do before being timed
virtual void prep() {}
// anything you want to do before threaded test
virtual void prepThreaded() {}
virtual void timed() = 0;
// optional 2nd test phase to be timed separately. You must provide it with a unique
// name in order for it to run by overloading 'name2'.
virtual void timed2(DBClientBase*) {}
// return name of second test.
virtual string name2() {
return name();
}
virtual void post() {}
virtual string name() = 0;
// how long to run test. 0 is a sentinel which means just run the timed() method once and time
// it.
virtual int howLongMillis() {
return profiling ? 30000 : 5000;
}
/* override if your test output doesn't need that */
virtual bool showDurStats() {
return true;
}
public:
virtual unsigned batchSize() {
return 50;
}
void say(unsigned long long n, long long us, string s) {
unsigned long long rps = (n * 1000 * 1000) / (us > 0 ? us : 1);
cout << "stats " << setw(42) << left << s << ' ' << right << setw(9) << rps << ' ' << right
<< setw(5) << us / 1000 << "ms ";
if (showDurStats()) {
cout << dur::stats.curr()->_asCSV();
}
cout << endl;
if (conn && !conn->isFailed()) {
const char* ns = "perf.pstats";
if (frameworkGlobalParams.perfHist) {
static bool needver = true;
try {
// try to report rps from last time */
Query q;
{
BSONObjBuilder b;
b.append("host", _perfhostname);
b.append("test", s);
b.append("dur", storageGlobalParams.dur);
DEV {
b.append("info.DEBUG", true);
}
else b.appendNull("info.DEBUG");
if (sizeof(int*) == 4)
b.append("info.bits", 32);
else
b.appendNull("info.bits");
q = Query(b.obj()).sort("when", -1);
}
BSONObj fields = BSON("rps" << 1 << "info" << 1);
vector v;
conn->findN(v, ns, q, frameworkGlobalParams.perfHist, 0, &fields);
for (vector::iterator i = v.begin(); i != v.end(); i++) {
BSONObj o = *i;
double lastrps = o["rps"].Number();
if (0 && lastrps) {
cout << "stats " << setw(42) << right << "new/old:" << ' ' << setw(9);
cout << fixed << setprecision(2) << rps / lastrps;
if (needver) {
cout << " "
<< dps::extractElementAtPath(o, "info.git").toString();
}
cout << '\n';
}
}
} catch (...) {
}
cout.flush();
needver = false;
}
{
bob b;
b.append("host", _perfhostname);
b.appendTimeT("when", time(0));
b.append("test", s);
b.append("rps", (int)rps);
b.append("millis", us / 1000);
b.appendBool("dur", storageGlobalParams.dur);
if (showDurStats() && storageGlobalParams.dur) {
b.append("durStats", dur::stats.asObj());
}
{
auto&& vii = VersionInfoInterface::instance();
bob inf;
inf.append("version", vii.version());
if (sizeof(int*) == 4)
inf.append("bits", 32);
DEV inf.append("DEBUG", true);
#if defined(_WIN32)
inf.append("os", "win");
#endif
inf.append("git", vii.gitVersion());
#ifdef MONGO_CONFIG_SSL
inf.append("OpenSSL", vii.openSSLVersion());
#endif
inf.append("boost", BOOST_VERSION);
b.append("info", inf.obj());
}
BSONObj o = b.obj();
// cout << "inserting " << o.toString() << endl;
try {
conn->insert(ns, o);
} catch (std::exception& e) {
warning() << "couldn't save perf results: " << e.what() << endl;
}
}
}
}
/** if true runs timed2() again with several threads (8 at time of this writing).
*/
virtual bool testThreaded() {
return false;
}
int howLong() {
int hlm = howLongMillis();
DEV {
// don't run very long with in debug mode - not very meaningful anyway on that build
hlm = min(hlm, 500);
}
return hlm;
}
void run() {
unsigned long long n = 0;
_ns = string("perftest.") + name();
client()->dropCollection(ns());
prep();
int hlm = howLong();
mongo::Timer t;
n = 0;
const unsigned int Batch = batchSize();
if (hlm == 0) {
// means just do once
timed();
} else {
do {
unsigned int i;
for (i = 0; i < Batch; i++)
timed();
n += i;
} while (t.micros() < (hlm * 1000));
}
client()->getLastError(); // block until all ops are finished
say(n, t.micros(), name());
post();
string test2name = name2();
{
if (test2name != name()) {
dur::stats.curr()->reset();
mongo::Timer t;
unsigned long long n = 0;
while (1) {
unsigned int i;
for (i = 0; i < Batch; i++)
timed2(client());
n += i;
if (t.millis() > hlm)
break;
}
say(n, t.micros(), test2name);
}
}
if (testThreaded()) {
const int nThreads = 8;
// cout << "testThreaded nThreads:" << nThreads << endl;
mongo::Timer t;
const unsigned long long result = launchThreads(nThreads);
say(result / nThreads, t.micros(), test2name + "-threaded");
}
}
bool stop;
void thread(unsigned long long* counter) {
#if defined(_WIN32)
static int z;
srand(++z ^ (unsigned)time(0));
#endif
Client::initThreadIfNotAlready("perftestthr");
const ServiceContext::UniqueOperationContext opCtxPtr = cc().makeOperationContext();
OperationContext& opCtx = *opCtxPtr;
DBDirectClient c(&opCtx);
const unsigned int Batch = batchSize();
prepThreaded();
while (1) {
unsigned int i = 0;
for (i = 0; i < Batch; i++)
timed2(&c);
*counter += i;
if (stop)
break;
}
}
unsigned long long launchThreads(int remaining) {
stop = false;
if (!remaining) {
int hlm = howLong();
sleepmillis(hlm);
stop = true;
return 0;
}
unsigned long long counter = 0;
stdx::thread athread(stdx::bind(&B::thread, this, &counter));
unsigned long long child = launchThreads(remaining - 1);
athread.join();
unsigned long long accum = child + counter;
return accum;
}
};
SimpleMutex m;
boost::mutex mboost; // NOLINT
boost::timed_mutex mboost_timed; // NOLINT
std::mutex mstd; // NOLINT
std::timed_mutex mstd_timed; // NOLINT
SpinLock s;
stdx::condition_variable c;
class boostmutexspeed : public B {
public:
string name() {
return "boost::mutex";
}
virtual int howLongMillis() {
return 500;
}
virtual bool showDurStats() {
return false;
}
void timed() {
boost::lock_guard lk(mboost); // NOLINT
}
};
class boosttimed_mutexspeed : public B {
public:
string name() {
return "boost::timed_mutex";
}
virtual int howLongMillis() {
return 500;
}
virtual bool showDurStats() {
return false;
}
void timed() {
boost::lock_guard lk(mboost_timed); // NOLINT
}
};
class simplemutexspeed : public B {
public:
string name() {
return "simplemutex";
}
virtual int howLongMillis() {
return 500;
}
virtual bool showDurStats() {
return false;
}
void timed() {
stdx::lock_guard lk(m);
}
};
class stdmutexspeed : public B {
public:
string name() {
return "std::mutex";
}
virtual int howLongMillis() {
return 500;
}
virtual bool showDurStats() {
return false;
}
void timed() {
std::lock_guard lk(mstd); // NOLINT
}
};
class stdtimed_mutexspeed : public B {
public:
string name() {
return "std::timed_mutex";
}
virtual int howLongMillis() {
return 500;
}
virtual bool showDurStats() {
return false;
}
void timed() {
std::lock_guard lk(mstd_timed); // NOLINT
}
};
class All : public Suite {
public:
All() : Suite("perf") {}
void setupTests() {
cout << "stats test rps------ time-- "
<< dur::stats.curr()->_CSVHeader() << endl;
add();
add();
add();
add();
add();
}
} myall;
} // namespace PerfTests