Files
mongo/dbtests/perftests.cpp
2011-12-02 15:43:19 -05:00

984 lines
30 KiB
C++

/** @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.
*/
/**
* 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 <http://www.gnu.org/licenses/>.
*/
#include "pch.h"
#include "../db/ops/query.h"
#include "../db/db.h"
#include "../db/instance.h"
#include "../db/json.h"
#include "../db/lasterror.h"
#include "../db/ops/update.h"
#include "../db/taskqueue.h"
#include "../util/timer.h"
#include "dbtests.h"
#include "../db/dur_stats.h"
#include "../util/checksum.h"
#include "../util/version.h"
#include "../db/key.h"
#include "../util/compress.h"
using namespace bson;
namespace mongo {
namespace regression {
extern unsigned perfHist;
}
}
namespace PerfTests {
const bool profiling = false;
typedef DBDirectClient DBClientType;
//typedef DBClientConnection DBClientType;
class ClientBase {
public:
// NOTE: Not bothering to backup the old error record.
ClientBase() {
//_client.connect("localhost");
mongo::lastError.reset( new LastError() );
}
virtual ~ClientBase() {
//mongo::lastError.release();
}
protected:
static void insert( const char *ns, BSONObj o ) {
_client.insert( ns, o );
}
static void update( const char *ns, BSONObj q, BSONObj o, bool upsert = 0 ) {
_client.update( ns, Query( q ), o, upsert );
}
static bool error() {
return !_client.getPrevError().getField( "err" ).isNull();
}
DBClientBase &client() const { return _client; }
private:
static DBClientType _client;
};
DBClientType ClientBase::_client;
// todo: use a couple threads. not a very good test yet.
class TaskQueueTest {
static int tot;
struct V {
int val;
static void go(const V &v) { tot += v.val; }
};
public:
void run() {
tot = 0;
TaskQueue<V> d;
int x = 0;
for( int i = 0; i < 100; i++ ) {
if( i % 30 == 0 )
d.invoke();
x += i;
writelock lk;
V v;
v.val = i;
d.defer(v);
}
d.invoke();
assert( x == tot );
}
};
int TaskQueueTest::tot;
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() { }
virtual void timed() = 0;
// optional 2nd test phase to be timed separately
// return name of it
virtual string timed2(DBClientBase&) { return ""; }
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 ? 60000 : 5000; }
/* override if your test output doesn't need that */
virtual bool showDurStats() { return true; }
static boost::shared_ptr<DBClientConnection> conn;
static unsigned once;
public:
/* if you want recording of the timings, place the password for the perf database
in ./../settings.py:
pstatspassword="<pwd>"
*/
void connect() {
if( once )
return;
++once;
// no writing to perf db if _DEBUG
DEV return;
const char *fn = "../../settings.py";
if( !exists(fn) ) {
if( exists("settings.py") )
fn = "settings.py";
else {
cout << "no ../../settings.py or ./settings.py file found. will not write perf stats to pstats db." << endl;
cout << "it is recommended this be enabled even on dev boxes" << endl;
return;
}
}
try {
if( conn == 0 ) {
MemoryMappedFile f;
const char *p = (const char *) f.mapWithOptions(fn, MongoFile::READONLY);
string pwd;
{
const char *q = str::after(p, "pstatspassword=\"");
if( *q == 0 ) {
cout << "info perftests.cpp: no pstatspassword= in settings.py" << endl;
return;
}
else {
pwd = str::before(q, '\"');
}
}
boost::shared_ptr<DBClientConnection> c(new DBClientConnection(false, 0, 60));
string err;
if( c->connect("perfdb.10gen.cc", err) ) {
if( !c->auth("perf", "perf", pwd, err) ) {
cout << "info: authentication with stats db failed: " << err << endl;
assert(false);
}
conn = c;
}
else {
cout << err << " (to log perfstats)" << endl;
}
}
}
catch(...) { }
}
virtual unsigned batchSize() { return 50; }
void say(unsigned long long n, int ms, string s) {
unsigned long long rps = n*1000/ms;
cout << "stats " << setw(33) << left << s << ' ' << right << setw(9) << rps << ' ' << right << setw(5) << ms << "ms ";
if( showDurStats() )
cout << dur::stats.curr->_asCSV();
cout << endl;
connect();
if( conn && !conn->isFailed() ) {
const char *ns = "perf.pstats";
if( perfHist ) {
static bool needver = true;
try {
// try to report rps from last time */
Query q;
{
BSONObjBuilder b;
b.append("host",getHostName()).append("test",s).append("dur",cmdLine.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<BSONObj> v;
conn->findN(v, ns, q, perfHist, 0, &fields);
for( vector<BSONObj>::iterator i = v.begin(); i != v.end(); i++ ) {
BSONObj o = *i;
double lastrps = o["rps"].Number();
if( lastrps ) {
cout << "stats " << setw(33) << right << "new/old:" << ' ' << setw(9);
cout << fixed << setprecision(2) << rps / lastrps;
if( needver ) {
cout << " " << o.getFieldDotted("info.git").toString();
}
cout << '\n';
}
}
} catch(...) { }
cout.flush();
needver = false;
}
{
bob b;
b.append("host", getHostName());
b.appendTimeT("when", time(0));
b.append("test", s);
b.append("rps", (int) rps);
b.append("millis", ms);
b.appendBool("dur", cmdLine.dur);
if( showDurStats() && cmdLine.dur )
b.append("durStats", dur::stats.curr->_asObj());
{
bob inf;
inf.append("version", versionString);
if( sizeof(int*) == 4 ) inf.append("bits", 32);
DEV inf.append("DEBUG", true);
#if defined(_WIN32)
inf.append("os", "win");
#endif
inf.append("git", gitVersion());
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;
}
}
}
}
virtual bool testThreaded() { return false; }
unsigned long long n;
void run() {
_ns = string("perftest.") + name();
client().dropCollection(ns());
prep();
int hlm = howLongMillis();
DEV {
// don't run very long with _DEBUG - not very meaningful anyway on that build
hlm = min(hlm, 500);
}
dur::stats._intervalMicros = 0; // no auto rotate
dur::stats.curr->reset();
mongo::Timer t;
n = 0;
const unsigned Batch = batchSize();
if( hlm == 0 ) {
// means just do once
timed();
}
else {
do {
unsigned i;
for( i = 0; i < Batch; i++ )
timed();
n += i;
} while( t.micros() < (unsigned) hlm * 1000 );
}
client().getLastError(); // block until all ops are finished
int ms = t.millis();
say(n, ms, name());
post();
string test2name = timed2(client());
{
if( test2name.size() != 0 ) {
dur::stats.curr->reset();
mongo::Timer t;
unsigned long long n = 0;
while( 1 ) {
unsigned i;
for( i = 0; i < Batch; i++ )
timed2(client());
n += i;
if( t.millis() > hlm )
break;
}
int ms = t.millis();
say(n, ms, test2name);
}
}
if( testThreaded() ) {
cout << "testThreaded" << endl;
mongo::Timer t;
launchThreads(8);
//cout << "threaded done " << t.millis() << "ms" << endl;
//cout << n * 1000 / t.millis() << " per second" << endl;
say(n, t.millis(), test2name+"-threaded");
}
}
void thread() {
DBClientType c;
Client::initThreadIfNotAlready("perftestthr");
for( unsigned long long i = 0; i < n/8; i++ ) {
timed2(c);
}
cc().shutdown();
}
void launchThreads(int remaining) {
if (!remaining)
return;
boost::thread athread(boost::bind(&B::thread, this));
launchThreads(remaining - 1);
athread.join();
}
};
boost::shared_ptr<DBClientConnection> B::conn;
unsigned B::once;
unsigned dontOptimizeOutHopefully;
class NonDurTest : public B {
public:
virtual int howLongMillis() { return 3000; }
virtual bool showDurStats() { return false; }
};
class BSONIter : public NonDurTest {
public:
int n;
bo b, sub;
string name() { return "BSONIter"; }
BSONIter() {
n = 0;
bo sub = bob().appendTimeT("t", time(0)).appendBool("abool", true).appendBinData("somebin", 3, BinDataGeneral, "abc").appendNull("anullone").obj();
b = BSON( "_id" << OID() << "x" << 3 << "yaaaaaa" << 3.00009 << "zz" << 1 << "q" << false << "obj" << sub << "zzzzzzz" << "a string a string" );
}
void timed() {
for( bo::iterator i = b.begin(); i.more(); )
if( i.next().fieldName() )
n++;
for( bo::iterator i = sub.begin(); i.more(); )
if( i.next().fieldName() )
n++;
}
};
class BSONGetFields1 : public NonDurTest {
public:
int n;
bo b, sub;
string name() { return "BSONGetFields1By1"; }
BSONGetFields1() {
n = 0;
bo sub = bob().appendTimeT("t", time(0)).appendBool("abool", true).appendBinData("somebin", 3, BinDataGeneral, "abc").appendNull("anullone").obj();
b = BSON( "_id" << OID() << "x" << 3 << "yaaaaaa" << 3.00009 << "zz" << 1 << "q" << false << "obj" << sub << "zzzzzzz" << "a string a string" );
}
void timed() {
if( b["x"].eoo() )
n++;
if( b["q"].eoo() )
n++;
if( b["zzz"].eoo() )
n++;
}
};
class BSONGetFields2 : public BSONGetFields1 {
public:
string name() { return "BSONGetFields"; }
void timed() {
static const char *names[] = { "x", "q", "zzz" };
BSONElement elements[3];
b.getFields(3, names, elements);
if( elements[0].eoo() )
n++;
if( elements[1].eoo() )
n++;
if( elements[2].eoo() )
n++;
}
};
class KeyTest : public B {
public:
KeyV1Owned a,b,c;
string name() { return "Key-woequal"; }
virtual int howLongMillis() { return 3000; }
KeyTest() :
a(BSON("a"<<1<<"b"<<3.0<<"c"<<"qqq")),
b(BSON("a"<<1<<"b"<<3.0<<"c"<<"qqq")),
c(BSON("a"<<1<<"b"<<3.0<<"c"<<"qqqb"))
{}
virtual bool showDurStats() { return false; }
void timed() {
assert( a.woEqual(b) );
assert( !a.woEqual(c) );
}
};
unsigned long long aaa;
class Timer : public B {
public:
string name() { return "Timer"; }
virtual int howLongMillis() { return 1000; }
virtual bool showDurStats() { return false; }
void timed() {
mongo::Timer t;
aaa += t.millis();
}
};
class Sleep0Ms : public B {
public:
string name() { return "Sleep0Ms"; }
virtual int howLongMillis() { return 400; }
virtual bool showDurStats() { return false; }
void timed() {
sleepmillis(0);
mongo::Timer t;
aaa++;
}
};
RWLock lk("testrw");
SimpleMutex m("simptst");
mongo::mutex mtest("mtest");
SpinLock s;
class mutexspeed : public B {
public:
string name() { return "mutex"; }
virtual int howLongMillis() { return 500; }
virtual bool showDurStats() { return false; }
void timed() {
mongo::mutex::scoped_lock lk(mtest);
}
};
class simplemutexspeed : public B {
public:
string name() { return "simplemutex"; }
virtual int howLongMillis() { return 500; }
virtual bool showDurStats() { return false; }
void timed() {
SimpleMutex::scoped_lock lk(m);
}
};
class spinlockspeed : public B {
public:
string name() { return "spinlock"; }
virtual int howLongMillis() { return 500; }
virtual bool showDurStats() { return false; }
void timed() {
mongo::scoped_spinlock lk(s);
}
};
int cas;
class casspeed : public B {
public:
string name() { return "compareandswap"; }
virtual int howLongMillis() { return 500; }
virtual bool showDurStats() { return false; }
void timed() {
#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_4
#define RUNCOMPARESWAP 1
__sync_bool_compare_and_swap(&cas, 0, 0);
#endif
}
};
class rlock : public B {
public:
string name() { return "rlock"; }
virtual int howLongMillis() { return 500; }
virtual bool showDurStats() { return false; }
void timed() {
lk.lock_shared();
lk.unlock_shared();
}
};
class wlock : public B {
public:
string name() { return "wlock"; }
virtual int howLongMillis() { return 500; }
virtual bool showDurStats() { return false; }
void timed() {
lk.lock();
lk.unlock();
}
};
#if 0
class ulock : public B {
public:
string name() { return "ulock"; }
virtual int howLongMillis() { return 500; }
virtual bool showDurStats() { return false; }
void timed() {
lk.lockAsUpgradable();
lk.unlockFromUpgradable();
}
};
#endif
class CTM : public B {
public:
CTM() : last(0), delts(0), n(0) { }
string name() { return "curTimeMillis64"; }
virtual int howLongMillis() { return 500; }
virtual bool showDurStats() { return false; }
unsigned long long last;
unsigned long long delts;
unsigned n;
void timed() {
unsigned long long x = curTimeMillis64();
aaa += x;
if( last ) {
unsigned long long delt = x-last;
if( delt ) {
delts += delt;
n++;
}
}
last = x;
}
void post() {
// we need to know if timing is highly ungranular - that could be relevant in some places
if( n )
cout << " avg timer granularity: " << ((double)delts)/n << "ms " << endl;
}
};
class Bldr : public B {
public:
int n;
string name() { return "BufBuilder"; }
Bldr() {
}
virtual int howLongMillis() { return 3000; }
virtual bool showDurStats() { return false; }
void timed() {
BufBuilder b;
b.appendNum(3);
b.appendUChar(' ');
b.appendStr("abcd");
n += b.len();
}
};
class StkBldr : public B {
public:
virtual int howLongMillis() { return 3000; }
int n;
string name() { return "StackBufBuilder"; }
virtual bool showDurStats() { return false; }
void timed() {
StackBufBuilder b;
b.appendNum(3);
b.appendUChar(' ');
b.appendStr("abcd");
n += b.len();
}
};
// if a test is this fast, it was optimized out
class Dummy : public B {
public:
Dummy() { }
virtual int howLongMillis() { return 3000; }
string name() { return "dummy"; }
void timed() {
dontOptimizeOutHopefully++;
}
virtual bool showDurStats() { return false; }
};
// test thread local speed
#if defined(_WIN32)
__declspec( thread ) int x;
class TLS2 : public B {
public:
virtual int howLongMillis() { return 3000; }
string name() { return "thread-local-storage2"; }
void timed() {
if( x )
dontOptimizeOutHopefully++;
}
virtual bool showDurStats() { return false; }
};
#endif
// test thread local speed
class TLS : public B {
public:
virtual int howLongMillis() { return 3000; }
string name() { return "thread-local-storage"; }
void timed() {
if( &cc() )
dontOptimizeOutHopefully++;
}
virtual bool showDurStats() { return false; }
};
class New128 : public B {
public:
virtual int howLongMillis() { return 2000; }
string name() { return "new128"; }
void timed() {
char *p = new char[128];
if( dontOptimizeOutHopefully++ > 0 )
delete p;
}
virtual bool showDurStats() { return false; }
};
class New8 : public B {
public:
virtual int howLongMillis() { return 2000; }
string name() { return "new8"; }
void timed() {
char *p = new char[8];
if( dontOptimizeOutHopefully++ > 0 )
delete p;
}
virtual bool showDurStats() { return false; }
};
class Compress : public B {
public:
const unsigned sz;
void *p;
Compress() : sz(1024*1024*100+3) { }
virtual unsigned batchSize() { return 1; }
string name() { return "compress"; }
virtual bool showDurStats() { return false; }
virtual int howLongMillis() { return 4000; }
void prep() {
p = malloc(sz);
// this isn't a fair test as it is mostly rands but we just want a rough perf check
static int last;
for (unsigned i = 0; i<sz; i++) {
int r = rand();
if( (r & 0x300) == 0x300 )
r = last;
((char*)p)[i] = r;
last = r;
}
}
size_t last;
string res;
void timed() {
mongo::Timer t;
string out;
size_t len = compress((const char *) p, sz, &out);
bool ok = uncompress(out.c_str(), out.size(), &res);
ASSERT(ok);
static unsigned once;
if( once++ == 0 )
cout << "compress round trip " << sz/(1024.0*1024) / (t.millis()/1000.0) << "MB/sec\n";
//cout << len / (1024.0/1024) << " compressed" << endl;
(void)len; //fix unused error while above line is commented out
}
void post() {
ASSERT( memcmp(res.c_str(), p, sz) == 0 );
free(p);
}
};
// test speed of checksum method
class ChecksumTest : public B {
public:
const unsigned sz;
ChecksumTest() : sz(1024*1024*100+3) { }
string name() { return "checksum"; }
virtual int howLongMillis() { return 2000; }
virtual bool showDurStats() { return false; }
virtual unsigned batchSize() { return 1; }
void *p;
void prep() {
{
// the checksum code assumes 'standard' rollover on addition overflows. let's check that:
unsigned long long x = 0xffffffffffffffffULL;
ASSERT( x+2 == 1 );
}
p = malloc(sz);
for (unsigned i = 0; i<sz; i++)
((char*)p)[i] = rand();
}
Checksum last;
void timed() {
static int i;
Checksum c;
c.gen(p, sz);
if( i == 0 )
last = c;
else if( i == 1 ) {
ASSERT( c == last );
}
}
void post() {
{
mongo::Checksum c;
c.gen(p, sz-1);
ASSERT( c != last );
((char *&)p)[0]++; // check same data, different order, doesn't give same checksum
((char *&)p)[1]--;
c.gen(p, sz);
ASSERT( c != last );
((char *&)p)[1]++; // check same data, different order, doesn't give same checksum (different longwords case)
((char *&)p)[8]--;
c.gen(p, sz);
ASSERT( c != last );
}
free(p);
}
};
class InsertDup : public B {
const BSONObj o;
public:
InsertDup() : o( BSON("_id" << 1) ) { } // dup keys
string name() {
return "insert-duplicate-_ids";
}
void prep() {
client().insert( ns(), o );
}
void timed() {
client().insert( ns(), o );
}
void post() {
assert( client().count(ns()) == 1 );
}
};
class Insert1 : public B {
const BSONObj x;
OID oid;
BSONObj query;
public:
virtual int howLongMillis() { return 30000; }
Insert1() : x( BSON("x" << 99) ) {
oid.init();
query = BSON("_id" << oid);
i = 0;
}
string name() { return "insert-simple"; }
unsigned i;
void timed() {
BSONObj o = BSON( "_id" << i++ << "x" << 99 );
client().insert( ns(), o );
//client().insert( ns(), x );
}
virtual bool testThreaded() { return true; }
string timed2(DBClientBase& c) {
Query q = QUERY( "_id" << (unsigned) Security::getNonce() % i );
c.findOne(ns(), q);
//client().findOne(ns(), query);
return "findOne_by_id";
}
void post() {
#if !defined(_DEBUG)
assert( client().count(ns()) > 50 );
#endif
}
};
class InsertBig : public B {
BSONObj x;
virtual int howLongMillis() {
if( sizeof(void*) == 4 )
return 1000; // could exceed mmapping if run too long, as this function adds a lot fasta
return 5000;
}
public:
InsertBig() {
char buf[200000];
BSONObjBuilder b;
b.append("x", 99);
b.appendBinData("bin", 200000, (BinDataType) 129, buf);
x = b.obj();
}
string name() { return "insert-big"; }
void timed() {
client().insert( ns(), x );
}
};
class InsertRandom : public B {
public:
virtual int howLongMillis() { return profiling ? 30000 : 5000; }
string name() { return "random-inserts"; }
void prep() {
client().insert( ns(), BSONObj() );
client().ensureIndex(ns(), BSON("x"<<1));
}
void timed() {
int x = rand();
BSONObj y = BSON("x" << x << "y" << rand() << "z" << 33);
client().insert(ns(), y);
}
};
/** upserts about 32k records and then keeps updating them
2 indexes
*/
class Update1 : public B {
public:
static int rand() {
return std::rand() & 0x7fff;
}
virtual string name() { return "random-upserts"; }
void prep() {
client().insert( ns(), BSONObj() );
client().ensureIndex(ns(), BSON("x"<<1));
}
void timed() {
int x = rand();
BSONObj q = BSON("x" << x);
BSONObj y = BSON("x" << x << "y" << rand() << "z" << 33);
client().update(ns(), q, y, /*upsert*/true);
}
virtual string timed2(DBClientBase& c) {
static BSONObj I = BSON( "$inc" << BSON( "y" << 1 ) );
// test some $inc's
int x = rand();
BSONObj q = BSON("x" << x);
c.update(ns(), q, I);
return name()+"-inc";
}
};
template <typename T>
class MoreIndexes : public T {
public:
string name() { return T::name() + "-more-indexes"; }
void prep() {
T::prep();
this->client().ensureIndex(this->ns(), BSON("y"<<1));
this->client().ensureIndex(this->ns(), BSON("z"<<1));
}
};
void t() {
for( int i = 0; i < 20; i++ ) {
sleepmillis(21);
string fn = "/tmp/t1";
MongoMMF f;
unsigned long long len = 1 * 1024 * 1024;
assert( f.create(fn, len, /*sequential*/rand()%2==0) );
{
char *p = (char *) f.getView();
assert(p);
// write something to the private view as a test
strcpy(p, "hello");
}
if( cmdLine.dur ) {
char *w = (char *) f.view_write();
strcpy(w + 6, "world");
}
MongoFileFinder ff;
ASSERT( ff.findByPath(fn) );
}
}
class All : public Suite {
public:
All() : Suite( "perf" ) { }
Result * run( const string& filter ) {
boost::thread a(t);
Result * res = Suite::run(filter);
a.join();
return res;
}
void setupTests() {
cout
<< "stats test rps------ time-- "
<< dur::stats.curr->_CSVHeader() << endl;
if( profiling ) {
add< New8 >();
add< New128 >();
}
else {
add< Dummy >();
add< ChecksumTest >();
add< Compress >();
add< TLS >();
#if defined(_WIN32)
add< TLS2 >();
#endif
add< New8 >();
add< New128 >();
add< Timer >();
add< Sleep0Ms >();
add< rlock >();
add< wlock >();
//add< ulock >();
add< mutexspeed >();
add< simplemutexspeed >();
add< spinlockspeed >();
#ifdef RUNCOMPARESWAP
add< casspeed >();
#endif
add< CTM >();
add< KeyTest >();
add< Bldr >();
add< StkBldr >();
add< BSONIter >();
add< BSONGetFields1 >();
add< BSONGetFields2 >();
add< TaskQueueTest >();
add< InsertDup >();
add< Insert1 >();
add< InsertRandom >();
add< MoreIndexes<InsertRandom> >();
add< Update1 >();
add< MoreIndexes<Update1> >();
add< InsertBig >();
}
}
} myall;
}