/* Copyright 2009 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. */ /* How to build and run: scons mongoperf ./mongoperf -h */ // note: mongoperf is an internal mongodb utility // so we define the following macro #define MONGO_EXPOSE_MACROS 1 #include "mongo/platform/basic.h" #include #include #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/db/storage/mmap_v1/logfile.h" #include "mongo/db/storage/mmap_v1/mmap.h" #include "mongo/platform/atomic_word.h" #include "mongo/stdx/thread.h" #include "mongo/util/allocator.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/processinfo.h" #include "mongo/util/time_support.h" #include "mongo/util/timer.h" using namespace std; using namespace mongo; using namespace mongoutils::str; int dummy; unsigned recSizeKB; LogFile* lf = 0; MemoryMappedFile* mmfFile; char* mmf = 0; bo options; unsigned long long len; // file len const unsigned PG = 4096; unsigned nThreadsRunning = 0; // as this is incremented A LOT, at some point this becomes a bottleneck if very high ops/second (in // cache) things are happening. AtomicUInt32 iops; SimpleMutex m; int syncDelaySecs = 0; void syncThread() { while (1) { mongo::Timer t; mmfFile->flush(true); cout << " mmf sync took " << t.millis() << "ms" << endl; sleepsecs(syncDelaySecs); } } void stripTrailing(std::string& s, const char* chars) { std::string::iterator to = s.begin(); for (std::string::iterator i = s.begin(); i != s.end(); i++) { // During each iteration if i finds a non-"chars" character it writes it to the // position of t. So the part of the string left from the "to" iterator is already // "cleared" string. if (!contains(chars, *i)) { if (i != to) s.replace(to, to + 1, 1, *i); to++; } } s.erase(to, s.end()); } char* round(char* x) { size_t f = (size_t)x; char* p = (char*)((f + PG - 1) / PG * PG); return p; } struct Aligned { char x[8192]; char* addr() { return round(x); } }; unsigned long long rrand() { // RAND_MAX is very small on windows return (static_cast(rand()) << 15) ^ rand(); } void workerThread() { bool r = options["r"].trueValue(); bool w = options["w"].trueValue(); cout << "read:" << r << " write:" << w << endl; long long su = options["sleepMicros"].numberLong(); Aligned a; while (1) { unsigned long long rofs = (rrand() * PG) % len; unsigned long long wofs = (rrand() * PG) % len; const unsigned P = PG / 1024; if (mmf) { if (r) { for (unsigned p = P; p <= recSizeKB; p += P) { if (rofs < len) dummy += mmf[rofs]; rofs += PG; } iops.fetchAndAdd(1); } if (w) { for (unsigned p = P; p <= recSizeKB; p += P) { if (wofs < len) mmf[wofs] = 3; wofs += PG; } iops.fetchAndAdd(1); } } else { if (r) { lf->readAt(rofs, a.addr(), recSizeKB * 1024); iops.fetchAndAdd(1); } if (w) { lf->writeAt(wofs, a.addr(), recSizeKB * 1024); iops.fetchAndAdd(1); } } long long micros = su / nThreadsRunning; if (micros) { sleepmicros(micros); } } } void go() { verify(options["r"].trueValue() || options["w"].trueValue()); recSizeKB = options["recSizeKB"].numberInt(); if (recSizeKB == 0) recSizeKB = 4; verify(recSizeKB <= 64000 && recSizeKB > 0); MemoryMappedFile f; cout << "creating test file size:"; len = options["fileSizeMB"].numberLong(); if (len == 0) len = 1; cout << len << "MB ..." << endl; if (0 && len > 2000 && !options["mmf"].trueValue()) { // todo make tests use 64 bit offsets in their i/o -- i.e. adjust LogFile::writeAt and such cout << "\nsizes > 2GB not yet supported with mmf:false" << endl; return; } len *= 1024 * 1024; const char* fname = "./mongoperf__testfile__tmp"; try { boost::filesystem::remove(fname); } catch (...) { cout << "error deleting file " << fname << endl; return; } lf = new LogFile(fname, true); // needs to be big as we are using synchronousAppend. if we used a regular MongoFile it // wouldn't have to be const unsigned sz = 1024 * 1024 * 32; char* buf = (char*)mongoMalloc(sz + 4096); const char* p = round(buf); for (unsigned long long i = 0; i < len; i += sz) { lf->synchronousAppend(p, sz); if (i % (1024ULL * 1024 * 1024) == 0 && i) { cout << i / (1024ULL * 1024 * 1024) << "GB..." << endl; } } BSONObj& o = options; if (o["mmf"].trueValue()) { delete lf; lf = 0; mmfFile = new MemoryMappedFile(); mmf = (char*)mmfFile->map(fname); verify(mmf); syncDelaySecs = options["syncDelay"].numberInt(); if (syncDelaySecs) { stdx::thread t(syncThread); } } cout << "testing..." << endl; cout << "options:" << o.toString() << endl; unsigned wthr = 1; if (!o["nThreads"].eoo()) { wthr = (unsigned)o["nThreads"].Int(); } cout << "wthr " << wthr << endl; if (wthr < 1) { cout << "bad threads field value" << endl; return; } unsigned i = 0; unsigned d = 1; unsigned& nthr = nThreadsRunning; while (1) { if (i++ % 8 == 0) { if (nthr < wthr) { while (nthr < wthr && nthr < d) { nthr++; stdx::thread w(workerThread); } cout << "new thread, total running : " << nthr << endl; d *= 2; } } sleepsecs(1); unsigned long long w = iops.loadRelaxed(); iops.store(0); w /= 1; // 1 secs cout << w << " ops/sec "; if (mmf == 0) // only writing 4 bytes with mmf so we don't say this cout << (w * PG / 1024 / 1024) << " MB/sec"; cout << endl; } } int main(int argc, char* argv[]) { try { cout << "mongoperf" << endl; if (argc > 1) { cout << "\n" "usage:\n" "\n" " mongoperf < myjsonconfigfile\n" "\n" " {\n" " nThreads:, // number of threads (default 1)\n" " fileSizeMB:, // test file size (default 1MB)\n" " sleepMicros:, // pause for sleepMicros/nThreads between each operation " "(default 0)\n" " mmf:, // if true do i/o's via memory mapped files (default " "false)\n" " r:, // do reads (default false)\n" " w:, // do writes (default false)\n" " recSizeKB:, // size of each write (default 4KB)\n" " syncDelay: // secs between fsyncs, like --syncdelay in mongod. " "(default 0/never)\n" " }\n" "\n" "mongoperf is a performance testing tool. the initial tests are of disk subsystem " "performance; \n" " tests of mongos and mongod will be added later.\n" "most fields are optional.\n" "non-mmf io is direct io (no caching). use a large file size to test making the " "heads\n" " move significantly and to avoid i/o coalescing\n" "mmf io uses caching (the file system cache).\n" "\n" << endl; return EXIT_SUCCESS; } cout << "use -h for help" << endl; char input[1024]; memset(input, 0, sizeof(input)); cin.read(input, 1000); if (*input == 0) { cout << "error no options found on stdin for mongoperf" << endl; return EXIT_FAILURE; } string s = input; stripTrailing(s, " \n\r\0x1a"); try { options = fromjson(s); } catch (...) { cout << "couldn't parse json options. input was:\n|" << s << "|" << endl; return EXIT_FAILURE; } cout << "parsed options:\n" << options.toString() << endl; ProcessInfo::initializeSystemInfo(); go(); } catch (DBException& e) { cout << "caught DBException " << e.toString() << endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }