Home | History | Annotate | Download | only in source
      1 /*
      2  *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
      3  *
      4  *  Use of this source code is governed by a BSD-style license
      5  *  that can be found in the LICENSE file in the root of the source
      6  *  tree. An additional intellectual property rights grant can be found
      7  *  in the file PATENTS.  All contributing project authors may
      8  *  be found in the AUTHORS file in the root of the source tree.
      9  */
     10 
     11 #include "cpu_win.h"
     12 
     13 #define _WIN32_DCOM
     14 
     15 #include <assert.h>
     16 #include <iostream>
     17 #include <Wbemidl.h>
     18 
     19 #pragma comment(lib, "wbemuuid.lib")
     20 
     21 #include "condition_variable_wrapper.h"
     22 #include "critical_section_wrapper.h"
     23 #include "event_wrapper.h"
     24 #include "thread_wrapper.h"
     25 
     26 namespace webrtc {
     27 WebRtc_Word32 CpuWindows::CpuUsage()
     28 {
     29     if (!has_initialized_)
     30     {
     31         return -1;
     32     }
     33     // Last element is the average
     34     return cpu_usage_[number_of_objects_ - 1];
     35 }
     36 
     37 WebRtc_Word32 CpuWindows::CpuUsageMultiCore(WebRtc_UWord32& num_cores,
     38                                             WebRtc_UWord32*& cpu_usage)
     39 {
     40     if (has_terminated_) {
     41         num_cores = 0;
     42         cpu_usage = NULL;
     43         return -1;
     44     }
     45     if (!has_initialized_)
     46     {
     47         num_cores = 0;
     48         cpu_usage = NULL;
     49         return -1;
     50     }
     51     num_cores = number_of_objects_ - 1;
     52     cpu_usage = cpu_usage_;
     53     return cpu_usage_[number_of_objects_-1];
     54 }
     55 
     56 CpuWindows::CpuWindows()
     57     : cpu_polling_thread(NULL),
     58       initialize_(true),
     59       has_initialized_(false),
     60       terminate_(false),
     61       has_terminated_(false),
     62       cpu_usage_(NULL),
     63       wbem_enum_access_(NULL),
     64       number_of_objects_(0),
     65       cpu_usage_handle_(0),
     66       previous_processor_timestamp_(NULL),
     67       timestamp_sys_100_ns_handle_(0),
     68       previous_100ns_timestamp_(NULL),
     69       wbem_service_(NULL),
     70       wbem_service_proxy_(NULL),
     71       wbem_refresher_(NULL),
     72       wbem_enum_(NULL)
     73 {
     74     // All resources are allocated in PollingCpu().
     75     if (AllocateComplexDataTypes())
     76     {
     77         StartPollingCpu();
     78     }
     79     else
     80     {
     81         assert(false);
     82     }
     83 }
     84 
     85 CpuWindows::~CpuWindows()
     86 {
     87     // All resources are reclaimed in StopPollingCpu().
     88     StopPollingCpu();
     89     DeAllocateComplexDataTypes();
     90 }
     91 
     92 bool CpuWindows::AllocateComplexDataTypes()
     93 {
     94     cpu_polling_thread = ThreadWrapper::CreateThread(
     95         CpuWindows::Process,
     96         reinterpret_cast<void*>(this),
     97         kNormalPriority,
     98         "CpuWindows");
     99     init_crit_ = CriticalSectionWrapper::CreateCriticalSection();
    100     init_cond_ = ConditionVariableWrapper::CreateConditionVariable();
    101     terminate_crit_ = CriticalSectionWrapper::CreateCriticalSection();
    102     terminate_cond_ = ConditionVariableWrapper::CreateConditionVariable();
    103     sleep_event = EventWrapper::Create();
    104     return (cpu_polling_thread != NULL) && (init_crit_ != NULL) &&
    105            (init_cond_ != NULL) && (terminate_crit_ != NULL) &&
    106            (terminate_cond_ != NULL) && (sleep_event != NULL);
    107 }
    108 
    109 void CpuWindows::DeAllocateComplexDataTypes()
    110 {
    111     if (sleep_event != NULL)
    112     {
    113         delete sleep_event;
    114         sleep_event = NULL;
    115     }
    116     if (terminate_cond_ != NULL)
    117     {
    118         delete terminate_cond_;
    119         terminate_cond_ = NULL;
    120     }
    121     if (terminate_crit_ != NULL)
    122     {
    123         delete terminate_crit_;
    124         terminate_crit_ = NULL;
    125     }
    126     if (init_cond_ != NULL)
    127     {
    128         delete init_cond_;
    129         init_cond_ = NULL;
    130     }
    131     if (init_crit_ != NULL)
    132     {
    133         delete init_crit_;
    134         init_crit_ = NULL;
    135     }
    136     if (cpu_polling_thread != NULL)
    137     {
    138         delete cpu_polling_thread;
    139         cpu_polling_thread = NULL;
    140     }
    141 }
    142 
    143 void CpuWindows::StartPollingCpu()
    144 {
    145     unsigned int dummy_id = 0;
    146     if (!cpu_polling_thread->Start(dummy_id))
    147     {
    148         initialize_ = false;
    149         has_terminated_ = true;
    150         assert(false);
    151     }
    152 }
    153 
    154 bool CpuWindows::StopPollingCpu()
    155 {
    156     {
    157         // If StopPollingCpu is called immediately after StartPollingCpu() it is
    158         // possible that cpu_polling_thread is in the process of initializing.
    159         // Let initialization finish to avoid getting into a bad state.
    160         CriticalSectionScoped cs(init_crit_);
    161         while(initialize_)
    162         {
    163             init_cond_->SleepCS(*init_crit_);
    164         }
    165     }
    166 
    167     CriticalSectionScoped cs(terminate_crit_);
    168     terminate_ = true;
    169     sleep_event->Set();
    170     while (!has_terminated_)
    171     {
    172         terminate_cond_->SleepCS(*terminate_crit_);
    173     }
    174     cpu_polling_thread->Stop();
    175     delete cpu_polling_thread;
    176     cpu_polling_thread = NULL;
    177     return true;
    178 }
    179 
    180 bool CpuWindows::Process(void* thread_object)
    181 {
    182     return reinterpret_cast<CpuWindows*>(thread_object)->ProcessImpl();
    183 }
    184 
    185 bool CpuWindows::ProcessImpl()
    186 {
    187     {
    188         CriticalSectionScoped cs(terminate_crit_);
    189         if (terminate_)
    190         {
    191             Terminate();
    192             terminate_cond_->WakeAll();
    193             return false;
    194         }
    195     }
    196     // Initialize on first iteration
    197     if (initialize_)
    198     {
    199         CriticalSectionScoped cs(init_crit_);
    200         initialize_ = false;
    201         const bool success = Initialize();
    202         init_cond_->WakeAll();
    203         if (!success || !has_initialized_)
    204         {
    205             has_initialized_ = false;
    206             terminate_ = true;
    207             return true;
    208         }
    209     }
    210     // Approximately one seconds sleep for each CPU measurement. Precision is
    211     // not important. 1 second refresh rate is also used by Performance Monitor
    212     // (perfmon).
    213     if(kEventTimeout != sleep_event->Wait(1000))
    214     {
    215         // Terminating. No need to update CPU usage.
    216         assert(terminate_);
    217         return true;
    218     }
    219 
    220     // UpdateCpuUsage() returns false if a single (or more) CPU read(s) failed.
    221     // Not a major problem if it happens.
    222     UpdateCpuUsage();
    223     return true;
    224 }
    225 
    226 bool CpuWindows::CreateWmiConnection()
    227 {
    228     IWbemLocator* service_locator = NULL;
    229     HRESULT hr = CoCreateInstance(CLSID_WbemLocator, NULL,
    230                                   CLSCTX_INPROC_SERVER, IID_IWbemLocator,
    231                                   reinterpret_cast<void**> (&service_locator));
    232     if (FAILED(hr))
    233     {
    234         return false;
    235     }
    236     // To get the WMI service specify the WMI namespace.
    237     BSTR wmi_namespace = SysAllocString(L"\\\\.\\root\\cimv2");
    238     if (wmi_namespace == NULL)
    239     {
    240         // This type of failure signifies running out of memory.
    241         service_locator->Release();
    242         return false;
    243     }
    244     hr = service_locator->ConnectServer(wmi_namespace, NULL, NULL, NULL, 0L,
    245                                         NULL, NULL, &wbem_service_);
    246     SysFreeString(wmi_namespace);
    247     service_locator->Release();
    248     return !FAILED(hr);
    249 }
    250 
    251 // Sets up WMI refresher and enum
    252 bool CpuWindows::CreatePerfOsRefresher()
    253 {
    254     // Create refresher.
    255     HRESULT hr = CoCreateInstance(CLSID_WbemRefresher, NULL,
    256                                   CLSCTX_INPROC_SERVER, IID_IWbemRefresher,
    257                                   reinterpret_cast<void**> (&wbem_refresher_));
    258     if (FAILED(hr))
    259     {
    260         return false;
    261     }
    262     // Create PerfOS_Processor enum.
    263     IWbemConfigureRefresher* wbem_refresher_config = NULL;
    264     hr = wbem_refresher_->QueryInterface(
    265         IID_IWbemConfigureRefresher,
    266         reinterpret_cast<void**> (&wbem_refresher_config));
    267     if (FAILED(hr))
    268     {
    269         return false;
    270     }
    271 
    272     // Create a proxy to the IWbemServices so that a local authentication
    273     // can be set up (this is needed to be able to successfully call
    274     // IWbemConfigureRefresher::AddEnum). Setting authentication with
    275     // CoInitializeSecurity is process-wide (which is too intrusive).
    276     hr = CoCopyProxy(static_cast<IUnknown*> (wbem_service_),
    277                      reinterpret_cast<IUnknown**> (&wbem_service_proxy_));
    278     if(FAILED(hr))
    279     {
    280         return false;
    281     }
    282     // Set local authentication.
    283     // RPC_C_AUTHN_WINNT means using NTLM instead of Kerberos which is default.
    284     hr = CoSetProxyBlanket(static_cast<IUnknown*> (wbem_service_proxy_),
    285                            RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
    286                            RPC_C_AUTHN_LEVEL_DEFAULT,
    287                            RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
    288     if(FAILED(hr))
    289     {
    290         return false;
    291     }
    292 
    293     // Don't care about the particular id for the enum.
    294     long enum_id = 0;
    295     hr = wbem_refresher_config->AddEnum(wbem_service_proxy_,
    296                                         L"Win32_PerfRawData_PerfOS_Processor",
    297                                         0, NULL, &wbem_enum_, &enum_id);
    298     wbem_refresher_config->Release();
    299     wbem_refresher_config = NULL;
    300     return !FAILED(hr);
    301 }
    302 
    303 // Have to pull the first round of data to be able set the handles.
    304 bool CpuWindows::CreatePerfOsCpuHandles()
    305 {
    306     // Update the refresher so that there is data available in wbem_enum_.
    307     wbem_refresher_->Refresh(0L);
    308 
    309     // The number of enumerators is the number of processor + 1 (the total).
    310     // This is unknown at this point.
    311     DWORD number_returned = 0;
    312     HRESULT hr = wbem_enum_->GetObjects(0L, number_of_objects_,
    313                                         wbem_enum_access_, &number_returned);
    314     // number_returned indicates the number of enumerators that are needed.
    315     if (hr == WBEM_E_BUFFER_TOO_SMALL &&
    316         number_returned > number_of_objects_)
    317     {
    318         // Allocate the number IWbemObjectAccess asked for by the
    319         // GetObjects(..) function.
    320         wbem_enum_access_ = new IWbemObjectAccess*[number_returned];
    321         cpu_usage_ = new WebRtc_UWord32[number_returned];
    322         previous_processor_timestamp_ = new unsigned __int64[number_returned];
    323         previous_100ns_timestamp_ = new unsigned __int64[number_returned];
    324         if ((wbem_enum_access_ == NULL) || (cpu_usage_ == NULL) ||
    325             (previous_processor_timestamp_ == NULL) ||
    326             (previous_100ns_timestamp_ == NULL))
    327         {
    328             // Out of memory.
    329             return false;
    330         }
    331 
    332         SecureZeroMemory(wbem_enum_access_, number_returned *
    333                          sizeof(IWbemObjectAccess*));
    334         memset(cpu_usage_, 0, sizeof(int) * number_returned);
    335         memset(previous_processor_timestamp_, 0, sizeof(unsigned __int64) *
    336                number_returned);
    337         memset(previous_100ns_timestamp_, 0, sizeof(unsigned __int64) *
    338                number_returned);
    339 
    340         number_of_objects_ = number_returned;
    341         // Read should be successfull now that memory has been allocated.
    342         hr = wbem_enum_->GetObjects(0L, number_of_objects_, wbem_enum_access_,
    343                                     &number_returned);
    344         if (FAILED(hr))
    345         {
    346             return false;
    347         }
    348     }
    349     else
    350     {
    351         // 0 enumerators should not be enough. Something has gone wrong here.
    352         return false;
    353     }
    354 
    355     // Get the enumerator handles that are needed for calculating CPU usage.
    356     CIMTYPE cpu_usage_type;
    357     hr = wbem_enum_access_[0]->GetPropertyHandle(L"PercentProcessorTime",
    358                                                  &cpu_usage_type,
    359                                                  &cpu_usage_handle_);
    360     if (FAILED(hr))
    361     {
    362         return false;
    363     }
    364     CIMTYPE timestamp_sys_100_ns_type;
    365     hr = wbem_enum_access_[0]->GetPropertyHandle(L"TimeStamp_Sys100NS",
    366                                                  &timestamp_sys_100_ns_type,
    367                                                  &timestamp_sys_100_ns_handle_);
    368     return !FAILED(hr);
    369 }
    370 
    371 bool CpuWindows::Initialize()
    372 {
    373     if (terminate_)
    374     {
    375         return false;
    376     }
    377     // Initialize COM library.
    378     HRESULT hr = CoInitializeEx(NULL,COINIT_MULTITHREADED);
    379     if (FAILED(hr))
    380     {
    381         return false;
    382     }
    383     if (FAILED(hr))
    384     {
    385         return false;
    386     }
    387 
    388     if (!CreateWmiConnection())
    389     {
    390         return false;
    391     }
    392     if (!CreatePerfOsRefresher())
    393     {
    394         return false;
    395     }
    396     if (!CreatePerfOsCpuHandles())
    397     {
    398         return false;
    399     }
    400     has_initialized_ = true;
    401     return true;
    402 }
    403 
    404 bool CpuWindows::Terminate()
    405 {
    406     if (has_terminated_)
    407     {
    408         return false;
    409     }
    410     // Reverse order of Initialize().
    411     // Some compilers complain about deleting NULL though it's well defined
    412     if (previous_100ns_timestamp_ != NULL)
    413     {
    414         delete[] previous_100ns_timestamp_;
    415         previous_100ns_timestamp_ = NULL;
    416     }
    417     if (previous_processor_timestamp_ != NULL)
    418     {
    419         delete[] previous_processor_timestamp_;
    420         previous_processor_timestamp_ = NULL;
    421     }
    422     if (cpu_usage_ != NULL)
    423     {
    424         delete[] cpu_usage_;
    425         cpu_usage_ = NULL;
    426     }
    427     if (wbem_enum_access_ != NULL)
    428     {
    429         for (DWORD i = 0; i < number_of_objects_; i++)
    430         {
    431             if(wbem_enum_access_[i] != NULL)
    432             {
    433                 wbem_enum_access_[i]->Release();
    434             }
    435         }
    436         delete[] wbem_enum_access_;
    437         wbem_enum_access_ = NULL;
    438     }
    439     if (wbem_enum_ != NULL)
    440     {
    441         wbem_enum_->Release();
    442         wbem_enum_ = NULL;
    443     }
    444     if (wbem_refresher_ != NULL)
    445     {
    446         wbem_refresher_->Release();
    447         wbem_refresher_ = NULL;
    448     }
    449     if (wbem_service_proxy_ != NULL)
    450     {
    451         wbem_service_proxy_->Release();
    452         wbem_service_proxy_ = NULL;
    453     }
    454     if (wbem_service_ != NULL)
    455     {
    456         wbem_service_->Release();
    457         wbem_service_ = NULL;
    458     }
    459     // CoUninitialized should be called once for every CoInitializeEx.
    460     // Regardless if it failed or not.
    461     CoUninitialize();
    462     has_terminated_ = true;
    463     return true;
    464 }
    465 
    466 bool CpuWindows::UpdateCpuUsage()
    467 {
    468     wbem_refresher_->Refresh(0L);
    469     DWORD number_returned = 0;
    470     HRESULT hr = wbem_enum_->GetObjects(0L, number_of_objects_,
    471                                         wbem_enum_access_,&number_returned);
    472     if (FAILED(hr))
    473     {
    474         // wbem_enum_access_ has already been allocated. Unless the number of
    475         // CPUs change runtime this should not happen.
    476         return false;
    477     }
    478     unsigned __int64 cpu_usage = 0;
    479     unsigned __int64 timestamp_100ns = 0;
    480     bool returnValue = true;
    481     for (DWORD i = 0; i < number_returned; i++)
    482     {
    483         hr = wbem_enum_access_[i]->ReadQWORD(cpu_usage_handle_,&cpu_usage);
    484         if (FAILED(hr))
    485         {
    486             returnValue = false;
    487         }
    488         hr = wbem_enum_access_[i]->ReadQWORD(timestamp_sys_100_ns_handle_,
    489                                              &timestamp_100ns);
    490         if (FAILED(hr))
    491         {
    492             returnValue = false;
    493         }
    494         wbem_enum_access_[i]->Release();
    495         wbem_enum_access_[i] = NULL;
    496 
    497         const bool wrapparound =
    498             (previous_processor_timestamp_[i] > cpu_usage) ||
    499             (previous_100ns_timestamp_[i] > timestamp_100ns);
    500         const bool first_time = (previous_processor_timestamp_[i] == 0) ||
    501                                 (previous_100ns_timestamp_[i] == 0);
    502         if (wrapparound || first_time)
    503         {
    504             previous_processor_timestamp_[i] = cpu_usage;
    505             previous_100ns_timestamp_[i] = timestamp_100ns;
    506             continue;
    507         }
    508         const unsigned __int64 processor_timestamp_delta =
    509             cpu_usage - previous_processor_timestamp_[i];
    510         const unsigned __int64 timestamp_100ns_delta =
    511             timestamp_100ns - previous_100ns_timestamp_[i];
    512 
    513         if (processor_timestamp_delta >= timestamp_100ns_delta)
    514         {
    515             cpu_usage_[i] = 0;
    516         } else {
    517             // Quotient must be float since the division is guaranteed to yield
    518             // a value between 0 and 1 which is 0 in integer division.
    519             const float delta_quotient =
    520                 static_cast<float>(processor_timestamp_delta) /
    521                 static_cast<float>(timestamp_100ns_delta);
    522             cpu_usage_[i] = 100 - static_cast<WebRtc_UWord32>(delta_quotient *
    523                                                               100);
    524         }
    525         previous_processor_timestamp_[i] = cpu_usage;
    526         previous_100ns_timestamp_[i] = timestamp_100ns;
    527     }
    528     return returnValue;
    529 }
    530 } // namespace webrtc
    531