//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1998 - 2000. // // File: LowRes.cxx // // Contents: Default low-resource detection // // Classes: CLowRes // CUserActivityMonitor // // History: 21-Jul-98 KyleP Created // //---------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include "lowres.hxx" CLowRes::CLowRes( CCiFrameworkParams & params ) : _cRefs(1), _params( params ), _UserMon( ) { // // Compute pages-per-meg for use in low memory computations. // SYSTEM_BASIC_INFORMATION Basic; NTSTATUS Status = NtQuerySystemInformation( SystemBasicInformation, &Basic, sizeof(Basic), 0 ); if ( SUCCEEDED(Status) ) { _ulPagesPerMeg = 1024*1024 / Basic.PageSize; _ulPageSize = Basic.PageSize; } else { _ulPagesPerMeg = 1024*1024 / PAGE_SIZE; _ulPageSize = PAGE_SIZE; } } //+------------------------------------------------------------------------- // // Method: CLowRes::AddRef // // Synopsis: Increments refcount // // History: 21-Jul-1998 KyleP Created // //-------------------------------------------------------------------------- ULONG STDMETHODCALLTYPE CLowRes::AddRef() { return InterlockedIncrement( &_cRefs ); } //+------------------------------------------------------------------------- // // Method: CLowRes::Release // // Synopsis: Decrement refcount. Delete if necessary. // // History: 21-Jul-1998 KyleP Created // //-------------------------------------------------------------------------- ULONG STDMETHODCALLTYPE CLowRes::Release() { Win4Assert( _cRefs > 0 ); ULONG uTmp = InterlockedDecrement( (long *) &_cRefs ); if ( 0 == uTmp ) delete this; return uTmp; } //+------------------------------------------------------------------------- // // Method: CLowRes::QueryInterface // // Synopsis: Rebind to other interface // // Arguments: [riid] -- IID of new interface // [ppvObject] -- New interface * returned here // // Returns: S_OK if bind succeeded, E_NOINTERFACE if bind failed // // History: 21-Jul-1998 KyleP Created // //-------------------------------------------------------------------------- SCODE STDMETHODCALLTYPE CLowRes::QueryInterface( REFIID riid, void ** ppvObject) { IUnknown *pUnkTemp = 0; SCODE sc = S_OK; *ppvObject = 0; if ( IID_ICiCResourceMonitor == riid ) pUnkTemp = (IUnknown *)(ICiCResourceMonitor *) this; else if ( IID_IUnknown == riid ) pUnkTemp = (IUnknown *) this; else sc = E_NOINTERFACE; *ppvObject = (void *) pUnkTemp; if( 0 != pUnkTemp ) pUnkTemp->AddRef(); return sc; } //QueryInterface //+------------------------------------------------------------------------- // // Method: CLowRes::IsMemoryLow, public // // Returns: S_OK if memory is low, S_FALSE if it is not. // // History: 21-Jul-1998 KyleP Created // //-------------------------------------------------------------------------- SCODE CLowRes::IsMemoryLow() { SYSTEM_PERFORMANCE_INFORMATION PerfInfo; NTSTATUS Status = NtQuerySystemInformation( SystemPerformanceInformation, &PerfInfo, sizeof(PerfInfo), 0 ); if ( SUCCEEDED(Status) ) { ULONG ulFreeMeg = (ULONG)((PerfInfo.CommitLimit - PerfInfo.CommittedPages) / _ulPagesPerMeg); if ( ulFreeMeg < _params.GetMinWordlistMemory() ) Status = S_OK; else Status = S_FALSE; } else Status = E_FAIL; return Status; } //+------------------------------------------------------------------------- // // Method: CLowRes::IsBatterLow, public // // Returns: S_OK if battery power is low, S_FALSE if it is not. // // History: 21-Jul-1998 KyleP Created // //-------------------------------------------------------------------------- SCODE CLowRes::IsBatteryLow() { SCODE sc = S_OK; SYSTEM_POWER_STATUS PowerStatus; ULONG ulMinBattery = _params.GetMinWordlistBattery(); if ( 0 == ulMinBattery ) sc = S_FALSE; else if ( GetSystemPowerStatus( &PowerStatus ) ) { if ( ( AC_LINE_ONLINE == PowerStatus.ACLineStatus ) || ( ( BATTERY_FLAG_UNKNOWN != PowerStatus.BatteryFlag ) && ( 0 == (PowerStatus.BatteryFlag & BATTERY_FLAG_NO_BATTERY) ) && ( BATTERY_PERCENTAGE_UNKNOWN != PowerStatus.BatteryLifePercent ) && ( PowerStatus.BatteryLifePercent > ulMinBattery ) ) ) { sc = S_FALSE; } } else { ciDebugOut(( DEB_IWARN, "GetSystemPowerStatus returned %u\n", GetLastError() )); sc = HRESULT_FROM_WIN32( GetLastError() ); } return sc; } //+------------------------------------------------------------------------- // // Method: CLowRes::IsOnBatterPower, public // // Returns: S_OK if on battery power is low, S_FALSE if it is not. // // History: 01-Oct-2000 dlee Created // //-------------------------------------------------------------------------- SCODE CLowRes::IsOnBatteryPower() { SCODE sc = S_OK; SYSTEM_POWER_STATUS PowerStatus; if ( GetSystemPowerStatus( &PowerStatus ) ) { if ( AC_LINE_ONLINE == PowerStatus.ACLineStatus ) sc = S_FALSE; } else { ciDebugOut(( DEB_IWARN, "GetSystemPowerStatus returned %u\n", GetLastError() )); sc = HRESULT_FROM_WIN32( GetLastError() ); } return sc; } //IsOnBatteryPower //+------------------------------------------------------------------------- // // Method: CLowRes::IsUserActive, public // // Arguments: [fCheckLongTerm] - TRUE if long-term activity should be checked. // Otherwise, short-term activity is checked. // // Returns: S_OK if user is typing or mousing, S_FALSE if it is not. // // History: 29 Jul 1998 AlanW Created // //-------------------------------------------------------------------------- SCODE CLowRes::IsUserActive( BOOL fCheckLongTerm) { const cIdleThreshold = 5; SCODE sc = S_OK; const ULONG ulIdleDetectInterval = _params.GetWordlistUserIdle() * 1000; if ( 0 == ulIdleDetectInterval ) sc = S_FALSE; else if ( fCheckLongTerm ) { if ( _UserMon.GetUserActivity( ulIdleDetectInterval ) <= cIdleThreshold ) sc = S_FALSE; } else { if ( _UserMon.GetUserActivity( 500 ) == 0 ) sc = S_FALSE; } return sc; } //+------------------------------------------------------------------------- // // Method: CLowRes::SampleUserActivity, public // // Returns: S_OK // // History: 29 Jul 1998 AlanW Created // //-------------------------------------------------------------------------- SCODE CLowRes::SampleUserActivity() { _UserMon.SampleUserActivity(); return S_OK; } //+------------------------------------------------------------------------- // // Method: CLowRes::IsIoHigh, public // // Returns: S_OK if system is in a high i/o state, S_FALSE if it is not. // // History: 21-Jul-1998 KyleP Created // //-------------------------------------------------------------------------- SCODE CLowRes::IsIoHigh( BOOL * pfAbort ) { // // If the user doesn't care about checking IO, don't spend any time here // if ( 0xffffffff == _params.GetMaxWordlistIo() ) return S_FALSE; unsigned const cSecSample = 5; SCODE scRet = S_FALSE; SYSTEM_PERFORMANCE_INFORMATION PerfInfo[2]; NTSTATUS Status = NtQuerySystemInformation( SystemPerformanceInformation, &PerfInfo[0], sizeof(PerfInfo), 0 ); if ( NT_SUCCESS(Status) ) { // // Alertable sleep. Sleep for about 5 seconds total in 200ms increments. // // NOTE: When it's about time for the tick count to wrap, dwEndTicks // could be less than the initial value of dwSleepTicks, which // simply means that we could misdiagnose whether the machine is // busy about once in 49 days. // DWORD dwSleepTicks = GetTickCount(); DWORD dwEndTicks = dwSleepTicks + cSecSample * 1000; while ( dwSleepTicks < dwEndTicks ) { if ( *pfAbort ) break; _UserMon.SampleUserActivity(); DWORD dwCurrentTicks = GetTickCount(); if ( dwCurrentTicks >= dwEndTicks ) break; dwSleepTicks += 200; if ( dwCurrentTicks < dwSleepTicks ) { //Win4Assert( dwSleepTicks - dwCurrentTicks <= 200 ); Sleep( dwSleepTicks - dwCurrentTicks ); } } Status = NtQuerySystemInformation( SystemPerformanceInformation, &PerfInfo[1], sizeof(PerfInfo), 0 ); if ( NT_SUCCESS(Status) ) { Win4Assert( PerfInfo[1].IoReadTransferCount.QuadPart >= PerfInfo[0].IoReadTransferCount.QuadPart ); Win4Assert( PerfInfo[1].IoWriteTransferCount.QuadPart >= PerfInfo[0].IoWriteTransferCount.QuadPart ); Win4Assert( PerfInfo[1].IoOtherTransferCount.QuadPart >= PerfInfo[0].IoOtherTransferCount.QuadPart ); Win4Assert( PerfInfo[1].PageReadCount >= PerfInfo[0].PageReadCount ); Win4Assert( PerfInfo[1].CacheReadCount >= PerfInfo[0].CacheReadCount ); Win4Assert( PerfInfo[1].DirtyPagesWriteCount >= PerfInfo[0].DirtyPagesWriteCount ); Win4Assert( PerfInfo[1].MappedPagesWriteCount >= PerfInfo[0].MappedPagesWriteCount ); LONGLONG cIo = PerfInfo[1].IoReadTransferCount.QuadPart - PerfInfo[0].IoReadTransferCount.QuadPart; cIo += PerfInfo[1].IoWriteTransferCount.QuadPart - PerfInfo[0].IoWriteTransferCount.QuadPart; // // This doesn't work when a Jaz drive is in the system. // //cIo += PerfInfo[1].IoOtherTransferCount.QuadPart - PerfInfo[0].IoOtherTransferCount.QuadPart; cIo += (PerfInfo[1].PageReadCount - PerfInfo[0].PageReadCount) * _ulPageSize; cIo += (PerfInfo[1].CacheReadCount - PerfInfo[0].CacheReadCount) * _ulPageSize; cIo += (PerfInfo[1].DirtyPagesWriteCount - PerfInfo[0].DirtyPagesWriteCount) * _ulPageSize; cIo += (PerfInfo[1].MappedPagesWriteCount - PerfInfo[0].MappedPagesWriteCount) * _ulPageSize; if ( cIo >= _params.GetMaxWordlistIo() * 1024 * cSecSample ) { ciDebugOut(( DEB_ITRACE, "IsIoHigh: %u bytes in %u seconds is HIGH (more than %u bytes/sec)\n", cIo, cSecSample, _params.GetMaxWordlistIo() * 1024 )); scRet = S_OK; } } } return scRet; } //+------------------------------------------------------------------------- // // Method: CUserActivityMonitor::SampleUserActivity, public // // Synopsis: Check to see if the user has used the keyboard or mouse // // Returns: - nothing - // // History: 30 Jul 1998 AlanW Created // //-------------------------------------------------------------------------- void CUserActivityMonitor::SampleUserActivity() { #if CIDBG == 1 if ( 0 == _tid ) _tid = GetCurrentThreadId(); else Win4Assert( GetCurrentThreadId() == _tid ); #endif // CIDBG == 1 LASTINPUTINFO ii; memset(&ii, 0, sizeof(ii)); ii.cbSize = sizeof(ii); GetLastInputInfo( &ii ); DWORD dwNow = GetTickCount(); SetInputFlag( ii.dwTime ); SetSampleFlag( dwNow ); if (IsBufferEmpty()) { Win4Assert( _iTail == 0 ); _adwSamples[_iTail++] = _dwLastInputTime = ii.dwTime; _adwSamples[_iTail] = _dwLastSampleTime = dwNow; return; } #if CIDBG == 1 if ( (Ticks(dwNow) - Ticks(_dwLastSampleTime)) > 5000 ) { DWORD dwPeriod = Ticks(dwNow) - Ticks(_dwLastSampleTime); ciDebugOut(( DEB_IWARN, "SampleUserActivity, WARNING - freq. too low. Interval = %u.%03u\n", dwPeriod/1000, dwPeriod%1000 )); } #endif // CIDBG == 1 if (ii.dwTime == _dwLastInputTime) { // // No input events since last time we looked. Just overwrite the last // sample time with the current one. // Win4Assert( IsSample( _adwSamples[_iTail] ) ); #if 0 //CIDBG == 1 note: this can mess up ordering of samples in buffer if ( _adwSamples[_iTail] != dwNow ) Add( dwNow ); // always add the sample time for analysis #else _adwSamples[_iTail] = dwNow; #endif // CIDBG == 1 } else { Win4Assert( IsSample( _adwSamples[_iTail] ) ); // The following could happen if an input event happens between // the calls to GetLastInputInfo and GetTickCount above. if ( Ticks( ii.dwTime ) < Ticks( _adwSamples[_iTail] ) ) { _adwSamples[_iTail] = ii.dwTime - 1; Win4Assert( IsSample( _adwSamples[_iTail] ) ); } Add( ii.dwTime ); Add( dwNow ); } _dwLastInputTime = ii.dwTime; _dwLastSampleTime = dwNow; } //+------------------------------------------------------------------------- // // Method: CUserActivityMonitor::GetUserActivity, public // // Synopsis: Return an indication of the activity level of the interactive user. // // Arguments: [dwTickCount] - number of ticks to consider for activity // // Returns: ULONG - number of input events detected over the interval // // Notes: This may behave oddly around the time when the tick count // overflows, but since that may just lead to some misdaiganosis // for a few minutes every 7 weeks, it's not worth worrying about. // // History: 30 Jul 1998 AlanW Created // //-------------------------------------------------------------------------- ULONG CUserActivityMonitor::GetUserActivity(ULONG dwTickCount) { DWORD dwStart = Ticks(_dwLastSampleTime) - dwTickCount; // // If the interval is very short, just return whether any input // has occurred since that time. // if ( dwTickCount <= 3000 ) { SampleUserActivity(); if ( Ticks(_dwLastInputTime) <= dwStart) return 0; else return 1; } // // Quick return if there has been no activity in the interval. // if ( Ticks(_dwLastInputTime) <= dwStart) return 0; #if CIDBG == 1 Analyze( 0x1000 ); #endif // CIDBG == 1 DWORD dwFirstSample = 0; BOOL fFullInterval = FALSE; ULONG cInputEvent = 0, cTotalEvents = 0; for (unsigned i = _iHead; i != _iTail; i = Next(i)) { DWORD dw = _adwSamples[i]; if (Ticks(dw) < dwStart) { fFullInterval = TRUE; // // If an input event and a sample time bracket the start of // the interval, we can consider the input event as the first // sample because we know that the user gave no input in that time. // if ( IsInput(dw) && Ticks(_adwSamples[ Next(i) ]) >= dwStart ) dwFirstSample = dw; continue; } else if (0 == dwFirstSample) dwFirstSample = dw; if (IsInput(dw)) cInputEvent++; cTotalEvents++; } #if CIDBG == 1 if (! fFullInterval) { ciDebugOut(( DEB_IWARN, "GetUserActivity, WARNING - sampling freq. too high! missed %d\n", dwFirstSample - dwStart )); } if (2 == cTotalEvents) { ciDebugOut(( DEB_IWARN, "GetUserActivity, WARNING - sampling frequency too low\n" )); } #endif // CIDBG == 1 DWORD dwInterval = Ticks(_dwLastSampleTime) - Ticks(dwFirstSample); // Scale the results if we don't have samples for 80% of the requested time if (dwInterval*100 < dwTickCount*80) { // Scale the count found to fit the full interval ciDebugOut(( DEB_IWARN, "GetUserActivity, need to scale count %u %u\n", dwInterval, dwTickCount )); cInputEvent *= dwTickCount; if (dwInterval) cInputEvent /= dwInterval; } return cInputEvent; } #if CIDBG == 1 inline void PrintTicks( ULONG infolevel, DWORD dwTicks ) { ciDebugOut(( infolevel|DEB_NOCOMPNAME, "%3u.%03u", dwTicks/1000, dwTicks%1000 )); } #define SHORT_TERM_IDLE 500 #define LONG_TERM_THRESHOLD 20 #define LONG_TERM_INTERVAL (120 * 1000) #define MID_TERM_THRESHOLD 10 #define MID_TERM_INTERVAL (30 * 1000) void CUserActivityMonitor::Analyze( ULONG infolevel ) { if (_iSnap == _iTail) return; ciDebugOut(( infolevel, "\t----\n" )); for (unsigned i = _iSnap; i != _iTail; i = Next(i)) { DWORD dw1 = _adwSamples[i]; DWORD dw2 = _adwSamples[Next(i)]; if (IsInput(dw1)) { ciDebugOut(( infolevel|DEB_NOCOMPNAME, "%u", dw1 )); } else { Win4Assert( Ticks(dw1) <= Ticks(dw2) ); ciDebugOut(( infolevel|DEB_NOCOMPNAME, "\t%u\n", dw1 )); } if (Next(i) == _iTail) ciDebugOut(( infolevel|DEB_NOCOMPNAME, "\t%u\n", dw2 )); } DWORD dwStart1 = Ticks(_dwLastSampleTime) - MID_TERM_INTERVAL; DWORD dwStart2 = Ticks(_dwLastSampleTime) - LONG_TERM_INTERVAL; DWORD dwFirstInput = 0; DWORD dwIdleTime = 0; DWORD dwUnknTime = 0; unsigned cInputEvent = 0; unsigned cInputQualified1 = 0; unsigned cInputQualified2 = 0; unsigned cTotalEvent = 0; for (i = _iHead; i != _iTail; i = Next(i)) { DWORD dw1 = _adwSamples[i]; DWORD dw2 = _adwSamples[Next(i)]; if (IsInput(dw1)) { Win4Assert( Ticks(dw1) <= Ticks(dw2) ); cInputEvent++; if ( IsSample(dw2) ) dwIdleTime += Ticks(dw2) - Ticks(dw1); if (Ticks(dw1) > dwStart1) cInputQualified1++; if (Ticks(dw1) > dwStart2) cInputQualified2++; } else { Win4Assert( Ticks(dw1) <= Ticks(dw2) ); if ( IsSample(dw2) ) dwIdleTime += Ticks(dw2) - Ticks(dw1); else dwUnknTime += Ticks(dw2) - Ticks(dw1); } cTotalEvent++; } ciDebugOut(( infolevel, "\tTotal time in buffer\t" )); PrintTicks( infolevel, Ticks(_dwLastSampleTime) - Ticks(_adwSamples[_iHead]) ); ciDebugOut(( infolevel|DEB_NOCOMPNAME, "\n\tTime since last snapshot\t" )); PrintTicks( infolevel, Ticks(_dwLastSampleTime) - Ticks(_adwSamples[_iSnap]) ); ciDebugOut(( infolevel|DEB_NOCOMPNAME, "\n\t\tTrue idle time\t" )); PrintTicks( infolevel, dwIdleTime ); ciDebugOut(( infolevel|DEB_NOCOMPNAME, "\n\t\tUnknown time\t" )); PrintTicks( infolevel, dwUnknTime ); ciDebugOut(( infolevel|DEB_NOCOMPNAME, "\n\t\tInput events\t%7d Qualified\t%d %d\n", cInputEvent, cInputQualified1, cInputQualified2 )); if ( (Ticks(_dwLastSampleTime) - Ticks(_dwLastInputTime)) < SHORT_TERM_IDLE ) { ciDebugOut(( infolevel|DEB_NOCOMPNAME, "\t*** Short term criteria\t%3d %3d\n", SHORT_TERM_IDLE, Ticks(_dwLastSampleTime) - Ticks(_dwLastInputTime) )); } if ( cInputQualified1 >= MID_TERM_THRESHOLD ) { ciDebugOut(( infolevel|DEB_NOCOMPNAME, "\t*** Mid term threshold\t%2d/%3d %3d\n", MID_TERM_THRESHOLD, MID_TERM_INTERVAL, cInputQualified1 )); } if ( cInputQualified2 >= LONG_TERM_THRESHOLD ) { ciDebugOut(( infolevel|DEB_NOCOMPNAME, "\t*** Long term threshold\t%2d/%3d %3d\n", LONG_TERM_THRESHOLD, LONG_TERM_INTERVAL, cInputQualified2 )); } _iSnap = _iTail; } #endif // CIDBG == 1