diff options
Diffstat (limited to 'db/dbwebserver.cpp')
-rw-r--r-- | db/dbwebserver.cpp | 305 |
1 files changed, 202 insertions, 103 deletions
diff --git a/db/dbwebserver.cpp b/db/dbwebserver.cpp index 0e1483c..75d3a92 100644 --- a/db/dbwebserver.cpp +++ b/db/dbwebserver.cpp @@ -27,6 +27,9 @@ #include "replset.h" #include "instance.h" #include "security.h" +#include "stats/snapshots.h" +#include "background.h" +#include "commands.h" #include <pcrecpp.h> #include <boost/date_time/posix_time/posix_time.hpp> @@ -61,48 +64,6 @@ namespace mongo { } unsigned long long start, timeLocked; }; - Timing tlast; - const int NStats = 32; - string lockStats[NStats]; - unsigned q = 0; - - void statsThread() { - /*cout << "TEMP disabled statsthread" << endl; - if( 1 ) - return;*/ - Client::initThread("stats"); - unsigned long long timeLastPass = 0; - while ( 1 ) { - { - /* todo: do we even need readlock here? if so for what? */ - readlock lk(""); - Top::completeSnapshot(); - q = (q+1)%NStats; - Timing timing; - dbMutex.info().getTimingInfo(timing.start, timing.timeLocked); - unsigned long long now = curTimeMicros64(); - if ( timeLastPass ) { - unsigned long long dt = now - timeLastPass; - unsigned long long dlocked = timing.timeLocked - tlast.timeLocked; - { - stringstream ss; - ss << dt / 1000 << '\t'; - ss << dlocked / 1000 << '\t'; - if ( dt ) - ss << (dlocked*100)/dt << '%'; - string s = ss.str(); - if ( cmdLine.cpu ) - log() << "cpu: " << s << endl; - lockStats[q] = s; - ClientCursor::idleTimeReport( (unsigned) ((dt - dlocked)/1000) ); - } - } - timeLastPass = now; - tlast = timing; - } - sleepsecs(4); - } - } bool _bold; string bold(bool x) { @@ -118,14 +79,11 @@ namespace mongo { // caller locks void doLockedStuff(stringstream& ss) { ss << "# databases: " << dbHolder.size() << '\n'; - if ( cc().database() ) { - ss << "curclient: " << cc().database()->name; // TODO: isn't this useless? - ss << '\n'; - } + ss << bold(ClientCursor::byLocSize()>10000) << "Cursors byLoc.size(): " << ClientCursor::byLocSize() << bold() << '\n'; ss << "\n<b>replication</b>\n"; - ss << "master: " << master << '\n'; - ss << "slave: " << slave << '\n'; + ss << "master: " << replSettings.master << '\n'; + ss << "slave: " << replSettings.slave << '\n'; if ( replPair ) { ss << "replpair:\n"; ss << replPair->getInfo(); @@ -135,26 +93,76 @@ namespace mongo { ss << "initialSyncCompleted: " << seemCaughtUp; if ( !seemCaughtUp ) ss << "</b>"; ss << '\n'; - - ss << "\n<b>DBTOP</b>\n"; - ss << "<table border=1><tr align='left'><th>Namespace</th><th>%</th><th>Reads</th><th>Writes</th><th>Calls</th><th>Time</th>"; - vector< Top::Usage > usage; - Top::usage( usage ); - for( vector< Top::Usage >::iterator i = usage.begin(); i != usage.end(); ++i ) - ss << setprecision( 2 ) << fixed << "<tr><td>" << i->ns << "</td><td>" << i->pct << "</td><td>" - << i->reads << "</td><td>" << i->writes << "</td><td>" << i->calls << "</td><td>" << i->time << "</td></tr>\n"; - ss << "</table>"; - ss << "\n<b>dt\ttlocked</b>\n"; - unsigned i = q; - while ( 1 ) { - ss << lockStats[i] << '\n'; - i = (i-1)%NStats; - if ( i == q ) - break; + auto_ptr<SnapshotDelta> delta = statsSnapshots.computeDelta(); + if ( delta.get() ){ + ss << "\n<b>DBTOP (occurences|percent of elapsed)</b>\n"; + ss << "<table border=1>"; + ss << "<tr align='left'>"; + ss << "<th>NS</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>"; + + display( ss , (double) delta->elapsed() , "GLOBAL" , 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>"; } + + statsSnapshots.outputLockInfoHTML( ss ); + + BackgroundOperation::dump(ss); } + 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; + ss << setprecision(2) << fixed << per << "%"; + ss << "</td>"; + } + + void display( stringstream& ss , double elapsed , const string& ns , const Top::CollectionData& data ){ + if ( ns != "GLOBAL" && 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>"; + } + + void tablecell( stringstream& ss , bool b ){ + ss << "<td>" << (b ? "<b>X</b>" : "") << "</td>"; + } + + + template< typename T> + void tablecell( stringstream& ss , const T& t ){ + ss << "<td>" << t << "</td>"; + } + void doUnlockedStuff(stringstream& ss) { /* this is in the header already ss << "port: " << port << '\n'; */ ss << mongodVersion() << "\n"; @@ -178,21 +186,51 @@ namespace mongo { ss << "\nreplInfo: " << replInfo << "\n\n"; ss << "Clients:\n"; - ss << "<table border=1><tr align='left'><th>Thread</th><th>Current op</th>\n"; + ss << "<table border=1>"; + ss << "<tr align='left'>" + << "<th>Thread</th>" + + << "<th>OpId</th>" + << "<th>Active</th>" + << "<th>LockType</th>" + << "<th>Waiting</th>" + << "<th>SecsRunning</th>" + << "<th>Op</th>" + << "<th>NameSpace</th>" + << "<th>Query</th>" + << "<th>client</th>" + << "<th>msg</th>" + << "<th>progress</th>" + + << "</tr>\n"; { - boostlock bl(Client::clientsMutex); + scoped_lock bl(Client::clientsMutex); for( set<Client*>::iterator i = Client::clients.begin(); i != Client::clients.end(); i++ ) { Client *c = *i; CurOp& co = *(c->curop()); - ss << "<tr><td>" << c->desc() << "</td><td"; - BSONObj info = co.infoNoauth(); - /* - if( info.getIntField("inLock") > 0 ) - ss << "style='color:red'"; - else if( info.getIntField("inLock") < 0 ) - ss << "style='color:green'"; - */ - ss << ">" << info << "</td></tr>\n"; + ss << "<tr><td>" << c->desc() << "</td>"; + + tablecell( ss , co.opNum() ); + tablecell( ss , co.active() ); + tablecell( ss , co.getLockType() ); + tablecell( ss , co.isWaitingForLock() ); + if ( co.active() ) + tablecell( ss , co.elapsedSeconds() ); + else + tablecell( ss , "" ); + tablecell( ss , co.getOp() ); + tablecell( ss , co.getNS() ); + if ( co.haveQuery() ) + tablecell( ss , co.query() ); + else + tablecell( ss , "" ); + tablecell( ss , co.getRemoteString() ); + + tablecell( ss , co.getMessage() ); + tablecell( ss , co.getProgressMeter().toString() ); + + + ss << "</tr>"; } } ss << "</table>\n"; @@ -203,7 +241,7 @@ namespace mongo { if ( from.localhost() ) return true; - if ( db.findOne( "admin.system.users" , BSONObj() ).isEmpty() ) + if ( db.findOne( "admin.system.users" , BSONObj() , 0 , QueryOption_SlaveOk ).isEmpty() ) return true; string auth = getHeader( rq , "Authorization" ); @@ -270,6 +308,23 @@ namespace mongo { //out() << "url [" << url << "]" << endl; if ( url.size() > 1 ) { + + if ( url.find( "/_status" ) == 0 ){ + if ( ! allowed( rq , headers, from ) ){ + responseCode = 401; + responseMsg = "not allowed\n"; + return; + } + generateServerStatus( url , responseMsg ); + responseCode = 200; + return; + } + + if ( ! cmdLine.rest ){ + responseCode = 403; + responseMsg = "rest is not enabled. use --rest to turn on"; + return; + } if ( ! allowed( rq , headers, from ) ){ responseCode = 401; responseMsg = "not allowed\n"; @@ -294,23 +349,18 @@ namespace mongo { doUnlockedStuff(ss); - int n = 2000; - Timer t; - while ( 1 ) { - if ( !dbMutex.info().isLocked() ) { - { - readlock lk(""); - ss << "time to get dblock: " << t.millis() << "ms\n"; - doLockedStuff(ss); - } - break; + { + Timer t; + readlocktry lk( "" , 2000 ); + if ( lk.got() ){ + ss << "time to get dblock: " << t.millis() << "ms\n"; + doLockedStuff(ss); } - sleepmillis(1); - if ( --n < 0 ) { + else { ss << "\n<b>timed out getting dblock</b>\n"; - break; } } + ss << "</pre></body></html>"; responseMsg = ss.str(); @@ -323,6 +373,51 @@ namespace mongo { } } + void generateServerStatus( string url , string& responseMsg ){ + static vector<string> commands; + if ( commands.size() == 0 ){ + commands.push_back( "serverStatus" ); + commands.push_back( "buildinfo" ); + } + + BSONObj params; + if ( url.find( "?" ) != string::npos ) { + parseParams( params , url.substr( url.find( "?" ) + 1 ) ); + } + + BSONObjBuilder buf(1024); + + for ( unsigned i=0; i<commands.size(); i++ ){ + string cmd = commands[i]; + + Command * c = Command::findCommand( cmd ); + assert( c ); + assert( c->locktype() == 0 ); + + BSONObj co; + { + BSONObjBuilder b; + b.append( cmd.c_str() , 1 ); + + if ( cmd == "serverStatus" && params["repl"].type() ){ + b.append( "repl" , atoi( params["repl"].valuestr() ) ); + } + + co = b.obj(); + } + + string errmsg; + + BSONObjBuilder sub; + if ( ! c->run( "admin.$cmd" , co , errmsg , sub , false ) ) + buf.append( cmd.c_str() , errmsg ); + else + buf.append( cmd.c_str() , sub.obj() ); + } + + responseMsg = buf.obj().jsonString(); + } + void handleRESTRequest( const char *rq, // the full request string url, string& responseMsg, @@ -341,7 +436,7 @@ namespace mongo { string coll = url.substr( first + 1 ); string action = ""; - map<string,string> params; + BSONObj params; if ( coll.find( "?" ) != string::npos ) { parseParams( params , coll.substr( coll.find( "?" ) + 1 ) ); coll = coll.substr( 0 , coll.find( "?" ) ); @@ -361,7 +456,7 @@ namespace mongo { if ( coll[i] == '/' ) coll[i] = '.'; - string fullns = dbname + "." + coll; + string fullns = urlDecode(dbname + "." + coll); headers.push_back( (string)"x-action: " + action ); headers.push_back( (string)"x-ns: " + fullns ); @@ -387,26 +482,29 @@ namespace mongo { responseMsg = ss.str(); } - void handleRESTQuery( string ns , string action , map<string,string> & params , int & responseCode , stringstream & out ) { + void handleRESTQuery( string ns , string action , BSONObj & params , int & responseCode , stringstream & out ) { Timer t; int skip = _getOption( params["skip"] , 0 ); int num = _getOption( params["limit"] , _getOption( params["count" ] , 1000 ) ); // count is old, limit is new int one = 0; - if ( params["one"].size() > 0 && tolower( params["one"][0] ) == 't' ) { + if ( params["one"].type() == String && tolower( params["one"].valuestr()[0] ) == 't' ) { num = 1; one = 1; } BSONObjBuilder queryBuilder; - for ( map<string,string>::iterator i = params.begin(); i != params.end(); i++ ) { - if ( ! i->first.find( "filter_" ) == 0 ) + BSONObjIterator i(params); + while ( i.more() ){ + BSONElement e = i.next(); + string name = e.fieldName(); + if ( ! name.find( "filter_" ) == 0 ) continue; - const char * field = i->first.substr( 7 ).c_str(); - const char * val = i->second.c_str(); + const char * field = name.substr( 7 ).c_str(); + const char * val = e.valuestr(); char * temp; @@ -454,7 +552,7 @@ namespace mongo { } // TODO Generate id and revision per couch POST spec - void handlePost( string ns, const char *body, map<string,string> & params, int & responseCode, stringstream & out ) { + void handlePost( string ns, const char *body, BSONObj& params, int & responseCode, stringstream & out ) { try { BSONObj obj = fromjson( body ); db.insert( ns.c_str(), obj ); @@ -468,10 +566,12 @@ namespace mongo { out << "{ \"ok\" : true }"; } - int _getOption( string val , int def ) { - if ( val.size() == 0 ) - return def; - return atoi( val.c_str() ); + int _getOption( BSONElement e , int def ) { + if ( e.isNumber() ) + return e.numberInt(); + if ( e.type() == String ) + return atoi( e.valuestr() ); + return def; } private: @@ -481,7 +581,6 @@ namespace mongo { DBDirectClient DbWebServer::db; void webServerThread() { - boost::thread thr(statsThread); Client::initThread("websvr"); DbWebServer mini; int p = cmdLine.port + 1000; |