diff options
Diffstat (limited to 'src/mongo/db/stats')
-rw-r--r-- | src/mongo/db/stats/counters.cpp | 207 | ||||
-rw-r--r-- | src/mongo/db/stats/counters.h | 159 | ||||
-rw-r--r-- | src/mongo/db/stats/fine_clock.h | 67 | ||||
-rw-r--r-- | src/mongo/db/stats/service_stats.cpp | 68 | ||||
-rw-r--r-- | src/mongo/db/stats/service_stats.h | 66 | ||||
-rw-r--r-- | src/mongo/db/stats/snapshots.cpp | 227 | ||||
-rw-r--r-- | src/mongo/db/stats/snapshots.h | 114 | ||||
-rw-r--r-- | src/mongo/db/stats/top.cpp | 183 | ||||
-rw-r--r-- | src/mongo/db/stats/top.h | 247 |
9 files changed, 1338 insertions, 0 deletions
diff --git a/src/mongo/db/stats/counters.cpp b/src/mongo/db/stats/counters.cpp new file mode 100644 index 00000000000..889e8a86c4c --- /dev/null +++ b/src/mongo/db/stats/counters.cpp @@ -0,0 +1,207 @@ +// counters.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 "../jsobj.h" +#include "counters.h" + +namespace mongo { + + OpCounters::OpCounters() { + int zero = 0; + + BSONObjBuilder b; + b.append( "insert" , zero ); + b.append( "query" , zero ); + b.append( "update" , zero ); + b.append( "delete" , zero ); + b.append( "getmore" , zero ); + b.append( "command" , zero ); + _obj = b.obj(); + + _insert = (AtomicUInt*)_obj["insert"].value(); + _query = (AtomicUInt*)_obj["query"].value(); + _update = (AtomicUInt*)_obj["update"].value(); + _delete = (AtomicUInt*)_obj["delete"].value(); + _getmore = (AtomicUInt*)_obj["getmore"].value(); + _command = (AtomicUInt*)_obj["command"].value(); + } + + void OpCounters::gotOp( int op , bool isCommand ) { + switch ( op ) { + case dbInsert: /*gotInsert();*/ break; // need to handle multi-insert + case dbQuery: + if ( isCommand ) + gotCommand(); + else + gotQuery(); + break; + + case dbUpdate: gotUpdate(); break; + case dbDelete: gotDelete(); break; + case dbGetMore: gotGetMore(); break; + case dbKillCursors: + case opReply: + case dbMsg: + break; + default: log() << "OpCounters::gotOp unknown op: " << op << endl; + } + } + + BSONObj& OpCounters::getObj() { + const unsigned MAX = 1 << 30; + RARELY { + bool wrap = + _insert->get() > MAX || + _query->get() > MAX || + _update->get() > MAX || + _delete->get() > MAX || + _getmore->get() > MAX || + _command->get() > MAX; + + if ( wrap ) { + _insert->zero(); + _query->zero(); + _update->zero(); + _delete->zero(); + _getmore->zero(); + _command->zero(); + } + + } + return _obj; + } + + IndexCounters::IndexCounters() { + _memSupported = _pi.blockCheckSupported(); + + _btreeMemHits = 0; + _btreeMemMisses = 0; + _btreeAccesses = 0; + + + _maxAllowed = ( numeric_limits< long long >::max() ) / 2; + _resets = 0; + + _sampling = 0; + _samplingrate = 100; + } + + void IndexCounters::append( BSONObjBuilder& b ) { + if ( ! _memSupported ) { + b.append( "note" , "not supported on this platform" ); + return; + } + + BSONObjBuilder bb( b.subobjStart( "btree" ) ); + bb.appendNumber( "accesses" , _btreeAccesses ); + bb.appendNumber( "hits" , _btreeMemHits ); + bb.appendNumber( "misses" , _btreeMemMisses ); + + bb.append( "resets" , _resets ); + + bb.append( "missRatio" , (_btreeAccesses ? (_btreeMemMisses / (double)_btreeAccesses) : 0) ); + + bb.done(); + + if ( _btreeAccesses > _maxAllowed ) { + _btreeAccesses = 0; + _btreeMemMisses = 0; + _btreeMemHits = 0; + _resets++; + } + } + + FlushCounters::FlushCounters() + : _total_time(0) + , _flushes(0) + , _last() + {} + + void FlushCounters::flushed(int ms) { + _flushes++; + _total_time += ms; + _last_time = ms; + _last = jsTime(); + } + + void FlushCounters::append( BSONObjBuilder& b ) { + b.appendNumber( "flushes" , _flushes ); + b.appendNumber( "total_ms" , _total_time ); + b.appendNumber( "average_ms" , (_flushes ? (_total_time / double(_flushes)) : 0.0) ); + b.appendNumber( "last_ms" , _last_time ); + b.append("last_finished", _last); + } + + + void GenericCounter::hit( const string& name , int count ) { + scoped_lock lk( _mutex ); + _counts[name]++; + } + + BSONObj GenericCounter::getObj() { + BSONObjBuilder b(128); + { + mongo::mutex::scoped_lock lk( _mutex ); + for ( map<string,long long>::iterator i=_counts.begin(); i!=_counts.end(); i++ ) { + b.appendNumber( i->first , i->second ); + } + } + return b.obj(); + } + + + void NetworkCounter::hit( long long bytesIn , long long bytesOut ) { + const long long MAX = 1ULL << 60; + + // don't care about the race as its just a counter + bool overflow = _bytesIn > MAX || _bytesOut > MAX; + + if ( overflow ) { + _lock.lock(); + _overflows++; + _bytesIn = bytesIn; + _bytesOut = bytesOut; + _requests = 1; + _lock.unlock(); + } + else { + _lock.lock(); + _bytesIn += bytesIn; + _bytesOut += bytesOut; + _requests++; + _lock.unlock(); + } + } + + void NetworkCounter::append( BSONObjBuilder& b ) { + _lock.lock(); + b.appendNumber( "bytesIn" , _bytesIn ); + b.appendNumber( "bytesOut" , _bytesOut ); + b.appendNumber( "numRequests" , _requests ); + _lock.unlock(); + } + + + OpCounters globalOpCounters; + OpCounters replOpCounters; + IndexCounters globalIndexCounters; + FlushCounters globalFlushCounters; + NetworkCounter networkCounter; + +} diff --git a/src/mongo/db/stats/counters.h b/src/mongo/db/stats/counters.h new file mode 100644 index 00000000000..0cb29aa49aa --- /dev/null +++ b/src/mongo/db/stats/counters.h @@ -0,0 +1,159 @@ +// counters.h +/* + * 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/>. + */ + +#pragma once + +#include "../../pch.h" +#include "../jsobj.h" +#include "../../util/net/message.h" +#include "../../util/processinfo.h" +#include "../../util/concurrency/spin_lock.h" + +namespace mongo { + + /** + * for storing operation counters + * note: not thread safe. ok with that for speed + */ + class OpCounters { + public: + + OpCounters(); + + AtomicUInt * getInsert() { return _insert; } + AtomicUInt * getQuery() { return _query; } + AtomicUInt * getUpdate() { return _update; } + AtomicUInt * getDelete() { return _delete; } + AtomicUInt * getGetMore() { return _getmore; } + AtomicUInt * getCommand() { return _command; } + + void incInsertInWriteLock(int n) { _insert->x += n; } + void gotInsert() { _insert[0]++; } + void gotQuery() { _query[0]++; } + void gotUpdate() { _update[0]++; } + void gotDelete() { _delete[0]++; } + void gotGetMore() { _getmore[0]++; } + void gotCommand() { _command[0]++; } + + void gotOp( int op , bool isCommand ); + + BSONObj& getObj(); + + private: + BSONObj _obj; + + // todo: there will be a lot of cache line contention on these. need to do something + // else eventually. + AtomicUInt * _insert; + AtomicUInt * _query; + AtomicUInt * _update; + AtomicUInt * _delete; + AtomicUInt * _getmore; + AtomicUInt * _command; + }; + + extern OpCounters globalOpCounters; + extern OpCounters replOpCounters; + + + class IndexCounters { + public: + IndexCounters(); + + // used without a mutex intentionally (can race) + void btree( char * node ) { + if ( ! _memSupported ) + return; + if ( _sampling++ % _samplingrate ) + return; + btree( _pi.blockInMemory( node ) ); + } + + void btree( bool memHit ) { + if ( memHit ) + _btreeMemHits++; + else + _btreeMemMisses++; + _btreeAccesses++; + } + void btreeHit() { _btreeMemHits++; _btreeAccesses++; } + void btreeMiss() { _btreeMemMisses++; _btreeAccesses++; } + + void append( BSONObjBuilder& b ); + + private: + ProcessInfo _pi; + bool _memSupported; + + int _sampling; + int _samplingrate; + + int _resets; + long long _maxAllowed; + + long long _btreeMemMisses; + long long _btreeMemHits; + long long _btreeAccesses; + }; + + extern IndexCounters globalIndexCounters; + + class FlushCounters { + public: + FlushCounters(); + + void flushed(int ms); + + void append( BSONObjBuilder& b ); + + private: + long long _total_time; + long long _flushes; + int _last_time; + Date_t _last; + }; + + extern FlushCounters globalFlushCounters; + + + class GenericCounter { + public: + GenericCounter() : _mutex("GenericCounter") { } + void hit( const string& name , int count=0 ); + BSONObj getObj(); + private: + map<string,long long> _counts; // TODO: replace with thread safe map + mongo::mutex _mutex; + }; + + class NetworkCounter { + public: + NetworkCounter() : _bytesIn(0), _bytesOut(0), _requests(0), _overflows(0) {} + void hit( long long bytesIn , long long bytesOut ); + void append( BSONObjBuilder& b ); + private: + long long _bytesIn; + long long _bytesOut; + long long _requests; + + long long _overflows; + + SpinLock _lock; + }; + + extern NetworkCounter networkCounter; +} diff --git a/src/mongo/db/stats/fine_clock.h b/src/mongo/db/stats/fine_clock.h new file mode 100644 index 00000000000..02600e718c4 --- /dev/null +++ b/src/mongo/db/stats/fine_clock.h @@ -0,0 +1,67 @@ +// fine_clock.h + +/** +* 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/>. +*/ + +#ifndef DB_STATS_FINE_CLOCK_HEADER +#define DB_STATS_FINE_CLOCK_HEADER + +#include <time.h> // struct timespec + +namespace mongo { + + /** + * This is a nano-second precision clock. We're skipping the + * harware TSC in favor of clock_gettime() which in some systems + * does not involve a trip to the OS (VDSO). + * + * We're exporting a type WallTime that is and should remain + * opaque. The business of getting accurate time is still ongoing + * and we may change the internal representation of this class. + * (http://lwn.net/Articles/388188/) + * + * Really, you shouldn't be using this class in hot code paths for + * platforms you're not sure whether the overhead is low. + */ + class FineClock { + public: + + typedef timespec WallTime; + + static WallTime now() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts; + } + + static uint64_t diffInNanos( WallTime end, WallTime start ) { + uint64_t diff; + if ( end.tv_nsec < start.tv_nsec ) { + diff = 1000000000 * ( end.tv_sec - start.tv_sec - 1); + diff += 1000000000 + end.tv_nsec - start.tv_nsec; + } + else { + diff = 1000000000 * ( end.tv_sec - start.tv_sec ); + diff += end.tv_nsec - start.tv_nsec; + } + return diff; + } + + }; +} + +#endif // DB_STATS_FINE_CLOCK_HEADER + diff --git a/src/mongo/db/stats/service_stats.cpp b/src/mongo/db/stats/service_stats.cpp new file mode 100644 index 00000000000..d69147fe969 --- /dev/null +++ b/src/mongo/db/stats/service_stats.cpp @@ -0,0 +1,68 @@ +// service_stats.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 <sstream> + +#include "../../util/histogram.h" +#include "service_stats.h" + +namespace mongo { + + using std::ostringstream; + + ServiceStats::ServiceStats() { + // Time histogram covers up to 128msec in exponential intervals + // starting at 125usec. + Histogram::Options timeOpts; + timeOpts.numBuckets = 12; + timeOpts.bucketSize = 125; + timeOpts.exponential = true; + _timeHistogram = new Histogram( timeOpts ); + + // Space histogram covers up to 1MB in exponentialintervals starting + // at 1K. + Histogram::Options spaceOpts; + spaceOpts.numBuckets = 12; + spaceOpts.bucketSize = 1024; + spaceOpts.exponential = true; + _spaceHistogram = new Histogram( spaceOpts ); + } + + ServiceStats::~ServiceStats() { + delete _timeHistogram; + delete _spaceHistogram; + } + + void ServiceStats::logResponse( uint64_t duration, uint64_t bytes ) { + _spinLock.lock(); + _timeHistogram->insert( duration / 1000 /* in usecs */ ); + _spaceHistogram->insert( bytes ); + _spinLock.unlock(); + } + + string ServiceStats::toHTML() const { + ostringstream res ; + res << "Cumulative wire stats\n" + << "Response times\n" << _timeHistogram->toHTML() + << "Response sizes\n" << _spaceHistogram->toHTML() + << '\n'; + + return res.str(); + } + +} // mongo diff --git a/src/mongo/db/stats/service_stats.h b/src/mongo/db/stats/service_stats.h new file mode 100644 index 00000000000..5b0e75fdcb9 --- /dev/null +++ b/src/mongo/db/stats/service_stats.h @@ -0,0 +1,66 @@ +// service_stats.h + +/** +* 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/>. +*/ + +#ifndef DB_STATS_SERVICE_STATS_HEADER +#define DB_STATS_SERVICE_STATS_HEADER + +#include <string> + +#include "../../util/concurrency/spin_lock.h" + +namespace mongo { + + using std::string; + + class Histogram; + + /** + * ServiceStats keeps track of the time a request/response message + * took inside a service as well as the size of the response + * generated. + */ + class ServiceStats { + public: + ServiceStats(); + ~ServiceStats(); + + /** + * Record the 'duration' in microseconds a request/response + * message took and the size in bytes of the generated + * response. + */ + void logResponse( uint64_t duration, uint64_t bytes ); + + /** + * Render the histogram as string that can be used inside an + * HTML doc. + */ + string toHTML() const; + + private: + SpinLock _spinLock; // protects state below + Histogram* _timeHistogram; + Histogram* _spaceHistogram; + + ServiceStats( const ServiceStats& ); + ServiceStats operator=( const ServiceStats& ); + }; + +} // namespace mongo + +#endif // DB_STATS_SERVICE_STATS_HEADER diff --git a/src/mongo/db/stats/snapshots.cpp b/src/mongo/db/stats/snapshots.cpp new file mode 100644 index 00000000000..900cc4ff1ad --- /dev/null +++ b/src/mongo/db/stats/snapshots.cpp @@ -0,0 +1,227 @@ +// snapshots.cpp + +/** +* 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 "snapshots.h" +#include "../client.h" +#include "../clientcursor.h" +#include "../dbwebserver.h" +#include "../../util/mongoutils/html.h" + +/** + handles snapshotting performance metrics and other such things + */ +namespace mongo { + + void SnapshotData::takeSnapshot() { + _created = curTimeMicros64(); + _globalUsage = Top::global.getGlobalData(); +// _totalWriteLockedTime = d.dbMutex.info().getTimeLocked(); + Top::global.cloneMap(_usage); + } + + SnapshotDelta::SnapshotDelta( const SnapshotData& older , const SnapshotData& newer ) + : _older( older ) , _newer( newer ) { + assert( _newer._created > _older._created ); + _elapsed = _newer._created - _older._created; + } + + Top::CollectionData SnapshotDelta::globalUsageDiff() { + return Top::CollectionData( _older._globalUsage , _newer._globalUsage ); + } + Top::UsageMap SnapshotDelta::collectionUsageDiff() { + assert( _newer._created > _older._created ); + Top::UsageMap u; + + for ( Top::UsageMap::const_iterator i=_newer._usage.begin(); i != _newer._usage.end(); i++ ) { + Top::UsageMap::const_iterator j = _older._usage.find(i->first); + if (j != _older._usage.end()) + u[i->first] = Top::CollectionData( j->second , i->second ); + else + u[i->first] = i->second; + } + return u; + } + + Snapshots::Snapshots(int n) + : _lock("Snapshots"), _n(n) + , _snapshots(new SnapshotData[n]) + , _loc(0) + , _stored(0) + {} + + const SnapshotData* Snapshots::takeSnapshot() { + scoped_lock lk(_lock); + _loc = ( _loc + 1 ) % _n; + _snapshots[_loc].takeSnapshot(); + if ( _stored < _n ) + _stored++; + return &_snapshots[_loc]; + } + + auto_ptr<SnapshotDelta> Snapshots::computeDelta( int numBack ) { + scoped_lock lk(_lock); + auto_ptr<SnapshotDelta> p; + if ( numBack < numDeltas() ) + p.reset( new SnapshotDelta( getPrev(numBack+1) , getPrev(numBack) ) ); + return p; + } + + const SnapshotData& Snapshots::getPrev( int numBack ) { + int x = _loc - numBack; + if ( x < 0 ) + x += _n; + return _snapshots[x]; + } + + void Snapshots::outputLockInfoHTML( stringstream& ss ) { + scoped_lock lk(_lock); + ss << "\n<div>"; + for ( int i=0; i<numDeltas(); i++ ) { + SnapshotDelta d( getPrev(i+1) , getPrev(i) ); + unsigned e = (unsigned) d.elapsed() / 1000; + ss << (unsigned)(100*d.percentWriteLocked()); + if( e < 3900 || e > 4100 ) + ss << '(' << e / 1000.0 << "s)"; + ss << ' '; + } + ss << "</div>\n"; + } + + void SnapshotThread::run() { + Client::initThread("snapshotthread"); + Client& client = cc(); + + long long numLoops = 0; + + const SnapshotData* prev = 0; + + while ( ! inShutdown() ) { + try { + const SnapshotData* s = statsSnapshots.takeSnapshot(); + + if ( prev && cmdLine.cpu ) { + unsigned long long elapsed = s->_created - prev->_created; + SnapshotDelta d( *prev , *s ); + log() << "cpu: elapsed:" << (elapsed/1000) <<" writelock: " << (int)(100*d.percentWriteLocked()) << "%" << endl; + } + + prev = s; + } + catch ( std::exception& e ) { + log() << "ERROR in SnapshotThread: " << e.what() << endl; + } + + numLoops++; + sleepsecs(4); + } + + client.shutdown(); + } + + using namespace mongoutils::html; + + class WriteLockStatus : public WebStatusPlugin { + public: + WriteLockStatus() : WebStatusPlugin( "write lock" , 51 , "% time in write lock, by 4 sec periods" ) {} + virtual void init() {} + + virtual void run( stringstream& ss ) { + statsSnapshots.outputLockInfoHTML( ss ); + + ss << "<a " + "href=\"http://www.mongodb.org/pages/viewpage.action?pageId=7209296\" " + "title=\"snapshot: was the db in the write lock when this page was generated?\">"; + ss << "write locked now:</a> " << (d.dbMutex.info().isLocked() ? "true" : "false") << "\n"; + } + + } writeLockStatus; + + class DBTopStatus : public WebStatusPlugin { + public: + DBTopStatus() : WebStatusPlugin( "dbtop" , 50 , "(occurrences|percent of elapsed)" ) {} + + void display( stringstream& ss , double elapsed , const Top::UsageData& usage ) { + ss << "<td>"; + ss << usage.count; + ss << "</td><td>"; + double per = 100 * ((double)usage.time)/elapsed; + if( per == (int) per ) + ss << (int) per; + else + ss << setprecision(1) << fixed << per; + ss << '%'; + ss << "</td>"; + } + + void display( stringstream& ss , double elapsed , const string& ns , const Top::CollectionData& data ) { + if ( ns != "TOTAL" && data.total.count == 0 ) + return; + ss << "<tr><th>" << ns << "</th>"; + + display( ss , elapsed , data.total ); + + display( ss , elapsed , data.readLock ); + display( ss , elapsed , data.writeLock ); + + display( ss , elapsed , data.queries ); + display( ss , elapsed , data.getmore ); + display( ss , elapsed , data.insert ); + display( ss , elapsed , data.update ); + display( ss , elapsed , data.remove ); + + ss << "</tr>\n"; + } + + void run( stringstream& ss ) { + auto_ptr<SnapshotDelta> delta = statsSnapshots.computeDelta(); + if ( ! delta.get() ) + return; + + ss << "<table border=1 cellpadding=2 cellspacing=0>"; + ss << "<tr align='left'><th>"; + ss << a("http://www.mongodb.org/display/DOCS/Developer+FAQ#DeveloperFAQ-What%27sa%22namespace%22%3F", "namespace") << + "NS</a></th>" + "<th colspan=2>total</th>" + "<th colspan=2>Reads</th>" + "<th colspan=2>Writes</th>" + "<th colspan=2>Queries</th>" + "<th colspan=2>GetMores</th>" + "<th colspan=2>Inserts</th>" + "<th colspan=2>Updates</th>" + "<th colspan=2>Removes</th>"; + ss << "</tr>\n"; + + display( ss , (double) delta->elapsed() , "TOTAL" , delta->globalUsageDiff() ); + + Top::UsageMap usage = delta->collectionUsageDiff(); + for ( Top::UsageMap::iterator i=usage.begin(); i != usage.end(); i++ ) { + display( ss , (double) delta->elapsed() , i->first , i->second ); + } + + ss << "</table>"; + + } + + virtual void init() {} + } dbtopStatus; + + Snapshots statsSnapshots; + SnapshotThread snapshotThread; + +} diff --git a/src/mongo/db/stats/snapshots.h b/src/mongo/db/stats/snapshots.h new file mode 100644 index 00000000000..d9b8e5eb901 --- /dev/null +++ b/src/mongo/db/stats/snapshots.h @@ -0,0 +1,114 @@ +// snapshots.h + +/** +* 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/>. +*/ + +#pragma once +#include "../../pch.h" +#include "../jsobj.h" +#include "top.h" +#include "../../util/background.h" + +/** + handles snapshotting performance metrics and other such things + */ +namespace mongo { + + class SnapshotThread; + + /** + * stores a point in time snapshot + * i.e. all counters at a given time + */ + class SnapshotData { + void takeSnapshot(); + + unsigned long long _created; + Top::CollectionData _globalUsage; + unsigned long long _totalWriteLockedTime; // micros of total time locked + Top::UsageMap _usage; + + friend class SnapshotThread; + friend class SnapshotDelta; + friend class Snapshots; + }; + + /** + * contains performance information for a time period + */ + class SnapshotDelta { + public: + SnapshotDelta( const SnapshotData& older , const SnapshotData& newer ); + + unsigned long long start() const { + return _older._created; + } + + unsigned long long elapsed() const { + return _elapsed; + } + + unsigned long long timeInWriteLock() const { + return _newer._totalWriteLockedTime - _older._totalWriteLockedTime; + } + double percentWriteLocked() const { + double e = (double) elapsed(); + double w = (double) timeInWriteLock(); + return w/e; + } + + Top::CollectionData globalUsageDiff(); + Top::UsageMap collectionUsageDiff(); + + private: + const SnapshotData& _older; + const SnapshotData& _newer; + + unsigned long long _elapsed; + }; + + class Snapshots { + public: + Snapshots(int n=100); + + const SnapshotData* takeSnapshot(); + + int numDeltas() const { return _stored-1; } + + const SnapshotData& getPrev( int numBack = 0 ); + auto_ptr<SnapshotDelta> computeDelta( int numBack = 0 ); + + + void outputLockInfoHTML( stringstream& ss ); + private: + mongo::mutex _lock; + int _n; + boost::scoped_array<SnapshotData> _snapshots; + int _loc; + int _stored; + }; + + class SnapshotThread : public BackgroundJob { + public: + virtual string name() const { return "snapshot"; } + void run(); + }; + + extern Snapshots statsSnapshots; + extern SnapshotThread snapshotThread; + + +} diff --git a/src/mongo/db/stats/top.cpp b/src/mongo/db/stats/top.cpp new file mode 100644 index 00000000000..f5b6ee42f1c --- /dev/null +++ b/src/mongo/db/stats/top.cpp @@ -0,0 +1,183 @@ +// top.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 "top.h" +#include "../../util/net/message.h" +#include "../commands.h" + +namespace mongo { + + Top::UsageData::UsageData( const UsageData& older , const UsageData& newer ) { + // this won't be 100% accurate on rollovers and drop(), but at least it won't be negative + time = (newer.time >= older.time) ? (newer.time - older.time) : newer.time; + count = (newer.count >= older.count) ? (newer.count - older.count) : newer.count; + } + + Top::CollectionData::CollectionData( const CollectionData& older , const CollectionData& newer ) + : total( older.total , newer.total ) , + readLock( older.readLock , newer.readLock ) , + writeLock( older.writeLock , newer.writeLock ) , + queries( older.queries , newer.queries ) , + getmore( older.getmore , newer.getmore ) , + insert( older.insert , newer.insert ) , + update( older.update , newer.update ) , + remove( older.remove , newer.remove ), + commands( older.commands , newer.commands ) { + + } + + void Top::record( const string& ns , int op , int lockType , long long micros , bool command ) { + if ( ns[0] == '?' ) + return; + + //cout << "record: " << ns << "\t" << op << "\t" << command << endl; + scoped_lock lk(_lock); + + if ( ( command || op == dbQuery ) && ns == _lastDropped ) { + _lastDropped = ""; + return; + } + + CollectionData& coll = _usage[ns]; + _record( coll , op , lockType , micros , command ); + _record( _global , op , lockType , micros , command ); + } + + void Top::_record( CollectionData& c , int op , int lockType , long long micros , bool command ) { + c.total.inc( micros ); + + if ( lockType > 0 ) + c.writeLock.inc( micros ); + else if ( lockType < 0 ) + c.readLock.inc( micros ); + + switch ( op ) { + case 0: + // use 0 for unknown, non-specific + break; + case dbUpdate: + c.update.inc( micros ); + break; + case dbInsert: + c.insert.inc( micros ); + break; + case dbQuery: + if ( command ) + c.commands.inc( micros ); + else + c.queries.inc( micros ); + break; + case dbGetMore: + c.getmore.inc( micros ); + break; + case dbDelete: + c.remove.inc( micros ); + break; + case dbKillCursors: + break; + case opReply: + case dbMsg: + log() << "unexpected op in Top::record: " << op << endl; + break; + default: + log() << "unknown op in Top::record: " << op << endl; + } + + } + + void Top::collectionDropped( const string& ns ) { + //cout << "collectionDropped: " << ns << endl; + scoped_lock lk(_lock); + _usage.erase(ns); + _lastDropped = ns; + } + + void Top::cloneMap(Top::UsageMap& out) const { + scoped_lock lk(_lock); + out = _usage; + } + + void Top::append( BSONObjBuilder& b ) { + scoped_lock lk( _lock ); + _appendToUsageMap( b , _usage ); + } + + void Top::_appendToUsageMap( BSONObjBuilder& b , const UsageMap& map ) const { + for ( UsageMap::const_iterator i=map.begin(); i!=map.end(); i++ ) { + BSONObjBuilder bb( b.subobjStart( i->first ) ); + + const CollectionData& coll = i->second; + + _appendStatsEntry( b , "total" , coll.total ); + + _appendStatsEntry( b , "readLock" , coll.readLock ); + _appendStatsEntry( b , "writeLock" , coll.writeLock ); + + _appendStatsEntry( b , "queries" , coll.queries ); + _appendStatsEntry( b , "getmore" , coll.getmore ); + _appendStatsEntry( b , "insert" , coll.insert ); + _appendStatsEntry( b , "update" , coll.update ); + _appendStatsEntry( b , "remove" , coll.remove ); + _appendStatsEntry( b , "commands" , coll.commands ); + + bb.done(); + } + } + + void Top::_appendStatsEntry( BSONObjBuilder& b , const char * statsName , const UsageData& map ) const { + BSONObjBuilder bb( b.subobjStart( statsName ) ); + bb.appendNumber( "time" , map.time ); + bb.appendNumber( "count" , map.count ); + bb.done(); + } + + class TopCmd : public Command { + public: + TopCmd() : Command( "top", true ) {} + + virtual bool slaveOk() const { return true; } + virtual bool adminOnly() const { return true; } + virtual LockType locktype() const { return READ; } + virtual void help( stringstream& help ) const { help << "usage by collection, in micros "; } + + virtual bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + { + BSONObjBuilder b( result.subobjStart( "totals" ) ); + b.append( "note" , "all times in microseconds" ); + Top::global.append( b ); + b.done(); + } + return true; + } + + } topCmd; + + Top Top::global; + + TopOld::T TopOld::_snapshotStart = TopOld::currentTime(); + TopOld::D TopOld::_snapshotDuration; + TopOld::UsageMap TopOld::_totalUsage; + TopOld::UsageMap TopOld::_snapshotA; + TopOld::UsageMap TopOld::_snapshotB; + TopOld::UsageMap &TopOld::_snapshot = TopOld::_snapshotA; + TopOld::UsageMap &TopOld::_nextSnapshot = TopOld::_snapshotB; + mongo::mutex TopOld::topMutex("topMutex"); + + +} diff --git a/src/mongo/db/stats/top.h b/src/mongo/db/stats/top.h new file mode 100644 index 00000000000..9645ed1a3a6 --- /dev/null +++ b/src/mongo/db/stats/top.h @@ -0,0 +1,247 @@ +// top.h : DB usage monitor. + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <boost/date_time/posix_time/posix_time.hpp> +#undef assert +#define assert MONGO_assert + +namespace mongo { + + /** + * tracks usage by collection + */ + class Top { + + public: + Top() : _lock("Top") { } + + struct UsageData { + UsageData() : time(0) , count(0) {} + UsageData( const UsageData& older , const UsageData& newer ); + long long time; + long long count; + + void inc( long long micros ) { + count++; + time += micros; + } + }; + + struct CollectionData { + /** + * constructs a diff + */ + CollectionData() {} + CollectionData( const CollectionData& older , const CollectionData& newer ); + + UsageData total; + + UsageData readLock; + UsageData writeLock; + + UsageData queries; + UsageData getmore; + UsageData insert; + UsageData update; + UsageData remove; + UsageData commands; + }; + + typedef map<string,CollectionData> UsageMap; + + public: + void record( const string& ns , int op , int lockType , long long micros , bool command ); + void append( BSONObjBuilder& b ); + void cloneMap(UsageMap& out) const; + CollectionData getGlobalData() const { return _global; } + void collectionDropped( const string& ns ); + + public: // static stuff + static Top global; + + private: + void _appendToUsageMap( BSONObjBuilder& b , const UsageMap& map ) const; + void _appendStatsEntry( BSONObjBuilder& b , const char * statsName , const UsageData& map ) const; + void _record( CollectionData& c , int op , int lockType , long long micros , bool command ); + + mutable mongo::mutex _lock; + CollectionData _global; + UsageMap _usage; + string _lastDropped; + }; + + /* Records per namespace utilization of the mongod process. + No two functions of this class may be called concurrently. + */ + class TopOld { + typedef boost::posix_time::ptime T; + typedef boost::posix_time::time_duration D; + typedef boost::tuple< D, int, int, int > UsageData; + public: + TopOld() : _read(false), _write(false) { } + + /* these are used to record activity: */ + + void clientStart( const char *client ) { + clientStop(); + _currentStart = currentTime(); + _current = client; + } + + /* indicate current request is a read operation. */ + void setRead() { _read = true; } + + void setWrite() { _write = true; } + + void clientStop() { + if ( _currentStart == T() ) + return; + D d = currentTime() - _currentStart; + + { + scoped_lock L(topMutex); + recordUsage( _current, d ); + } + + _currentStart = T(); + _read = false; + _write = false; + } + + /* these are used to fetch the stats: */ + + struct Usage { + string ns; + D time; + double pct; + int reads, writes, calls; + }; + + static void usage( vector< Usage > &res ) { + scoped_lock L(topMutex); + + // Populate parent namespaces + UsageMap snapshot; + UsageMap totalUsage; + fillParentNamespaces( snapshot, _snapshot ); + fillParentNamespaces( totalUsage, _totalUsage ); + + multimap< D, string, more > sorted; + for( UsageMap::iterator i = snapshot.begin(); i != snapshot.end(); ++i ) + sorted.insert( make_pair( i->second.get<0>(), i->first ) ); + for( multimap< D, string, more >::iterator i = sorted.begin(); i != sorted.end(); ++i ) { + if ( trivialNs( i->second.c_str() ) ) + continue; + Usage u; + u.ns = i->second; + u.time = totalUsage[ u.ns ].get<0>(); + u.pct = _snapshotDuration != D() ? 100.0 * i->first.ticks() / _snapshotDuration.ticks() : 0; + u.reads = snapshot[ u.ns ].get<1>(); + u.writes = snapshot[ u.ns ].get<2>(); + u.calls = snapshot[ u.ns ].get<3>(); + res.push_back( u ); + } + for( UsageMap::iterator i = totalUsage.begin(); i != totalUsage.end(); ++i ) { + if ( snapshot.count( i->first ) != 0 || trivialNs( i->first.c_str() ) ) + continue; + Usage u; + u.ns = i->first; + u.time = i->second.get<0>(); + u.pct = 0; + u.reads = 0; + u.writes = 0; + u.calls = 0; + res.push_back( u ); + } + } + + static void completeSnapshot() { + scoped_lock L(topMutex); + + if ( &_snapshot == &_snapshotA ) { + _snapshot = _snapshotB; + _nextSnapshot = _snapshotA; + } + else { + _snapshot = _snapshotA; + _nextSnapshot = _snapshotB; + } + _snapshotDuration = currentTime() - _snapshotStart; + _snapshotStart = currentTime(); + _nextSnapshot.clear(); + } + + private: + static mongo::mutex topMutex; + static bool trivialNs( const char *ns ) { + const char *ret = strrchr( ns, '.' ); + return ret && ret[ 1 ] == '\0'; + } + typedef map<string,UsageData> UsageMap; // duration, # reads, # writes, # total calls + static T currentTime() { + return boost::posix_time::microsec_clock::universal_time(); + } + void recordUsage( const string &client, D duration ) { + recordUsageForMap( _totalUsage, client, duration ); + recordUsageForMap( _nextSnapshot, client, duration ); + } + void recordUsageForMap( UsageMap &map, const string &client, D duration ) { + UsageData& g = map[client]; + g.get< 0 >() += duration; + if ( _read && !_write ) + g.get< 1 >()++; + else if ( !_read && _write ) + g.get< 2 >()++; + g.get< 3 >()++; + } + static void fillParentNamespaces( UsageMap &to, const UsageMap &from ) { + for( UsageMap::const_iterator i = from.begin(); i != from.end(); ++i ) { + string current = i->first; + size_t dot = current.rfind( "." ); + if ( dot == string::npos || dot != current.length() - 1 ) { + inc( to[ current ], i->second ); + } + while( dot != string::npos ) { + current = current.substr( 0, dot ); + inc( to[ current ], i->second ); + dot = current.rfind( "." ); + } + } + } + static void inc( UsageData &to, const UsageData &from ) { + to.get<0>() += from.get<0>(); + to.get<1>() += from.get<1>(); + to.get<2>() += from.get<2>(); + to.get<3>() += from.get<3>(); + } + struct more { bool operator()( const D &a, const D &b ) { return a > b; } }; + string _current; + T _currentStart; + static T _snapshotStart; + static D _snapshotDuration; + static UsageMap _totalUsage; + static UsageMap _snapshotA; + static UsageMap _snapshotB; + static UsageMap &_snapshot; + static UsageMap &_nextSnapshot; + bool _read; + bool _write; + }; + +} // namespace mongo |