// dbwebserver.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 . */ #include "stdafx.h" #include "../util/miniwebserver.h" #include "db.h" #include "repl.h" #include "replset.h" #include "instance.h" extern int port; extern const char *replInfo; bool getInitialSyncCompleted(); time_t started = time(0); /* string toString() { stringstream ss; unsigned long long dt = last - start; ss << dt/1000; ss << '\t'; ss << timeLocked/1000 << '\t'; if( dt ) ss << (timeLocked*100)/dt << '%'; return ss.str(); } */ struct Timing { Timing() { start = timeLocked = 0; } unsigned long long start, timeLocked; }; Timing tlast; const int NStats = 32; string lockStats[NStats]; unsigned q = 0; extern bool cpu; void statsThread() { unsigned long long timeLastPass = 0; while ( 1 ) { { Timer lktm; dblock lk; q = (q+1)%NStats; Timing timing; dbMutexInfo.timingInfo(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 ( cpu ) log() << "cpu: " << s << endl; lockStats[q] = s; } } timeLastPass = now; tlast = timing; } sleepsecs(4); } } unsigned byLocSize(); bool _bold; string bold(bool x) { _bold = x; return x ? "" : ""; } string bold() { return _bold ? "" : ""; } class DbWebServer : public MiniWebServer { public: // caller locks void doLockedStuff(stringstream& ss) { ss << "# databases: " << databases.size() << '\n'; if ( database ) { ss << "curclient: " << database->name; ss << '\n'; } ss << bold(byLocSize()>10000) << "Cursors byLoc.size(): " << byLocSize() << bold() << '\n'; ss << "\nreplication\n"; ss << "master: " << master << '\n'; ss << "slave: " << slave << '\n'; if ( replPair ) { ss << "replpair:\n"; ss << replPair->getInfo(); } bool seemCaughtUp = getInitialSyncCompleted(); if ( !seemCaughtUp ) ss << ""; ss << "initialSyncCompleted: " << seemCaughtUp; if ( !seemCaughtUp ) ss << ""; ss << '\n'; ss << "\ndt\ttlocked\n"; unsigned i = q; while ( 1 ) { ss << lockStats[i] << '\n'; i = (i-1)%NStats; if ( i == q ) break; } } void doUnlockedStuff(stringstream& ss) { ss << "port: " << port << '\n'; ss << "dblocked: " << dbMutexInfo.isLocked() << " (initial)\n"; ss << "uptime: " << time(0)-started << " seconds\n"; if ( allDead ) ss << "replication allDead=" << allDead << "\n"; ss << "\nassertions:\n"; for ( int i = 0; i < 4; i++ ) { if ( lastAssert[i].isSet() ) { ss << ""; if ( i == 3 ) ss << "usererr"; else ss << i; ss << "" << ' ' << lastAssert[i].toString(); } } ss << "\nreplInfo: " << replInfo << '\n'; } virtual void doRequest( const char *rq, // the full request string url, // set these and return them: string& responseMsg, int& responseCode, vector& headers // if completely empty, content-type: text/html will be added ) { //cout << "url [" << url << "]" << endl; if ( url.size() > 1 ){ handleRESTRequest( rq , url , responseMsg , responseCode , headers ); return; } responseCode = 200; stringstream ss; ss << ""; string dbname; { stringstream z; z << "db " << getHostName() << ':' << port << ' '; dbname = z.str(); } ss << dbname << "

" << dbname << "

\n

";

        doUnlockedStuff(ss);

        int n = 2000;
        Timer t;
        while ( 1 ) {
            if ( !dbMutexInfo.isLocked() ) {
                {
                    dblock lk;
                    ss << "time to get dblock: " << t.millis() << "ms\n";
                    doLockedStuff(ss);
                }
                break;
            }
            sleepmillis(1);
            if ( --n < 0 ) {
                ss << "\ntimed out getting dblock\n";
                break;
            }
        }

        ss << "
"; responseMsg = ss.str(); } void handleRESTRequest( const char *rq, // the full request string url, string& responseMsg, int& responseCode, vector& headers // if completely empty, content-type: text/html will be added ){ string::size_type first = url.find( "/" , 1 ); if ( first == string::npos ){ responseCode = 400; return; } string method = parseMethod( rq ); string dbname = url.substr( 1 , first - 1 ); string coll = url.substr( first + 1 ); string action = ""; map params; if ( coll.find( "?" ) != string::npos ){ parseParams( params , coll.substr( coll.find( "?" ) + 1 ) ); coll = coll.substr( 0 , coll.find( "?" ) ); } string::size_type last = coll.find_last_of( "/" ); if ( last == string::npos ){ action = coll; coll = "_defaultCollection"; } else { action = coll.substr( last + 1 ); coll = coll.substr( 0 , last ); } for ( string::size_type i=0; i & params , int & responseCode , stringstream & out ){ static DBDirectClient db; 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' ){ num = 1; one = 1; } BSONObjBuilder queryBuilder; for ( map::iterator i = params.begin(); i != params.end(); i++ ){ if ( ! i->first.find( "filter_" ) == 0 ) continue; const char * field = i->first.substr( 7 ).c_str(); const char * val = i->second.c_str(); char * temp; // TODO: this is how i guess if something is a number. pretty lame right now double number = strtod( val , &temp ); if ( temp != val ) queryBuilder.append( field , number ); else queryBuilder.append( field , val ); } BSONObj query = queryBuilder.doneAndDecouple(); auto_ptr cursor = db.query( ns.c_str() , query, num , skip ); if ( one ){ if ( cursor->more() ){ BSONObj obj = cursor->next(); out << obj.jsonString() << "\n"; } else { responseCode = 404; } return; } out << "{\n"; out << " \"offset\" : " << skip << ",\n"; out << " \"rows\": [\n"; int howMany = 0; while ( cursor->more() ){ if ( howMany++ ) out << " ,\n"; BSONObj obj = cursor->next(); out << " " << obj.jsonString(); } out << "\n ]\n\n"; out << " \"total_rows\" : " << howMany << " ,\n"; out << " \"query\" : " << query.jsonString() << " ,\n"; out << " \"millis\" : " << t.millis() << " ,\n"; out << "}\n"; } int _getOption( string val , int def ){ if ( val.size() == 0 ) return def; return atoi( val.c_str() ); } }; void webServerThread() { boost::thread thr(statsThread); DbWebServer mini; if ( mini.init(port+1000) ) mini.run(); }