diff options
Diffstat (limited to 'tools/stat.cpp')
-rw-r--r-- | tools/stat.cpp | 653 |
1 files changed, 556 insertions, 97 deletions
diff --git a/tools/stat.cpp b/tools/stat.cpp index d94cf8d..fa6be31 100644 --- a/tools/stat.cpp +++ b/tools/stat.cpp @@ -20,6 +20,7 @@ #include "client/dbclient.h" #include "db/json.h" #include "../util/httpclient.h" +#include "../util/text.h" #include "tool.h" @@ -31,61 +32,65 @@ namespace po = boost::program_options; namespace mongo { - + class Stat : public Tool { public: - Stat() : Tool( "stat" , false , "admin" ){ + Stat() : Tool( "stat" , REMOTE_SERVER , "admin" ) { _sleep = 1; - _rowNum = 0; - _showHeaders = true; _http = false; + _many = false; add_hidden_options() - ( "sleep" , po::value<int>() , "time to sleep between calls" ) - ; + ( "sleep" , po::value<int>() , "time to sleep between calls" ) + ; add_options() - ("noheaders", "don't output column names") - ("rowcount,n", po::value<int>()->default_value(0), "number of stats lines to print (0 for indefinite)") - ("http", "use http instead of raw db connection") - ; + ("noheaders", "don't output column names") + ("rowcount,n", po::value<int>()->default_value(0), "number of stats lines to print (0 for indefinite)") + ("http", "use http instead of raw db connection") + ("discover" , "discover nodes and display stats for all" ) + ("all" , "all optional fields" ) + ; addPositionArg( "sleep" , 1 ); _autoreconnect = true; } - virtual void printExtraHelp( ostream & out ){ + virtual void printExtraHelp( ostream & out ) { out << "usage: " << _name << " [options] [sleep time]" << endl; out << "sleep time: time to wait (in seconds) between calls" << endl; } - virtual void printExtraHelpAfter( ostream & out ){ + virtual void printExtraHelpAfter( ostream & out ) { out << "\n"; out << " Fields\n"; - out << " inserts/s \t- # of inserts per second\n"; - out << " query/s \t- # of queries per second\n"; - out << " update/s \t- # of updates per second\n"; - out << " delete/s \t- # of deletes per second\n"; - out << " getmore/s \t- # of get mores (cursor batch) per second\n"; - out << " command/s \t- # of commands per second\n"; - out << " flushes/s \t- # of fsync flushes per second\n"; + out << " inserts \t- # of inserts per second\n"; + out << " query \t- # of queries per second\n"; + out << " update \t- # of updates per second\n"; + out << " delete \t- # of deletes per second\n"; + out << " getmore \t- # of get mores (cursor batch) per second\n"; + out << " command \t- # of commands per second\n"; + out << " flushes \t- # of fsync flushes per second\n"; out << " mapped \t- amount of data mmaped (total data size) megabytes\n"; out << " visze \t- virtual size of process in megabytes\n"; out << " res \t- resident size of process in megabytes\n"; - out << " faults/s \t- # of pages faults/sec (linux only)\n"; + out << " faults \t- # of pages faults per sec (linux only)\n"; out << " locked \t- percent of time in global write lock\n"; out << " idx miss \t- percent of btree page misses (sampled)\n"; - out << " q t|r|w \t- ops waiting for lock from db.currentOp() (total|read|write)\n"; + out << " qr|qw \t- queue lengths for clients waiting (read|write)\n"; + out << " ar|aw \t- active clients (read|write)\n"; + out << " netIn \t- network traffic in - bits\n"; + out << " netOut \t- network traffic out - bits\n"; out << " conn \t- number of open connections\n"; } - - BSONObj stats(){ - if ( _http ){ + + BSONObj stats() { + if ( _http ) { HttpClient c; HttpClient::Result r; - + string url; { stringstream ss; @@ -96,36 +101,36 @@ namespace mongo { url = ss.str(); } - if ( c.get( url , &r ) != 200 ){ + if ( c.get( url , &r ) != 200 ) { cout << "error (http): " << r.getEntireResponse() << endl; return BSONObj(); } - + BSONObj x = fromjson( r.getBody() ); BSONElement e = x["serverStatus"]; - if ( e.type() != Object ){ + if ( e.type() != Object ) { cout << "BROKEN: " << x << endl; return BSONObj(); } return e.embeddedObjectUserCheck(); } BSONObj out; - if ( ! conn().simpleCommand( _db , &out , "serverStatus" ) ){ + if ( ! conn().simpleCommand( _db , &out , "serverStatus" ) ) { cout << "error: " << out << endl; return BSONObj(); } return out.getOwned(); } - double diff( const string& name , const BSONObj& a , const BSONObj& b ){ + double diff( const string& name , const BSONObj& a , const BSONObj& b ) { BSONElement x = a.getFieldDotted( name.c_str() ); BSONElement y = b.getFieldDotted( name.c_str() ); if ( ! x.isNumber() || ! y.isNumber() ) return -1; return ( y.number() - x.number() ) / _sleep; } - - double percent( const char * outof , const char * val , const BSONObj& a , const BSONObj& b ){ + + double percent( const char * outof , const char * val , const BSONObj& a , const BSONObj& b ) { double x = ( b.getFieldDotted( val ).number() - a.getFieldDotted( val ).number() ); double y = ( b.getFieldDotted( outof ).number() - a.getFieldDotted( outof ).number() ); if ( y == 0 ) @@ -135,151 +140,605 @@ namespace mongo { return p; } - void cellstart( stringstream& ss , string name , unsigned& width ){ - if ( ! _showHeaders ) { - return; - } + template<typename T> + void _append( BSONObjBuilder& result , const string& name , unsigned width , const T& t ) { if ( name.size() > width ) width = name.size(); - if ( _rowNum % 20 == 0 ) - cout << setw(width) << name << " "; + result.append( name , BSON( "width" << (int)width << "data" << t ) ); } - void cell( stringstream& ss , string name , unsigned width , double val ){ - cellstart( ss , name , width ); - ss << setw(width) << setprecision(3) << val << " "; - } + void _appendMem( BSONObjBuilder& result , const string& name , unsigned width , double sz ) { + string unit = "m"; + if ( sz > 1024 ) { + unit = "g"; + sz /= 1024; + } + + if ( sz >= 1000 ) { + string s = str::stream() << (int)sz << unit; + _append( result , name , width , s ); + return; + } - void cell( stringstream& ss , string name , unsigned width , int val ){ - cellstart( ss , name , width ); - ss << setw(width) << val << " "; + stringstream ss; + ss << setprecision(3) << sz << unit; + _append( result , name , width , ss.str() ); } - void cell( stringstream& ss , string name , unsigned width , const string& val ){ - cellstart( ss , name , width ); - ss << setw(width) << val << " "; + void _appendNet( BSONObjBuilder& result , const string& name , double diff ) { + // I think 1000 is correct for megabit, but I've seen conflicting things (ERH 11/2010) + const double div = 1000; + + string unit = "b"; + + if ( diff >= div ) { + unit = "k"; + diff /= div; + } + + if ( diff >= div ) { + unit = "m"; + diff /= div; + } + + if ( diff >= div ) { + unit = "g"; + diff /= div; + } + + string out = str::stream() << (int)diff << unit; + _append( result , name , 6 , out ); } - string doRow( const BSONObj& a , const BSONObj& b ){ - stringstream ss; + /** + * BSON( <field> -> BSON( width : ### , data : XXX ) ) + */ + BSONObj doRow( const BSONObj& a , const BSONObj& b ) { + BSONObjBuilder result; - if ( b["opcounters"].type() == Object ){ + if ( a["opcounters"].isABSONObj() && b["opcounters"].isABSONObj() ) { BSONObj ax = a["opcounters"].embeddedObject(); BSONObj bx = b["opcounters"].embeddedObject(); + + BSONObj ar = a["opcountersRepl"].isABSONObj() ? a["opcountersRepl"].embeddedObject() : BSONObj(); + BSONObj br = b["opcountersRepl"].isABSONObj() ? b["opcountersRepl"].embeddedObject() : BSONObj(); + BSONObjIterator i( bx ); - while ( i.more() ){ + while ( i.more() ) { BSONElement e = i.next(); - cell( ss , (string)(e.fieldName()) + "/s" , 6 , (int)diff( e.fieldName() , ax , bx ) ); + if ( ar.isEmpty() || br.isEmpty() ) { + _append( result , e.fieldName() , 6 , (int)diff( e.fieldName() , ax , bx ) ); + } + else { + string f = e.fieldName(); + + int m = (int)diff( f , ax , bx ); + int r = (int)diff( f , ar , br ); + + string myout; + + if ( f == "command" ) { + myout = str::stream() << m << "|" << r; + } + else if ( f == "getmore" ) { + myout = str::stream() << m; + } + else if ( m && r ) { + // this is weird... + myout = str::stream() << m << "|" << r; + } + else if ( m ) { + myout = str::stream() << m; + } + else if ( r ) { + myout = str::stream() << "*" << r; + } + else { + myout = "*0"; + } + + _append( result , f , 6 , myout ); + } } } - if ( b["backgroundFlushing"].type() == Object ){ + if ( b["backgroundFlushing"].type() == Object ) { BSONObj ax = a["backgroundFlushing"].embeddedObject(); BSONObj bx = b["backgroundFlushing"].embeddedObject(); - BSONObjIterator i( bx ); - cell( ss , "flushes/s" , 6 , (int)diff( "flushes" , ax , bx ) ); + _append( result , "flushes" , 6 , (int)diff( "flushes" , ax , bx ) ); } - if ( b.getFieldDotted("mem.supported").trueValue() ){ + if ( b.getFieldDotted("mem.supported").trueValue() ) { BSONObj bx = b["mem"].embeddedObject(); BSONObjIterator i( bx ); - cell( ss , "mapped" , 6 , bx["mapped"].numberInt() ); - cell( ss , "vsize" , 6 , bx["virtual"].numberInt() ); - cell( ss , "res" , 6 , bx["resident"].numberInt() ); + _appendMem( result , "mapped" , 6 , bx["mapped"].numberInt() ); + _appendMem( result , "vsize" , 6 , bx["virtual"].numberInt() ); + _appendMem( result , "res" , 6 , bx["resident"].numberInt() ); + + if ( _all ) + _appendMem( result , "non-mapped" , 6 , bx["virtual"].numberInt() - bx["mapped"].numberInt() ); } - if ( b["extra_info"].type() == Object ){ + if ( b["extra_info"].type() == Object ) { BSONObj ax = a["extra_info"].embeddedObject(); BSONObj bx = b["extra_info"].embeddedObject(); if ( ax["page_faults"].type() || ax["page_faults"].type() ) - cell( ss , "faults/s" , 6 , (int)diff( "page_faults" , ax , bx ) ); + _append( result , "faults" , 6 , (int)diff( "page_faults" , ax , bx ) ); } - - cell( ss , "locked %" , 8 , percent( "globalLock.totalTime" , "globalLock.lockTime" , a , b ) ); - cell( ss , "idx miss %" , 8 , percent( "indexCounters.btree.accesses" , "indexCounters.btree.misses" , a , b ) ); - if ( b.getFieldDotted( "globalLock.currentQueue" ).type() == Object ){ + _append( result , "locked %" , 8 , percent( "globalLock.totalTime" , "globalLock.lockTime" , a , b ) ); + _append( result , "idx miss %" , 8 , percent( "indexCounters.btree.accesses" , "indexCounters.btree.misses" , a , b ) ); + + if ( b.getFieldDotted( "globalLock.currentQueue" ).type() == Object ) { int r = b.getFieldDotted( "globalLock.currentQueue.readers" ).numberInt(); int w = b.getFieldDotted( "globalLock.currentQueue.writers" ).numberInt(); stringstream temp; - temp << r+w << "|" << r << "|" << w; - cell( ss , "q t|r|w" , 10 , temp.str() ); + temp << r << "|" << w; + _append( result , "qr|qw" , 9 , temp.str() ); + } + + if ( b.getFieldDotted( "globalLock.activeClients" ).type() == Object ) { + int r = b.getFieldDotted( "globalLock.activeClients.readers" ).numberInt(); + int w = b.getFieldDotted( "globalLock.activeClients.writers" ).numberInt(); + stringstream temp; + temp << r << "|" << w; + _append( result , "ar|aw" , 7 , temp.str() ); + } + + if ( a["network"].isABSONObj() && b["network"].isABSONObj() ) { + BSONObj ax = a["network"].embeddedObject(); + BSONObj bx = b["network"].embeddedObject(); + _appendNet( result , "netIn" , diff( "bytesIn" , ax , bx ) ); + _appendNet( result , "netOut" , diff( "bytesOut" , ax , bx ) ); + } + + _append( result , "conn" , 5 , b.getFieldDotted( "connections.current" ).numberInt() ); + + if ( b["repl"].type() == Object ) { + + BSONObj x = b["repl"].embeddedObject(); + bool isReplSet = x["setName"].type() == String; + + stringstream ss; + + if ( isReplSet ) { + string setName = x["setName"].String(); + _append( result , "set" , setName.size() , setName ); + } + + if ( x["ismaster"].trueValue() ) + ss << "M"; + else if ( x["secondary"].trueValue() ) + ss << "SEC"; + else if ( x["isreplicaset"].trueValue() ) + ss << "REC"; + else if ( isReplSet ) + ss << "UNK"; + else + ss << "SLV"; + + _append( result , "repl" , 4 , ss.str() ); + + } + else if ( b["shardCursorType"].type() == Object ) { + // is a mongos + // TODO: should have a better check + _append( result , "repl" , 4 , "RTR" ); } - cell( ss , "conn" , 5 , b.getFieldDotted( "connections.current" ).numberInt() ); { struct tm t; time_t_to_Struct( time(0), &t , true ); stringstream temp; - temp << setfill('0') << setw(2) << t.tm_hour - << ":" + temp << setfill('0') << setw(2) << t.tm_hour + << ":" << setfill('0') << setw(2) << t.tm_min - << ":" + << ":" << setfill('0') << setw(2) << t.tm_sec; - cell( ss , "time" , 10 , temp.str() ); + _append( result , "time" , 10 , temp.str() ); } + return result.obj(); + } - if ( _showHeaders && _rowNum % 20 == 0 ){ - // this is the newline after the header line - cout << endl; + virtual void preSetup() { + if ( hasParam( "http" ) ) { + _http = true; + _noconnection = true; } - _rowNum++; - return ss.str(); - } - - virtual void preSetup(){ - if ( hasParam( "http" ) ){ - _http = true; + if ( hasParam( "host" ) && + getParam( "host" ).find( ',' ) != string::npos ) { + _noconnection = true; + _many = true; + } + + if ( hasParam( "discover" ) ) { _noconnection = true; + _many = true; } } - int run(){ + int run() { _sleep = getParam( "sleep" , _sleep ); - if ( hasParam( "noheaders" ) ) { - _showHeaders = false; + _all = hasParam( "all" ); + if ( _many ) + return runMany(); + return runNormal(); + } + + static void printHeaders( const BSONObj& o ) { + BSONObjIterator i(o); + while ( i.more() ) { + BSONElement e = i.next(); + BSONObj x = e.Obj(); + cout << setw( x["width"].numberInt() ) << e.fieldName() << ' '; } - _rowCount = getParam( "rowcount" , 0 ); + cout << endl; + } + + static void printData( const BSONObj& o , const BSONObj& headers ) { + + BSONObjIterator i(headers); + while ( i.more() ) { + BSONElement e = i.next(); + BSONObj h = e.Obj(); + int w = h["width"].numberInt(); + + BSONElement data; + { + BSONElement temp = o[e.fieldName()]; + if ( temp.isABSONObj() ) + data = temp.Obj()["data"]; + } + + if ( data.type() == String ) + cout << setw(w) << data.String(); + else if ( data.type() == NumberDouble ) + cout << setw(w) << setprecision(3) << data.number(); + else if ( data.type() == NumberInt ) + cout << setw(w) << data.numberInt(); + else if ( data.eoo() ) + cout << setw(w) << ""; + else + cout << setw(w) << "???"; + + cout << ' '; + } + cout << endl; + } + + int runNormal() { + bool showHeaders = ! hasParam( "noheaders" ); + int rowCount = getParam( "rowcount" , 0 ); + int rowNum = 0; BSONObj prev = stats(); if ( prev.isEmpty() ) return -1; - while ( _rowCount == 0 || _rowNum < _rowCount ){ + while ( rowCount == 0 || rowNum < rowCount ) { sleepsecs(_sleep); BSONObj now; try { now = stats(); } - catch ( std::exception& e ){ + catch ( std::exception& e ) { cout << "can't get data: " << e.what() << endl; continue; } if ( now.isEmpty() ) return -2; - + try { - cout << doRow( prev , now ) << endl; + + BSONObj out = doRow( prev , now ); + + if ( showHeaders && rowNum % 10 == 0 ) { + printHeaders( out ); + } + + printData( out , out ); + } - catch ( AssertionException& e ){ + catch ( AssertionException& e ) { cout << "\nerror: " << e.what() << "\n" << now << endl; } - + prev = now; + rowNum++; + } + return 0; + } + + struct ServerState { + ServerState() : lock( "Stat::ServerState" ) {} + string host; + scoped_ptr<boost::thread> thr; + + mongo::mutex lock; + + BSONObj prev; + BSONObj now; + time_t lastUpdate; + vector<BSONObj> shards; + + string error; + bool mongos; + }; + + static void serverThread( shared_ptr<ServerState> state ) { + try { + DBClientConnection conn( true ); + conn._logLevel = 1; + string errmsg; + if ( ! conn.connect( state->host , errmsg ) ) + state->error = errmsg; + + long long cycleNumber = 0; + + while ( ++cycleNumber ) { + try { + BSONObj out; + if ( conn.simpleCommand( "admin" , &out , "serverStatus" ) ) { + scoped_lock lk( state->lock ); + state->error = ""; + state->lastUpdate = time(0); + state->prev = state->now; + state->now = out.getOwned(); + } + else { + scoped_lock lk( state->lock ); + state->error = "serverStatus failed"; + state->lastUpdate = time(0); + } + + if ( out["shardCursorType"].type() == Object ) { + state->mongos = true; + if ( cycleNumber % 10 == 1 ) { + auto_ptr<DBClientCursor> c = conn.query( "config.shards" , BSONObj() ); + vector<BSONObj> shards; + while ( c->more() ) { + shards.push_back( c->next().getOwned() ); + } + scoped_lock lk( state->lock ); + state->shards = shards; + } + } + } + catch ( std::exception& e ) { + scoped_lock lk( state->lock ); + state->error = e.what(); + } + + sleepsecs( 1 ); + } + + + } + catch ( std::exception& e ) { + cout << "serverThread (" << state->host << ") fatal error : " << e.what() << endl; + } + catch ( ... ) { + cout << "serverThread (" << state->host << ") fatal error" << endl; + } + } + + typedef map<string,shared_ptr<ServerState> > StateMap; + + bool _add( StateMap& threads , string host ) { + shared_ptr<ServerState>& state = threads[host]; + if ( state ) + return false; + + state.reset( new ServerState() ); + state->host = host; + state->thr.reset( new boost::thread( boost::bind( serverThread , state ) ) ); + return true; + } + + /** + * @param hosts [ "a.foo.com" , "b.foo.com" ] + */ + bool _addAll( StateMap& threads , const BSONObj& hosts ) { + BSONObjIterator i( hosts ); + bool added = false; + while ( i.more() ) { + bool me = _add( threads , i.next().String() ); + added = added || me; + } + return added; + } + + bool _discover( StateMap& threads , const string& host , const shared_ptr<ServerState>& ss ) { + + BSONObj info = ss->now; + + bool found = false; + + if ( info["repl"].isABSONObj() ) { + BSONObj x = info["repl"].Obj(); + if ( x["hosts"].isABSONObj() ) + if ( _addAll( threads , x["hosts"].Obj() ) ) + found = true; + if ( x["passives"].isABSONObj() ) + if ( _addAll( threads , x["passives"].Obj() ) ) + found = true; + } + + if ( ss->mongos ) { + for ( unsigned i=0; i<ss->shards.size(); i++ ) { + BSONObj x = ss->shards[i]; + + string errmsg; + ConnectionString cs = ConnectionString::parse( x["host"].String() , errmsg ); + if ( errmsg.size() ) { + cerr << errmsg << endl; + continue; + } + + vector<HostAndPort> v = cs.getServers(); + for ( unsigned i=0; i<v.size(); i++ ) { + if ( _add( threads , v[i].toString() ) ) + found = true; + } + } + } + + return found; + } + + int runMany() { + StateMap threads; + + + { + string orig = getParam( "host" ); + if ( orig == "" ) + orig = "localhost"; + + if ( orig.find( ":" ) == string::npos ) { + if ( hasParam( "port" ) ) + orig += ":" + _params["port"].as<string>(); + else + orig += ":27017"; + } + + StringSplitter ss( orig.c_str() , "," ); + while ( ss.more() ) { + string host = ss.next(); + _add( threads , host ); + } } + + sleepsecs(1); + + int row = 0; + bool discover = hasParam( "discover" ); + + while ( 1 ) { + sleepsecs( _sleep ); + + // collect data + vector<Row> rows; + for ( map<string,shared_ptr<ServerState> >::iterator i=threads.begin(); i!=threads.end(); ++i ) { + scoped_lock lk( i->second->lock ); + + if ( i->second->error.size() ) { + rows.push_back( Row( i->first , i->second->error ) ); + } + else if ( i->second->prev.isEmpty() || i->second->now.isEmpty() ) { + rows.push_back( Row( i->first ) ); + } + else { + BSONObj out = doRow( i->second->prev , i->second->now ); + rows.push_back( Row( i->first , out ) ); + } + + if ( discover && ! i->second->now.isEmpty() ) { + if ( _discover( threads , i->first , i->second ) ) + break; + } + } + + // compute some stats + unsigned longestHost = 0; + BSONObj biggest; + for ( unsigned i=0; i<rows.size(); i++ ) { + if ( rows[i].host.size() > longestHost ) + longestHost = rows[i].host.size(); + if ( rows[i].data.nFields() > biggest.nFields() ) + biggest = rows[i].data; + } + + { + // check for any headers not in biggest + + // TODO: we put any new headers at end, + // ideally we would interleave + + set<string> seen; + + BSONObjBuilder b; + + { + // iterate biggest + BSONObjIterator i( biggest ); + while ( i.more() ) { + BSONElement e = i.next(); + seen.insert( e.fieldName() ); + b.append( e ); + } + } + + // now do the rest + for ( unsigned j=0; j<rows.size(); j++ ) { + BSONObjIterator i( rows[j].data ); + while ( i.more() ) { + BSONElement e = i.next(); + if ( seen.count( e.fieldName() ) ) + continue; + seen.insert( e.fieldName() ); + b.append( e ); + } + + } + + biggest = b.obj(); + + } + + // display data + + cout << endl; + + // header + if ( row++ % 5 == 0 && ! biggest.isEmpty() ) { + cout << setw( longestHost ) << "" << "\t"; + printHeaders( biggest ); + } + + // rows + for ( unsigned i=0; i<rows.size(); i++ ) { + cout << setw( longestHost ) << rows[i].host << "\t"; + if ( rows[i].err.size() ) + cout << rows[i].err << endl; + else if ( rows[i].data.isEmpty() ) + cout << "no data" << endl; + else + printData( rows[i].data , biggest ); + } + + } + return 0; } - int _sleep; - int _rowNum; - int _rowCount; - bool _showHeaders; bool _http; + bool _many; + bool _all; + + struct Row { + Row( string h , string e ) { + host = h; + err = e; + } + + Row( string h ) { + host = h; + } + + Row( string h , BSONObj d ) { + host = h; + data = d; + } + string host; + string err; + BSONObj data; + }; }; } |