| /* |
| * Copyright (c) 2011 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 0; |
| } |
| 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(). |
| const bool success = StopPollingCpu(); |
| assert(success); |
| 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_); |
| } |
| } |
| |
| if (!has_initialized_) |
| { |
| return false; |
| } |
| 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_) |
| { |
| const bool success = Terminate(); |
| assert(success); |
| 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 but make sure it doesnt trigger in |
| // debug. |
| const bool success = UpdateCpuUsage(); |
| assert(success); |
| 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 |