/* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "cpu_win.h" #define _WIN32_DCOM #include <assert.h> #include <iostream> #include <Wbemidl.h> #pragma comment(lib, "wbemuuid.lib") #include "condition_variable_wrapper.h" #include "critical_section_wrapper.h" #include "event_wrapper.h" #include "thread_wrapper.h" namespace webrtc { WebRtc_Word32 CpuWindows::CpuUsage() { if (!has_initialized_) { return -1; } // Last element is the average return cpu_usage_[number_of_objects_ - 1]; } WebRtc_Word32 CpuWindows::CpuUsageMultiCore(WebRtc_UWord32& num_cores, WebRtc_UWord32*& cpu_usage) { if (has_terminated_) { num_cores = 0; cpu_usage = NULL; return -1; } if (!has_initialized_) { num_cores = 0; cpu_usage = NULL; return -1; } num_cores = number_of_objects_ - 1; cpu_usage = cpu_usage_; return cpu_usage_[number_of_objects_-1]; } CpuWindows::CpuWindows() : cpu_polling_thread(NULL), initialize_(true), has_initialized_(false), terminate_(false), has_terminated_(false), cpu_usage_(NULL), wbem_enum_access_(NULL), number_of_objects_(0), cpu_usage_handle_(0), previous_processor_timestamp_(NULL), timestamp_sys_100_ns_handle_(0), previous_100ns_timestamp_(NULL), wbem_service_(NULL), wbem_service_proxy_(NULL), wbem_refresher_(NULL), wbem_enum_(NULL) { // All resources are allocated in PollingCpu(). if (AllocateComplexDataTypes()) { StartPollingCpu(); } else { assert(false); } } CpuWindows::~CpuWindows() { // All resources are reclaimed in StopPollingCpu(). StopPollingCpu(); DeAllocateComplexDataTypes(); } bool CpuWindows::AllocateComplexDataTypes() { cpu_polling_thread = ThreadWrapper::CreateThread( CpuWindows::Process, reinterpret_cast<void*>(this), kNormalPriority, "CpuWindows"); init_crit_ = CriticalSectionWrapper::CreateCriticalSection(); init_cond_ = ConditionVariableWrapper::CreateConditionVariable(); terminate_crit_ = CriticalSectionWrapper::CreateCriticalSection(); terminate_cond_ = ConditionVariableWrapper::CreateConditionVariable(); sleep_event = EventWrapper::Create(); return (cpu_polling_thread != NULL) && (init_crit_ != NULL) && (init_cond_ != NULL) && (terminate_crit_ != NULL) && (terminate_cond_ != NULL) && (sleep_event != NULL); } void CpuWindows::DeAllocateComplexDataTypes() { if (sleep_event != NULL) { delete sleep_event; sleep_event = NULL; } if (terminate_cond_ != NULL) { delete terminate_cond_; terminate_cond_ = NULL; } if (terminate_crit_ != NULL) { delete terminate_crit_; terminate_crit_ = NULL; } if (init_cond_ != NULL) { delete init_cond_; init_cond_ = NULL; } if (init_crit_ != NULL) { delete init_crit_; init_crit_ = NULL; } if (cpu_polling_thread != NULL) { delete cpu_polling_thread; cpu_polling_thread = NULL; } } void CpuWindows::StartPollingCpu() { unsigned int dummy_id = 0; if (!cpu_polling_thread->Start(dummy_id)) { initialize_ = false; has_terminated_ = true; assert(false); } } bool CpuWindows::StopPollingCpu() { { // If StopPollingCpu is called immediately after StartPollingCpu() it is // possible that cpu_polling_thread is in the process of initializing. // Let initialization finish to avoid getting into a bad state. CriticalSectionScoped cs(init_crit_); while(initialize_) { init_cond_->SleepCS(*init_crit_); } } CriticalSectionScoped cs(terminate_crit_); terminate_ = true; sleep_event->Set(); while (!has_terminated_) { terminate_cond_->SleepCS(*terminate_crit_); } cpu_polling_thread->Stop(); delete cpu_polling_thread; cpu_polling_thread = NULL; return true; } bool CpuWindows::Process(void* thread_object) { return reinterpret_cast<CpuWindows*>(thread_object)->ProcessImpl(); } bool CpuWindows::ProcessImpl() { { CriticalSectionScoped cs(terminate_crit_); if (terminate_) { Terminate(); terminate_cond_->WakeAll(); return false; } } // Initialize on first iteration if (initialize_) { CriticalSectionScoped cs(init_crit_); initialize_ = false; const bool success = Initialize(); init_cond_->WakeAll(); if (!success || !has_initialized_) { has_initialized_ = false; terminate_ = true; return true; } } // Approximately one seconds sleep for each CPU measurement. Precision is // not important. 1 second refresh rate is also used by Performance Monitor // (perfmon). if(kEventTimeout != sleep_event->Wait(1000)) { // Terminating. No need to update CPU usage. assert(terminate_); return true; } // UpdateCpuUsage() returns false if a single (or more) CPU read(s) failed. // Not a major problem if it happens. UpdateCpuUsage(); return true; } bool CpuWindows::CreateWmiConnection() { IWbemLocator* service_locator = NULL; HRESULT hr = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, reinterpret_cast<void**> (&service_locator)); if (FAILED(hr)) { return false; } // To get the WMI service specify the WMI namespace. BSTR wmi_namespace = SysAllocString(L"\\\\.\\root\\cimv2"); if (wmi_namespace == NULL) { // This type of failure signifies running out of memory. service_locator->Release(); return false; } hr = service_locator->ConnectServer(wmi_namespace, NULL, NULL, NULL, 0L, NULL, NULL, &wbem_service_); SysFreeString(wmi_namespace); service_locator->Release(); return !FAILED(hr); } // Sets up WMI refresher and enum bool CpuWindows::CreatePerfOsRefresher() { // Create refresher. HRESULT hr = CoCreateInstance(CLSID_WbemRefresher, NULL, CLSCTX_INPROC_SERVER, IID_IWbemRefresher, reinterpret_cast<void**> (&wbem_refresher_)); if (FAILED(hr)) { return false; } // Create PerfOS_Processor enum. IWbemConfigureRefresher* wbem_refresher_config = NULL; hr = wbem_refresher_->QueryInterface( IID_IWbemConfigureRefresher, reinterpret_cast<void**> (&wbem_refresher_config)); if (FAILED(hr)) { return false; } // Create a proxy to the IWbemServices so that a local authentication // can be set up (this is needed to be able to successfully call // IWbemConfigureRefresher::AddEnum). Setting authentication with // CoInitializeSecurity is process-wide (which is too intrusive). hr = CoCopyProxy(static_cast<IUnknown*> (wbem_service_), reinterpret_cast<IUnknown**> (&wbem_service_proxy_)); if(FAILED(hr)) { return false; } // Set local authentication. // RPC_C_AUTHN_WINNT means using NTLM instead of Kerberos which is default. hr = CoSetProxyBlanket(static_cast<IUnknown*> (wbem_service_proxy_), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE); if(FAILED(hr)) { return false; } // Don't care about the particular id for the enum. long enum_id = 0; hr = wbem_refresher_config->AddEnum(wbem_service_proxy_, L"Win32_PerfRawData_PerfOS_Processor", 0, NULL, &wbem_enum_, &enum_id); wbem_refresher_config->Release(); wbem_refresher_config = NULL; return !FAILED(hr); } // Have to pull the first round of data to be able set the handles. bool CpuWindows::CreatePerfOsCpuHandles() { // Update the refresher so that there is data available in wbem_enum_. wbem_refresher_->Refresh(0L); // The number of enumerators is the number of processor + 1 (the total). // This is unknown at this point. DWORD number_returned = 0; HRESULT hr = wbem_enum_->GetObjects(0L, number_of_objects_, wbem_enum_access_, &number_returned); // number_returned indicates the number of enumerators that are needed. if (hr == WBEM_E_BUFFER_TOO_SMALL && number_returned > number_of_objects_) { // Allocate the number IWbemObjectAccess asked for by the // GetObjects(..) function. wbem_enum_access_ = new IWbemObjectAccess*[number_returned]; cpu_usage_ = new WebRtc_UWord32[number_returned]; previous_processor_timestamp_ = new unsigned __int64[number_returned]; previous_100ns_timestamp_ = new unsigned __int64[number_returned]; if ((wbem_enum_access_ == NULL) || (cpu_usage_ == NULL) || (previous_processor_timestamp_ == NULL) || (previous_100ns_timestamp_ == NULL)) { // Out of memory. return false; } SecureZeroMemory(wbem_enum_access_, number_returned * sizeof(IWbemObjectAccess*)); memset(cpu_usage_, 0, sizeof(int) * number_returned); memset(previous_processor_timestamp_, 0, sizeof(unsigned __int64) * number_returned); memset(previous_100ns_timestamp_, 0, sizeof(unsigned __int64) * number_returned); number_of_objects_ = number_returned; // Read should be successfull now that memory has been allocated. hr = wbem_enum_->GetObjects(0L, number_of_objects_, wbem_enum_access_, &number_returned); if (FAILED(hr)) { return false; } } else { // 0 enumerators should not be enough. Something has gone wrong here. return false; } // Get the enumerator handles that are needed for calculating CPU usage. CIMTYPE cpu_usage_type; hr = wbem_enum_access_[0]->GetPropertyHandle(L"PercentProcessorTime", &cpu_usage_type, &cpu_usage_handle_); if (FAILED(hr)) { return false; } CIMTYPE timestamp_sys_100_ns_type; hr = wbem_enum_access_[0]->GetPropertyHandle(L"TimeStamp_Sys100NS", ×tamp_sys_100_ns_type, ×tamp_sys_100_ns_handle_); return !FAILED(hr); } bool CpuWindows::Initialize() { if (terminate_) { return false; } // Initialize COM library. HRESULT hr = CoInitializeEx(NULL,COINIT_MULTITHREADED); if (FAILED(hr)) { return false; } if (FAILED(hr)) { return false; } if (!CreateWmiConnection()) { return false; } if (!CreatePerfOsRefresher()) { return false; } if (!CreatePerfOsCpuHandles()) { return false; } has_initialized_ = true; return true; } bool CpuWindows::Terminate() { if (has_terminated_) { return false; } // Reverse order of Initialize(). // Some compilers complain about deleting NULL though it's well defined if (previous_100ns_timestamp_ != NULL) { delete[] previous_100ns_timestamp_; previous_100ns_timestamp_ = NULL; } if (previous_processor_timestamp_ != NULL) { delete[] previous_processor_timestamp_; previous_processor_timestamp_ = NULL; } if (cpu_usage_ != NULL) { delete[] cpu_usage_; cpu_usage_ = NULL; } if (wbem_enum_access_ != NULL) { for (DWORD i = 0; i < number_of_objects_; i++) { if(wbem_enum_access_[i] != NULL) { wbem_enum_access_[i]->Release(); } } delete[] wbem_enum_access_; wbem_enum_access_ = NULL; } if (wbem_enum_ != NULL) { wbem_enum_->Release(); wbem_enum_ = NULL; } if (wbem_refresher_ != NULL) { wbem_refresher_->Release(); wbem_refresher_ = NULL; } if (wbem_service_proxy_ != NULL) { wbem_service_proxy_->Release(); wbem_service_proxy_ = NULL; } if (wbem_service_ != NULL) { wbem_service_->Release(); wbem_service_ = NULL; } // CoUninitialized should be called once for every CoInitializeEx. // Regardless if it failed or not. CoUninitialize(); has_terminated_ = true; return true; } bool CpuWindows::UpdateCpuUsage() { wbem_refresher_->Refresh(0L); DWORD number_returned = 0; HRESULT hr = wbem_enum_->GetObjects(0L, number_of_objects_, wbem_enum_access_,&number_returned); if (FAILED(hr)) { // wbem_enum_access_ has already been allocated. Unless the number of // CPUs change runtime this should not happen. return false; } unsigned __int64 cpu_usage = 0; unsigned __int64 timestamp_100ns = 0; bool returnValue = true; for (DWORD i = 0; i < number_returned; i++) { hr = wbem_enum_access_[i]->ReadQWORD(cpu_usage_handle_,&cpu_usage); if (FAILED(hr)) { returnValue = false; } hr = wbem_enum_access_[i]->ReadQWORD(timestamp_sys_100_ns_handle_, ×tamp_100ns); if (FAILED(hr)) { returnValue = false; } wbem_enum_access_[i]->Release(); wbem_enum_access_[i] = NULL; const bool wrapparound = (previous_processor_timestamp_[i] > cpu_usage) || (previous_100ns_timestamp_[i] > timestamp_100ns); const bool first_time = (previous_processor_timestamp_[i] == 0) || (previous_100ns_timestamp_[i] == 0); if (wrapparound || first_time) { previous_processor_timestamp_[i] = cpu_usage; previous_100ns_timestamp_[i] = timestamp_100ns; continue; } const unsigned __int64 processor_timestamp_delta = cpu_usage - previous_processor_timestamp_[i]; const unsigned __int64 timestamp_100ns_delta = timestamp_100ns - previous_100ns_timestamp_[i]; if (processor_timestamp_delta >= timestamp_100ns_delta) { cpu_usage_[i] = 0; } else { // Quotient must be float since the division is guaranteed to yield // a value between 0 and 1 which is 0 in integer division. const float delta_quotient = static_cast<float>(processor_timestamp_delta) / static_cast<float>(timestamp_100ns_delta); cpu_usage_[i] = 100 - static_cast<WebRtc_UWord32>(delta_quotient * 100); } previous_processor_timestamp_[i] = cpu_usage; previous_100ns_timestamp_[i] = timestamp_100ns; } return returnValue; } } // namespace webrtc