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
26namespace webrtc {
27WebRtc_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
37WebRtc_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
56CpuWindows::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
85CpuWindows::~CpuWindows()
86{
87    // All resources are reclaimed in StopPollingCpu().
88    StopPollingCpu();
89    DeAllocateComplexDataTypes();
90}
91
92bool 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
109void 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
143void 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
154bool 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
180bool CpuWindows::Process(void* thread_object)
181{
182    return reinterpret_cast<CpuWindows*>(thread_object)->ProcessImpl();
183}
184
185bool 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
226bool 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
252bool 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.
304bool 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
371bool 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
404bool 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
466bool 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