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 ×tamp_sys_100_ns_type, 367 ×tamp_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 ×tamp_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