79ba0c7b14
Review URL: http://webrtc-codereview.appspot.com/24006 git-svn-id: http://webrtc.googlecode.com/svn/trunk@15 4adac7df-926f-26a2-2b94-8c16560cd09d
533 lines
16 KiB
C++
533 lines
16 KiB
C++
/*
|
|
* 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_windows.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_initialized_)
|
|
{
|
|
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())
|
|
{
|
|
const bool success = StartPollingCpu();
|
|
assert(success);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
bool CpuWindows::StartPollingCpu()
|
|
{
|
|
unsigned int dummy_id = 0;
|
|
if (!cpu_polling_thread->Start(dummy_id))
|
|
{
|
|
return false;
|
|
}
|
|
{
|
|
CriticalSectionScoped cs(*init_crit_);
|
|
while(initialize_)
|
|
{
|
|
init_cond_->SleepCS(*init_crit_);
|
|
}
|
|
}
|
|
if (!has_initialized_)
|
|
{
|
|
cpu_polling_thread->Stop();
|
|
return false;
|
|
}
|
|
return has_initialized_;
|
|
}
|
|
|
|
bool CpuWindows::StopPollingCpu()
|
|
{
|
|
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 false;
|
|
}
|
|
}
|
|
// 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
|