diff --git a/.gitignore b/.gitignore index 1555ea47d4a59..6640cbd964aec 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ scratch # binaries /mongo /mongod +/mongod.man /mongogrid /mongos diff --git a/SConstruct b/SConstruct index 221304a088c74..885d5985a6e2a 100644 --- a/SConstruct +++ b/SConstruct @@ -654,6 +654,20 @@ elif "win32" == os.sys.platform: env.Append( EXTRACPPPATH=["#/../winpcap/Include"] ) env.Append( EXTRALIBPATH=["#/../winpcap/Lib"] ) + if force64: + env.Append( CTRPP= winSDKHome + "/Bin/x64/ctrpp.exe" ) + else: + env.Append( CTRPP= winSDKHome + "/Bin/ctrpp.exe" ) + if os.path.exists( env['CTRPP'] ): + print( "found ctrpp at " + env['CTRPP'] ) + + ctrpp_resource_builder = Builder( action = '"$CTRPP" -prefix Ctrpp_ -rc $TARGET $SOURCE', src_suffix='.man', suffix='.rc' ) + ctrpp_header_builder = Builder( action = '"$CTRPP" -prefix Ctrpp_ -o $TARGET $SOURCE', src_suffix='.man', suffix='.h' ) + env['BUILDERS']['PerfCounters_Resource'] = ctrpp_resource_builder + env['BUILDERS']['PerfCounters_Header'] = ctrpp_header_builder + + + else: print( "No special config for [" + os.sys.platform + "] which probably means it won't work" ) diff --git a/src/mongo/SConscript b/src/mongo/SConscript index fc16628775673..c8cb83c907f07 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -378,6 +378,14 @@ env.StaticLibrary("notmongodormongos", everythingButMongodAndMongosFiles) mongodOnlyFiles = [ "db/db.cpp", "db/compact.cpp", "db/commands/touch.cpp" ] +if "win32" == os.sys.platform: + env.PerfCounters_Header( target='winperfcounters_ctrpp', source='db/modules/winperfcounters/mongod' ) + env.PerfCounters_Resource( target='winperfcounters_ctrpp', source='db/modules/winperfcounters/mongod' ) + env.RES( target='mongod', source='winperfcounters_ctrpp.rc' ) + mongodOnlyFiles.append( "db/modules/winperfcounters/winperfcounters_instance.cpp" ) + mongodOnlyFiles.append( "mongod.res" ) +env.StaticLibrary( "winperfcounters", [ "db/modules/winperfcounters/winperfcounters.cpp" ] ) + # create a library per module, and add it as a dependency # for the mongod target; as of now, modules are only included # in mongod, as though they were part of serverOnlyFiles @@ -400,10 +408,21 @@ env.StaticLibrary("coreserver", coreServerFiles, LIBDEPS=["mongocommon", "script # main db target mongod = env.Install( '#/', env.Program( "mongod", mongodOnlyFiles, - LIBDEPS=["coreserver", "serveronly", "coredb", "ntservice", + LIBDEPS=["coreserver", "serveronly", "coredb", "ntservice", "winperfcounters", "mongodandmongos"] + modules ) ) Default( mongod ) +# performance counters +if "win32" == os.sys.platform: + # TODO: what we really want is to always install mongod.man when installing mongod.exe + mongodman = env.Install( '#/', 'db/modules/winperfcounters/mongod.man' ) + wpcprobe = env.Install( '#/', testEnv.Program( "winperfcountersprobe", [ "db/modules/winperfcounters/winperfcountersprobe.cpp" ], + LIBDEPS=["serveronly", "coreserver", "coredb", "notmongodormongos", "ntservice", "winperfcounters" ] ) ) + Default( mongodman ) + env.Alias( 'core', [ mongodman ] ) + env.Alias( 'all', [ mongodman, wpcprobe ] ) + + # tools allToolFiles = [ "tools/tool.cpp", "tools/stat_util.cpp" ] env.StaticLibrary("alltools", allToolFiles, LIBDEPS=["serveronly", "coreserver", "coredb", diff --git a/src/mongo/db/instance.cpp b/src/mongo/db/instance.cpp index 218fb9eec2b56..b4cb2ada793bd 100644 --- a/src/mongo/db/instance.cpp +++ b/src/mongo/db/instance.cpp @@ -40,6 +40,7 @@ #endif #include "stats/counters.h" #include "background.h" +#include "module.h" #include "dur_journal.h" #include "dur_recover.h" #include "d_concurrency.h" @@ -954,6 +955,9 @@ namespace mongo { log() << "shutdown: going to close listening sockets..." << endl; ListeningSockets::get()->closeAll(); + log() << "shutdown: going to shutdown modules..." << endl; + Module::shutdownAll(); + log() << "shutdown: going to flush diaglog..." << endl; _diaglog.flush(); diff --git a/src/mongo/db/module.cpp b/src/mongo/db/module.cpp index 4269c5e99a0dc..0675706f60df2 100644 --- a/src/mongo/db/module.cpp +++ b/src/mongo/db/module.cpp @@ -65,4 +65,16 @@ namespace mongo { } + + void Module::shutdownAll() { + if ( ! _all ) { + return; + } + for ( list::iterator i=_all->begin(); i!=_all->end(); i++ ) { + Module* m = *i; + m->shutdown(); + } + + } + } diff --git a/src/mongo/db/module.h b/src/mongo/db/module.h index 71f276e058594..d58dffe7e0dfc 100644 --- a/src/mongo/db/module.h +++ b/src/mongo/db/module.h @@ -50,7 +50,7 @@ namespace mongo { virtual void init() = 0; /** - * called when the database is about to shutdown + * called when the database is about to shutdown, can be called without init() */ virtual void shutdown() = 0; @@ -61,6 +61,7 @@ namespace mongo { static void addOptions( boost::program_options::options_description& options ); static void configAll( boost::program_options::variables_map& params ); static void initAll(); + static void shutdownAll(); private: static std::list * _all; diff --git a/src/mongo/db/modules/winperfcounters/README b/src/mongo/db/modules/winperfcounters/README new file mode 100644 index 0000000000000..245240d80a9da --- /dev/null +++ b/src/mongo/db/modules/winperfcounters/README @@ -0,0 +1,281 @@ +WinPerfCounters README + + Windows Performance Counters Module + + +OVERVIEW + + Windows Performance Counters for mongod.exe, implemented as a mongo Module, + using the Windows API Performance Counters Library V2.0 (aka perflib 2.0, + introduced in Windows Vista / Windows Server 2008). + + Shows as "MongoDB" counterset. + + Have zero CPU cost when there are no client applications monitoring the + counters. Have negligible CPU cost when there are clients. Memory cost is + negligible, always. + + Compatible with 32 and 64 bit versions of mongod.exe, and, unlike perflib 1, + doesn't care if the Windows performance counter clients are 32 or 64 bits. + + Runs with standalone mongod.exe, service mongod.exe, and multiple and + mixed instances of both. Each will appear as a separate and independent + counterset instance. + + Adds a single command line option to mongod.exe, to enable: --winPerfCounters + + As with any perflib2 application, there is an install/uninstall/upgrade + step required. See below. + + Unlike perflib 1, no performance DLL is built. The only artifact that needs + to be distributed with mongod.exe is the XML manifest file "mongod.man". + + +BUILDING + + The module builds with mongod.exe, no special action required. + + scons mongod.man : copies mongod.man to the project root dir. + + scons winperfcountersprobe.exe : builds and copy probe needed for testing. + + scons all : builds all above. + + MUST be built with Windows SDK 7.0 (SDK for Windows 7) or more recent, to get + the right version of the ctrpp.exe tool. + + +INSTALLING + + If you don't want to use performance counters, you don't need to do anything. + + If not installed, there will be no runtime error, i.e., mongod.exe will + not complain, even if given the command line option to activate this + module. But no client application will be able to see the counters. + + To install, you MUST follow the official MS way: + + .from a prompt with admin privileges + .on the same directory containing BOTH mongod.exe and mongod.man + .run "lodctr /m:mongod.man" + + See if lodctr explicitly tells you it succeeded, because in Vista SP2 + it sometimes does NOT tell you if it failed. + + Then, undocumented and painfully discovered (in Vista SP2): + + .edit mongod.exe ACL: grant read access to Administrators and "Performance + Monitor Users" + + The lodctr.exe tool is distributed with Windows. This will register with + Windows all the required metadata and the location of mongod.exe, where + perflib2 will get the counter's strings. + + IMPORTANT: if you want to change mongod.exe's name or directory, you MUST + reinstall. + + +UNINSTALLING + + To uninstall, you MUST follow the official MS way: + + .from a prompt with admin privileges + .on the same directory containing both THE INSTALLED mongod.exe and + mongod.man + .run "unlodctr /m:mongod.man" + + The unlodctr.exe tool is distributed with Windows. + + +UPGRADING + + Before installing a new version of mongod.exe, you SHOULD uninstall the older + one. + + That's the official MS way, the safe way, and always works. + + If the new mongod.exe has the EXACT same mongod.man as the old one, and will + be put in the same directory, overwriting the old one, you can do without + reinstalling. That's how I usually do while developing on Vista SP2. + + +USING + + Start mongod.exe with --winPerfCounters to enable. + + -vv(...) levels available, see source code. + + No special permission required for mongod.exe. + + +QUIRKS + + mongod.exe file locked, cannot delete/upgrade + + .close all perf counter client applications + .stop the SNMP service + .rename away mongod.exe + + "unlodctr.exe /m:mongod.man" fails + + .run as an administrator + .mongod.man MUST be the same as the installed + + Counters not showing/showing wrong + + .run app as a member of "Performance Monitor Users" or Administrators + .check mongod.exe ACL + .try with perfmon.exe (PDH API) + .reinstall + .only WMI not showing? "winmgmt.exe /resyncperf" or restart WMI or + just wait a few minutes + + +DEVELOPING NEW COUNTERS + + Roadmap: + + 1.mongod.man: create new "" element + + 2.winperfcounters.cpp: implement counter update logic in + WinPerfCounters::updateCounters() + + 3.winperfcountersprobe.cpp: add counter metadata to + to CounterNameInfoMap::CounterNameInfoMap() + + 4.winperfcounterstest.wsf: add counter metadata to global variable + gMongoCounters + + Guidelines: + + Keep these design goals: zero CPU cost when no clients, negligible CPU cost + with clients. Negligible memory cost in either case. + + Stick to this instructions, unless you have a very firm grasp of perflib2. + + WMI (and so the test script) can be particularly weird with changing + counter definitions (registry or .exe strings). During the dev cycle it's + very common to have the test script fail when it shouldn't - it's just + WMI acting up. See quirks. + + mongod.man: + + WARNING: screw this manifest, and you can screw the user's registry. + + There is a GUI editor in Windows SDK, called "Manifest Generator" + (ecmangen.exe), which can be used. + + Note that this file is UTF-16 encoded, with BOM. Keep it that way. + + To define a new counter just add a new element. Read the GUI + editor's help (F1) for a very brief guide on the parameters. More info can + be found on the SDK's chapter "Performance Counters Schema". There are no + naming style guide to counters. + + Once a counter has made it to a stable, official MongoDb release, + never change its name. It will brake someone's monitoring app/script. + + USE ONLY THE FOLLOWING TYPES: + + perf_counter_rawcount + + ULONG counter (32 bit), "shows the last observed value". + Examples: MBytes Received, Current Connections, Requests in Queue + + perf_counter_large_rawcount + + ULONGLONG (64 bit) version of previous. + Examples: Bytes Received + + perf_counter_counter + + ULONG, "shows the average number of operations completed during each + second of the sample interval". NOTE: we only have to set the absolute + value at each update, it's the clients job to do all the calculation / + time tracking. + Examples: Read Operations/sec + + perf_counter_bulk_count + + ULONGLONG version of previous. + Examples: Bytes/sec + + UNLESS you want to DIG DEEP: + + There are dozens of other types, most of them never seen in the wild. Start + by reading WinPef.h from the SDK, then the official documentation on types, + which is NOT on the SDK, but in the technet library, on "Windows Server + 2003 Deployment Guide" -> ... -> "Counter Types". (At + http://go.microsoft.com/fwlink?linkid=44341 or + http://technet.microsoft.com/en-us/library/cc785636(WS.10).aspx ) + Then back to the SDK. + + +TESTING + + See winperfcounterstest.wsf. + + Build winperfcountersprobe.exe with scons. + + WARNING: DON'T automate testing without considering that mongod.exe must be + installed, and that implies administrative privileges AND the potential to + mess badly with the registry. + + +USEFUL TOOLS + + perfmon.exe, typeperf.exe, wbemtest.exe, wmic.exe + + +DEVELOPERS BE WARNED + + Microsoft screwed this one up. + + This version 2.0 of perflib looks very nice, BUT it was unbelievably + painful and time consuming to deal with a documentation that is lacking, + obtuse and simply wrong at times; painful to do so many tests and debugging + to discover the many blatant bugs in perflib2; and painful to deal with + idiosyncratic things like lodctr/unlodctr. + + For such a core service, it's just not up to MS own standards. + + +HOTFIXES + + On Vista SP2 x86/x64 with NO hotfixes, winperfcounterstest.wsf passes, and + all manual scenarios tried run fine (perfmon, wmic, wbemtest, etc). + + Here are SOME hotfixes that I found that may be relevant to users and + developers. I've tested some of them, and found that some of the bugs I've + found are fixed, even if the KB doesn't seem to be related. + + KB970520 - The Wmiprvse.exe process creates a memory leak on a computer that + is running Windows Server 2008 if you remotely monitor this process by using + the WMI interface on a computer that is running Windows Server 2003 or + Windows XP + + KB970838 - The handle count and the memory usage of an application keep + increasing if the application calls PDH APIs to add and delete V2 performance + counters on a computer that is running Windows Server 2008 or Windows Vista + + KB2414106 - You cannot remotely query version 2.0 performance counters on a + computer that is running Windows Server 2008 or Windows Vista + + KB2581359 - The PdhEnumObjects function used together with the bRefresh + parameter does not function correctly in Windows 7 or in Windows Server + 2008 R2 + + KB2268318 - Duplicated data may be in the Performance log when you use the + Performance Logs and Alerts service to collect performance data in Windows + Server 2008, in Windows Vista, in Windows 7, or in Windows Server 2008 R2 + + KB2613988 - Changes to performance counters are not updated for at least 15 + minutes when you use WMI to query performance counter values in Windows 7 + or in Windows Server 2008 R2 + + +AUTHOR + + Erich Siedler + erich.siedler@gmail.com + diff --git a/src/mongo/db/modules/winperfcounters/mongod.man b/src/mongo/db/modules/winperfcounters/mongod.man new file mode 100644 index 0000000000000..fe31915686ad2 Binary files /dev/null and b/src/mongo/db/modules/winperfcounters/mongod.man differ diff --git a/src/mongo/db/modules/winperfcounters/winperfcounters.cpp b/src/mongo/db/modules/winperfcounters/winperfcounters.cpp new file mode 100644 index 0000000000000..3de785758679b --- /dev/null +++ b/src/mongo/db/modules/winperfcounters/winperfcounters.cpp @@ -0,0 +1,334 @@ +/** @file mongo/db/modules/winperfcounters/winperfcounters.cpp - Windows performance counters for mongod.exe */ + +/* + * Copyright (C) 2012 Erich Siedler + * + * 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 . + */ + +#if defined (_WIN32) + +#include "mongo/pch.h" + +#include "mongo/db/modules/winperfcounters/winperfcounters.h" +#include "winperfcounters_ctrpp.h" // generated from mongod.man by Windows SDK's ctrpp.exe during build + +#include + +#include "mongo/db/client.h" +#include "mongo/db/d_globals.h" +#include "mongo/db/stats/counters.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/concurrency/mutex.h" +#include "mongo/util/net/listen.h" +#include "mongo/util/ntservice.h" + +namespace mongo { + + WinPerfCounters::WinPerfCounters() + : Module( "Windows Performance Counters" ) + , _isInited( false ) + , _configEnableModule( false ) + , _clientsCnt( 0 ) + , _hTimer( NULL ) + , _hEndRun( NULL ) + , _setInstance( NULL ) + , _mutexCallback( "WinPerfCounters_Callback" ) { + + verify( ! _this ); + _this = this; + add_options()( "winPerfCounters", "enable windows performance counters" ); + } + + WinPerfCounters::~WinPerfCounters() { + shutdown(); + _this = 0; + } + + void WinPerfCounters::config( boost::program_options::variables_map& params ) { + if ( params.count( "winPerfCounters" ) ) { + _configEnableModule = true; + } + } + + // if init() fails we just disable this module and leave the system in a clean state + void WinPerfCounters::init() { + verify( ! _isInited ); + + if ( ! _configEnableModule ) { + LOG( 1 ) << "WinPerfCounters not enabled" << endl; + return; + } + + if ( NULL == ( _hEndRun = ::CreateEvent( /*security=*/ NULL, /*manual=*/ TRUE, /*state=*/ FALSE, /*name=*/ NULL ) ) ) { + error() << "WinPerfCounters::init() failed on CreateEvent(): " << ::GetLastError() << endl; + return; + } + + if ( NULL == ( _hTimer = ::CreateWaitableTimer( /*security=*/ NULL, /*manual=*/ FALSE, /*name=*/ NULL ) ) ) { + error() << "WinPerfCounters::init() failed on CreateWaitableTimer(): " << ::GetLastError() << endl; + wassert( ::CloseHandle( _hEndRun ) ); + _hEndRun = NULL; + return; + } + + { + // controlCallback() might get called right after Ctrpp_CounterInitialize(), lock will make it + // wait until we have finished initializing + SimpleMutex::scoped_lock lock( _this->_mutexCallback ); + + // Ctrpp_CounterInitialize() created by ctrpp.exe + PERF_MEM_ALLOC const no_custom_alloc = NULL; PERF_MEM_FREE const no_custom_free = NULL; PVOID const no_custom_arg = NULL; + ULONG ci_res = ! ERROR_SUCCESS; + if ( ERROR_SUCCESS != ( ci_res = Ctrpp_CounterInitialize( controlCallback, no_custom_alloc, no_custom_free, no_custom_arg ) ) ) { + error() << "WinPerfCounters::init() failed on Ctrpp_CounterInitialize(): " << ci_res << endl; + wassert( ::CloseHandle( _hEndRun ) ); + _hEndRun = NULL; + wassert( ::CloseHandle( _hTimer ) ); + _hTimer = NULL; + return; + } + + // make the instance name the same as the service name + _perf_instance_name = ServiceController::getServiceName(); + if ( _perf_instance_name.empty() ) { + // not running as a service; make a name unique to multiple instances, unlike to collide with + // mongodb services, and easy for the user to relate the name to the database + _perf_instance_name = L"Listening_" + std::wstring( boost::lexical_cast< std::wstring >( cmdLine.port ) ); + } + + // perf_instance_name + iindex uniquely identify an instance systemwide (the SDK is lacking and confusing about iindex) + ULONG const iindex = 0; + if ( NULL == ( _setInstance = ::PerfCreateInstance( Ctrpp_MongoDbProvider, &Ctrpp_MongoDbGuid, perf_instance_name().c_str(), iindex ) ) ) { + error() << "WinPerfCounters::init() failed on PerfCreateInstance(): " << ::GetLastError() << endl; + wassert( ::CloseHandle( _hEndRun ) ); + _hEndRun = NULL; + wassert( ::CloseHandle( _hTimer ) ); + _hTimer = NULL; + Ctrpp_CounterCleanup(); + return; + } + + _isInited = true; + } + + // start BackgroundJob + go(); + } + + void WinPerfCounters::shutdown() { + if ( ! _isInited ) + return; + + wassert( ::SetEvent( _hEndRun ) ); + wassert( wait( SHUTDOWN_TIMEOUT_MS ) ); + wassert( !running() ); + + wassert( ERROR_SUCCESS == ::PerfDeleteInstance( Ctrpp_MongoDbProvider, _setInstance ) ); + _setInstance = NULL; + Ctrpp_CounterCleanup(); + wassert( ::CloseHandle( _hEndRun ) ); + _hEndRun = NULL; + wassert( ::CloseHandle( _hTimer ) ); + _hTimer = NULL; + + _isInited = false; + } + + // BackgroundJob thread name + std::string WinPerfCounters::name() const { + return "winPerfCounters"; + } + + // BackgroundJob main + void WinPerfCounters::run() { + verify( _isInited ); + + log() << "WinPerfCounters starting: instance: " << toUtf8String( perf_instance_name() ) + << " update interval: " << UPDATE_INTERVAL_MS << " ms" << endl; + + Client::initThread( name().c_str() ); + updateCounters(); + + HANDLE handles[] = { _hEndRun, _hTimer }; + DWORD const IDX_HND_END = WAIT_OBJECT_0; DWORD const IDX_HND_TIMER = WAIT_OBJECT_0 + 1; + DWORD wait = WAIT_FAILED; + + while ( IDX_HND_TIMER == ( wait = ::WaitForMultipleObjects( 2, handles, /*wait all=*/ FALSE, INFINITE ) ) ) { + LOG( LL_UPDATE ) << "WinPerfCounters updating counters" << endl; + updateCounters(); + } + wassert( IDX_HND_END == wait ); + + cc().shutdown(); + } + + std::wstring WinPerfCounters::perf_instance_name() const { + return _perf_instance_name; + } + + void WinPerfCounters::startTimer() { + verify( NULL != _hTimer ); + + LARGE_INTEGER const signal_now = { 0 }; + PTIMERAPCROUTINE const no_completion_func = NULL; + LPVOID const no_arg = NULL; + BOOL const resume_FALSE = FALSE; // don't resume a suspended power system + wassert( ::SetWaitableTimer( _hTimer, &signal_now, UPDATE_INTERVAL_MS, no_completion_func, no_arg, resume_FALSE ) ); + } + + // Will be called from a thread (or more?) injected by Windows, must return in less than a second. + // Must be VERY careful with what is called. For example: no updateCounters(), no cc(). + // + // + // The SDK states that we can use PERF_ADD_COUNTER and PERF_REMOVE_COUNTER to start and stop updating + // the counters, but, at least in Vista SP2, perflib2 is very buggy in that respect. I've hit many + // scenarios in which a single or many PERF_ADD_COUNTER or PERF_REMOVE_COUNTER are missing. + // + // The Vista SP2/Server 2008 SP2 hotfix KB970838 fixes only one of such buggy behaviour. + // + // This implementation accounts for all observed bugs, no windows performance counter client will ever see + // a problem. There is one small downside: IF we already have/had one active client, AND we hit one of this + // windows bugs, then we will never stop updating the counters ( i.e., will behave like there is always one + // active client ). + ULONG WINAPI WinPerfCounters::controlCallback( ULONG requestCode, PVOID /*buffer*/, ULONG /*bufferSize*/ ) { + verify( _this ); + verify( NULL != _this->_hTimer ); + + SimpleMutex::scoped_lock lock( _this->_mutexCallback ); + + switch ( requestCode ) { + case PERF_ADD_COUNTER: + LOG( LL_CALLBACK ) << "WinPerfCounters received PERF_ADD_COUNTER" << endl; + if ( 1 == ++_this->_clientsCnt ) { + LOG( LL_STARTSTOP ) << "WinPerfCounters have clients, started updating counters" << endl; + _this->startTimer(); + }; + break; + + case PERF_REMOVE_COUNTER: + LOG( LL_CALLBACK ) << "WinPerfCounters received PERF_REMOVE_COUNTER" << endl; + { + int sc = --_this->_clientsCnt; + if ( 0 == sc ) { + LOG( LL_STARTSTOP ) << "WinPerfCounters have no more clients, stopped updating counters" << endl; + wassert( ::CancelWaitableTimer( _this->_hTimer ) ); + } + else if ( sc < 0 ) { + // received more REMOVEs than ADDs + warning() << "WinPerfCounters client tracking imbalance, collecting data permanently (harmless)" << endl; + _this->_clientsCnt = 1000 * 1000 * 1000; + } + } + break; + + case PERF_COLLECT_START: + if ( _this->_clientsCnt <= 0 ) { + warning() << "WinPerfCounters unexpected start, collecting data permanently (harmless)" << endl; + _this->_clientsCnt = 1000 * 1000 * 1000; + _this->startTimer(); + } + break; + } + wassert( _this->_clientsCnt >= 0 ); + return ERROR_SUCCESS; + } + + inline void WinPerfCounters::setULONG( ULONG counter_id, ULONG value ) { + wassert( ERROR_SUCCESS == ::PerfSetULongCounterValue( Ctrpp_MongoDbProvider, _setInstance, ( counter_id ), ( value ) ) ); + } + + inline void WinPerfCounters::setULONGLONG( ULONG counter_id, ULONGLONG value ) { + wassert( ERROR_SUCCESS == ::PerfSetULongLongCounterValue( Ctrpp_MongoDbProvider, _setInstance, ( counter_id ), ( value ) ) ); + } + + // To add new counters: read README, be light, don't throw, update tests + // + // Update the counters with the API ::PerfSetULong[Long]CounterValue(), ::PerfIncrementULong[Long]CounterValue(), + // ::PerfDecrementULong[Long]CounterValue(). + // Although the SDK says you can bypass these and access the raw counter in _setInstance, I currently recommend against it. + void WinPerfCounters::updateCounters() { + verify( _isInited ); + verify( NULL != _setInstance ); + + { + setULONG( MongoDb_Connections, connTicketHolder.used() ); + setULONG( MongoDb_ConnectionsAvailable, connTicketHolder.available() ); + } + + { + ULONGLONG in = networkCounter.getBytesIn(), out = networkCounter.getBytesOut(), req = networkCounter.getRequests(); + setULONGLONG( MongoDb_NetworkBytesIn, in ); + setULONGLONG( MongoDb_NetworkBytesInSec, in ); + setULONGLONG( MongoDb_NetworkBytesOut, out ); + setULONGLONG( MongoDb_NetworkBytesOutSec, out ); + setULONGLONG( MongoDb_NetworkRequests, req ); + setULONGLONG( MongoDb_NetworkRequestsSec, req ); + } + + { + int w=0, r=0; + Client::recommendedYieldMicros( &w , &r ); + setULONG( MongoDb_gL_CurrentQueueTotal, w + r ); + setULONG( MongoDb_gL_CurrentQueueReaders, r ); + setULONG( MongoDb_gL_CurrentQueueWriters, w ); + } + + { + int w=0, r=0; + Client::getActiveClientCount( w , r ); + setULONG( MongoDb_gL_ClientsTotal, w + r ); + setULONG( MongoDb_gL_ClientsReaders, r ); + setULONG( MongoDb_gL_ClientsWriters, w ); + } + + { + ULONG regular = assertionCount.regular; + ULONG warning = assertionCount.warning; + ULONG msg = assertionCount.msg; + ULONG user = assertionCount.user; + ULONGLONG total = regular + warning + msg + user; + setULONGLONG( MongoDb_AssertsTotal, total ); + setULONGLONG( MongoDb_AssertsTotalPerSec, total ); + setULONG( MongoDb_AssertsRollovers, assertionCount.rollovers ); + setULONG( MongoDb_AssertsRegular, regular ); + setULONG( MongoDb_AssertsWarning, warning ); + setULONG( MongoDb_AssertsMsg, msg ); + setULONG( MongoDb_AssertsUser, user ); + } + + { + unsigned insert = globalOpCounters.getInsert()->get(); + unsigned query = globalOpCounters.getQuery()->get(); + unsigned update = globalOpCounters.getUpdate()->get(); + unsigned del = globalOpCounters.getDelete()->get(); + unsigned getmore = globalOpCounters.getGetMore()->get(); + unsigned command = globalOpCounters.getCommand()->get(); + ULONGLONG total = insert + query + update + del + getmore + command; + setULONGLONG( MongoDb_OpPerSec, total ); + setULONG( MongoDb_OpInsertPerSec, insert ); + setULONG( MongoDb_OpQueryPerSec, query ); + setULONG( MongoDb_OpUpdatePerSec, update ); + setULONG( MongoDb_OpDeletePerSec, del ); + setULONG( MongoDb_OpGetMorePerSec, getmore ); + setULONG( MongoDb_OpCommandPerSec, command ); + } + } + + + + WinPerfCounters* volatile WinPerfCounters::_this = 0; + +} // namespace mongo + +#endif diff --git a/src/mongo/db/modules/winperfcounters/winperfcounters.h b/src/mongo/db/modules/winperfcounters/winperfcounters.h new file mode 100644 index 0000000000000..81707024385d2 --- /dev/null +++ b/src/mongo/db/modules/winperfcounters/winperfcounters.h @@ -0,0 +1,110 @@ +/** @file mongo/db/modules/winperfcounters/winperfcounters.h - Windows performance counters for mongod.exe */ + +/* + * Copyright (C) 2012 Erich Siedler + * + * 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 . + */ + +#pragma once + +#if defined (_WIN32) + +#include "mongo/db/module.h" +#include "mongo/util/background.h" + +namespace mongo { + + /** + * Windows performance counters for mongod.exe, implemented as a Module. + * + * This module adds a command line option: "--winPerfCounters". + * + * If the option is not defined at Module::config() time, this module does nothing, + * costs nothing. + * If defined, a new instance of the counters defined in the XML file "mongod.man" is made + * available on the OS. + * The counters are updated only when there are active Windows performance counter clients + * requesting these counters. The counters will then be updated about once every second, + * with very little/negligible CPU cost. + * When there are no active clients this module should have zero CPU cost. + * + * There can exist only one instance of this class at any time. + * + * Do read the README for important information on how to use, how to create new counters, + * implementation details and caveats. + */ + class WinPerfCounters : public Module, BackgroundJob { + public: + WinPerfCounters(); + virtual ~WinPerfCounters(); + + // Module interface + virtual void config( boost::program_options::variables_map& params ); + virtual void init(); + virtual void shutdown(); + + protected: + // BackgroundJob interface + virtual std::string name() const; + virtual void run(); + + // not private for testability + PPERF_COUNTERSET_INSTANCE _setInstance; + + private: + // LOG() level for start/stop updating counters + static int const LL_STARTSTOP = 1; + + // LOG() level for controlCallback() ADD/REMOVE events + static int const LL_CALLBACK = 6; + + // LOG() level for updateCounters() + static int const LL_UPDATE = 7; + + // how often to updateCounters() + // per Windows SDK, clients must wait a minimum of 1 sec between samples + static unsigned int const UPDATE_INTERVAL_MS = 950; + + // shutdown() is expected to complete in a few ms, this is a safeguard + static unsigned int const SHUTDOWN_TIMEOUT_MS = 10*1000; + + // the only instance of this class + static WinPerfCounters* volatile _this; + + bool volatile _isInited; // ready to go() or already running() + bool _configEnableModule; // --winPerfCounters + int volatile _clientsCnt; // number of active perf counter clients + HANDLE _hTimer; // waitable timer, updateCounters() heartbeat + HANDLE _hEndRun; // event, signals run() to end + std::wstring _perf_instance_name; // the instance name that clients see + + // protects controlCallback() + SimpleMutex _mutexCallback; + + void startTimer(); + + // PERFLIBREQUEST type function, called by thread injected by Windows + static ULONG WINAPI controlCallback( ULONG requestCode, PVOID buffer, ULONG bufferSize ); + + void setULONG( ULONG counter_id, ULONG value ); + void setULONGLONG( ULONG counter_id, ULONGLONG value ); + + // virtual for testability + virtual std::wstring perf_instance_name() const; + virtual void updateCounters(); + }; + +} // namespace mongo + +#endif diff --git a/src/mongo/db/modules/winperfcounters/winperfcounters_instance.cpp b/src/mongo/db/modules/winperfcounters/winperfcounters_instance.cpp new file mode 100644 index 0000000000000..a2b94236d5b55 --- /dev/null +++ b/src/mongo/db/modules/winperfcounters/winperfcounters_instance.cpp @@ -0,0 +1,31 @@ +/** @file mongo/db/modules/winperfcounters/winperfcounters_instance.cpp */ + +/* + * Copyright (C) 2012 Erich Siedler + * + * 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 . + */ + +#if defined (_WIN32) + +#include "mongo/pch.h" +#include "mongo/db/modules/winperfcounters/winperfcounters.h" + +namespace mongo { + + // instantiate and mongo will automagically pick us up + WinPerfCounters moduleWinPerfCounters; + +} + +#endif diff --git a/src/mongo/db/modules/winperfcounters/winperfcountersprobe.cpp b/src/mongo/db/modules/winperfcounters/winperfcountersprobe.cpp new file mode 100644 index 0000000000000..631e70039d683 --- /dev/null +++ b/src/mongo/db/modules/winperfcounters/winperfcountersprobe.cpp @@ -0,0 +1,187 @@ +/** @file mongo/db/modules/winperfcounters/winperfcountersprobe.cpp */ + +/* + * Copyright (C) 2012 Erich Siedler + * + * 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 . + */ + +/* + * To test new counters, just update CounterNameInfoMap::CounterNameInfoMap() +*/ + +#include "mongo/db/modules/winperfcounters/winperfcounters.h" +#include "winperfcounters_ctrpp.h" // generated from mongod.man by Windows SDK's ctrpp.exe during build + +#include +#include +#include +#include + +#include "mongo/db/CmdLine.h" + +using std::cin; +using std::cout; +using std::string; +using std::wstring; + +enum CounterType { + CT_INVALID, + CT_ULONG, + CT_ULONGLONG +}; + +struct CounterInfo { + unsigned int id; + CounterType type; + + CounterInfo() : id( 0 ), type( CT_INVALID ) {} + CounterInfo( unsigned int _id, CounterType _type ) : id( _id ), type( _type ) {} +}; + +// WMI name mangling: +// "Some Counter/sec" -> "SomeCounterPersec" +// +// XML manifest type to windows type: +// perf_counter_rawcount = CT_ULONG +// perf_counter_counter = CT_ULONG +// perf_counter_bulk_count = CT_ULONGLONG +// perf_counter_large_rawcounter = CT_ULONGLONG +class CounterNameInfoMap : public std::map< string, CounterInfo > { +public: + CounterNameInfoMap() { + (*this)[ "Connections" ] = CounterInfo( MongoDb_Connections, CT_ULONG ); + (*this)[ "ConnectionsAvailable" ] = CounterInfo( MongoDb_ConnectionsAvailable, CT_ULONG ); + (*this)[ "NetworkBytesIn" ] = CounterInfo( MongoDb_NetworkBytesIn, CT_ULONGLONG ); + (*this)[ "NetworkBytesInPersec" ] = CounterInfo( MongoDb_NetworkBytesInSec, CT_ULONGLONG ); + (*this)[ "NetworkBytesOut" ] = CounterInfo( MongoDb_NetworkBytesOut, CT_ULONGLONG ); + (*this)[ "NetworkBytesOutPersec" ] = CounterInfo( MongoDb_NetworkBytesOutSec, CT_ULONGLONG ); + (*this)[ "NetworkRequests" ] = CounterInfo( MongoDb_NetworkRequests, CT_ULONGLONG ); + (*this)[ "NetworksRequestsPersec" ] = CounterInfo( MongoDb_NetworkRequestsSec, CT_ULONGLONG ); + (*this)[ "globalLockCurrentQueue" ] = CounterInfo( MongoDb_gL_CurrentQueueTotal, CT_ULONG ); + (*this)[ "globalLockCurrentQueueReaders" ] = CounterInfo( MongoDb_gL_CurrentQueueReaders, CT_ULONG ); + (*this)[ "globalLockCurrentQueueWriters" ] = CounterInfo( MongoDb_gL_CurrentQueueWriters, CT_ULONG ); + (*this)[ "globalLockActiveClients" ] = CounterInfo( MongoDb_gL_ClientsTotal, CT_ULONG ); + (*this)[ "globalLockActiveClientsReaders" ] = CounterInfo( MongoDb_gL_ClientsReaders, CT_ULONG ); + (*this)[ "globalLockActiveClientsWriters" ] = CounterInfo( MongoDb_gL_ClientsWriters, CT_ULONG ); + (*this)[ "Asserts" ] = CounterInfo( MongoDb_AssertsTotal, CT_ULONGLONG ); + (*this)[ "AssertsPersec" ] = CounterInfo( MongoDb_AssertsTotalPerSec, CT_ULONGLONG ); + (*this)[ "AssertsRollovers" ] = CounterInfo( MongoDb_AssertsRollovers, CT_ULONG ); + (*this)[ "AssertsRegular" ] = CounterInfo( MongoDb_AssertsRegular, CT_ULONG ); + (*this)[ "AssertsWarning" ] = CounterInfo( MongoDb_AssertsWarning, CT_ULONG ); + (*this)[ "AssertsMessage" ] = CounterInfo( MongoDb_AssertsMsg, CT_ULONG ); + (*this)[ "AssertsUser" ] = CounterInfo( MongoDb_AssertsUser, CT_ULONG ); + (*this)[ "OpCountersPersec" ] = CounterInfo( MongoDb_OpPerSec, CT_ULONGLONG ); + (*this)[ "OpCountersInsertsPersec" ] = CounterInfo( MongoDb_OpInsertPerSec, CT_ULONG ); + (*this)[ "OpCountersQueriesPersec" ] = CounterInfo( MongoDb_OpQueryPerSec, CT_ULONG ); + (*this)[ "OpCountersUpdatesPersec" ] = CounterInfo( MongoDb_OpUpdatePerSec, CT_ULONG ); + (*this)[ "OpCountersDeletesPersec" ] = CounterInfo( MongoDb_OpDeletePerSec, CT_ULONG ); + (*this)[ "OpCountersGetMorePersec" ] = CounterInfo( MongoDb_OpGetMorePerSec, CT_ULONG ); + (*this)[ "OpCountersCommandsPersec" ] = CounterInfo( MongoDb_OpCommandPerSec, CT_ULONG ); + } + +private: + CounterNameInfoMap( CounterNameInfoMap const& ); + CounterNameInfoMap const& operator=( CounterNameInfoMap const& ); +}; + +namespace mongo { + + class WinPerfCountersProbe : public WinPerfCounters { + public: + WinPerfCountersProbe( wstring const& name ) : _perf_instance_name( name ) {} + + // valueset_line: "PerformanceMonitorClients=123;ConnectionsCurrent=456" + void setValues( string const& valueset_line ) { + CounterNameInfoMap counters_map; + typedef boost::split_iterator< string::const_iterator > it_type; + + for ( it_type it = boost::make_split_iterator( valueset_line, boost::token_finder( boost::is_any_of( "=;" ) ) ); ! it.eof(); ) { + string name( boost::copy_range< string >( *it++ ) ); + verify( ! it.eof() ); + string value( boost::copy_range< string >( *it++ ) ); + + switch( counters_map[ name ].type ) { + case CT_ULONG: + ::PerfSetULongCounterValue( Ctrpp_MongoDbProvider, _setInstance, counters_map[ name ].id, boost::lexical_cast< ULONG >( value ) ); + break; + case CT_ULONGLONG: + ::PerfSetULongLongCounterValue( Ctrpp_MongoDbProvider, _setInstance, counters_map[ name ].id, boost::lexical_cast< ULONGLONG >( value ) ); + break; + default: + verify( false ); + } + } + } + + protected: + virtual wstring perf_instance_name() const { + return _perf_instance_name; + } + + private: + wstring _perf_instance_name; + virtual void updateCounters() {} // we will update manually with setValues() + }; + + // make the linker happy + CmdLine cmdLine; + bool initService() { verify( false ); return false; } + +} // namespace mongo + +void ok() { + std::cout << "OK\n"; +} + +int wmain( int argc, wchar_t const* argv[] ) { + if ( 2 != argc ) { + cout << "Parameter error, use: program.exe \n"; + return 1; + } + + // boot WinPerfCounters module + namespace po = boost::program_options; + po::variables_map vm; + vm.insert( po::variables_map::value_type( "winPerfCounters", po::variable_value() ) ); + mongo::WinPerfCountersProbe probe( argv[1] ); + probe.config( vm ); + + // respond to test commands + string cmd; + do { + cout << "READY\n"; + cmd.clear(); + cin >> cmd; + + if ( "INIT" == cmd ) { + // init() returns after PerfCreateInstance(). Although the SDK doesn't say, we are assuming + // that this perfcounters instance is immediately visible systemwide + probe.init(); + ok(); + } + else if ( "SHUTDOWN" == cmd ) { + probe.shutdown(); + ok(); + } + else if ( boost::starts_with( cmd, "SET:" ) ) { + probe.setValues( cmd.substr( 4 ) ); // cmd without "SET:" + ok(); + } + else { + cout << "UNKNOWN\n"; + } + } while ( cin.good() && "QUIT" != cmd ); + + return 0; +} diff --git a/src/mongo/db/modules/winperfcounters/winperfcounterstest.wsf b/src/mongo/db/modules/winperfcounters/winperfcounterstest.wsf new file mode 100644 index 0000000000000..d9e926d4787ce --- /dev/null +++ b/src/mongo/db/modules/winperfcounters/winperfcounterstest.wsf @@ -0,0 +1,413 @@ + + + + + +Indirectly tests windows performance counters implemented in mongod.exe by +using a probe as the provider, and the WMI infrastructure to read the data. + +This exercises the C++ implementation, the XML manifest definitions, and the +installation of mongod.exe counters. + +The actual real data gathering ( WinPerfCounters::updateCounters() ) is not +tested here. + +All 4 combinations of 32/64-bits probe and 32/64-bits cscript.exe ( i.e. +\Windows\System32\cscript.exe and \Windows\SysWOW64\cscript.exe ) should be +run. The installed mongod.exe should match the probe version. + +It can happen, especially after upgrading mongod.exe/probe.exe ( i.e. while +developing new counters ), that WMI doesn't see the new counters. In that +case running "winmgmt.exe /resyncperf" and restarting the WMI service +usually does the trick. See README (also about hotfixes for Windows). + +How to run: + + .use cscript.exe + .be a member of the "Performance Monitor Users" group + or have administrative rights + .install mongod.exe, see README + +How to add new counters: + + .add their names to the gMongoCounters variable + + + + +Example: cscript winperfcounterstest.wsf ..\..\winperfcountersprobe.exe + + + + + + + + + + + + + diff --git a/src/mongo/db/stats/counters.h b/src/mongo/db/stats/counters.h index 1296e4d5fa186..cb9c78d6b623c 100644 --- a/src/mongo/db/stats/counters.h +++ b/src/mongo/db/stats/counters.h @@ -138,6 +138,11 @@ namespace mongo { class NetworkCounter { public: NetworkCounter() : _bytesIn(0), _bytesOut(0), _requests(0), _overflows(0) {} + + long long getBytesIn() const { return _bytesIn; } + long long getBytesOut() const { return _bytesOut; } + long long getRequests() const { return _requests; } + void hit( long long bytesIn , long long bytesOut ); void append( BSONObjBuilder& b ); private: diff --git a/src/mongo/platform/windows_basic.h b/src/mongo/platform/windows_basic.h index 07776d8b46df9..d247b0ed5ca22 100644 --- a/src/mongo/platform/windows_basic.h +++ b/src/mongo/platform/windows_basic.h @@ -15,4 +15,6 @@ # include # include # include +# include +# include #endif diff --git a/src/mongo/util/ntservice.cpp b/src/mongo/util/ntservice.cpp index 92dfa7d8990e7..e8672b4afc43a 100644 --- a/src/mongo/util/ntservice.cpp +++ b/src/mongo/util/ntservice.cpp @@ -416,6 +416,7 @@ namespace mongo { _serviceName = serviceName; _serviceCallback = startService; + // lpServiceName is ignored when SERVICE_WIN32_OWN_PROCESS, but cannot be NULL SERVICE_TABLE_ENTRY dispTable[] = { { (LPTSTR)serviceName.c_str(), (LPSERVICE_MAIN_FUNCTION)ServiceController::initService }, { NULL, NULL } @@ -457,6 +458,15 @@ namespace mongo { } void WINAPI ServiceController::initService( DWORD argc, LPTSTR *argv ) { + // the SDK says argc can be 0, but in many code examples from MS it's assumed that the service name + // is always present at argv[0]; testing on Vista SP2 corroborates that + if ( 1 <= argc ) { + verify( argv && argv[0] ); + _serviceName = argv[0]; + verify( ! _serviceName.empty() ); + } + + // lpServiceName is not verified when SERVICE_WIN32_OWN_PROCESS _statusHandle = RegisterServiceCtrlHandler( _serviceName.c_str(), serviceCtrl ); if ( !_statusHandle ) return; @@ -492,6 +502,10 @@ namespace mongo { } } + std::wstring ServiceController::getServiceName() { + return _serviceName; + } + } // namespace mongo #endif diff --git a/src/mongo/util/ntservice.h b/src/mongo/util/ntservice.h index 4492c317e27e2..57900a4381401 100644 --- a/src/mongo/util/ntservice.h +++ b/src/mongo/util/ntservice.h @@ -61,6 +61,8 @@ namespace mongo { static void WINAPI initService( DWORD argc, LPTSTR *argv ); static void WINAPI serviceCtrl( DWORD ctrlCode ); + static std::wstring getServiceName(); + protected: static std::wstring _serviceName; static SERVICE_STATUS_HANDLE _statusHandle;