| /* |
| * libjingle |
| * Copyright 2010 Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <iomanip> |
| #include <iostream> |
| #include <vector> |
| |
| #ifdef WIN32 |
| #include "talk/base/win32.h" |
| #endif |
| |
| #include "talk/base/cpumonitor.h" |
| #include "talk/base/flags.h" |
| #include "talk/base/gunit.h" |
| #include "talk/base/scoped_ptr.h" |
| #include "talk/base/thread.h" |
| #include "talk/base/timeutils.h" |
| #include "talk/base/timing.h" |
| |
| namespace talk_base { |
| |
| static const int kMaxCpus = 1024; |
| static const int kSettleTime = 100; // Amount of time to between tests. |
| static const int kIdleTime = 500; // Amount of time to be idle in ms. |
| static const int kBusyTime = 1000; // Amount of time to be busy in ms. |
| |
| class BusyThread : public talk_base::Thread { |
| public: |
| BusyThread(double load, double duration, double interval) : |
| load_(load), duration_(duration), interval_(interval) { |
| } |
| void Run() { |
| Timing time; |
| double busy_time = interval_ * load_ / 100.0; |
| for (;;) { |
| time.BusyWait(busy_time); |
| time.IdleWait(interval_ - busy_time); |
| if (duration_) { |
| duration_ -= interval_; |
| if (duration_ <= 0) { |
| break; |
| } |
| } |
| } |
| } |
| private: |
| double load_; |
| double duration_; |
| double interval_; |
| }; |
| |
| class CpuLoadListener : public sigslot::has_slots<> { |
| public: |
| CpuLoadListener() |
| : current_cpus_(0), |
| cpus_(0), |
| process_load_(.0f), |
| system_load_(.0f), |
| count_(0) { |
| } |
| |
| void OnCpuLoad(int current_cpus, int cpus, float proc_load, float sys_load) { |
| current_cpus_ = current_cpus; |
| cpus_ = cpus; |
| process_load_ = proc_load; |
| system_load_ = sys_load; |
| ++count_; |
| } |
| |
| int current_cpus() const { return current_cpus_; } |
| int cpus() const { return cpus_; } |
| float process_load() const { return process_load_; } |
| float system_load() const { return system_load_; } |
| int count() const { return count_; } |
| |
| private: |
| int current_cpus_; |
| int cpus_; |
| float process_load_; |
| float system_load_; |
| int count_; |
| }; |
| |
| // Set affinity (which cpu to run on), but respecting FLAG_affinity: |
| // -1 means no affinity - run on whatever cpu is available. |
| // 0 .. N means run on specific cpu. The tool will create N threads and call |
| // SetThreadAffinity on 0 to N - 1 as cpu. FLAG_affinity sets the first cpu |
| // so the range becomes affinity to affinity + N - 1 |
| // Note that this function affects Windows scheduling, effectively giving |
| // the thread with affinity for a specified CPU more priority on that CPU. |
| bool SetThreadAffinity(BusyThread* t, int cpu, int affinity) { |
| #ifdef WIN32 |
| if (affinity >= 0) { |
| return ::SetThreadAffinityMask(t->GetHandle(), |
| 1 << (cpu + affinity)) != FALSE; |
| } |
| #endif |
| return true; |
| } |
| |
| bool SetThreadPriority(BusyThread* t, int prio) { |
| if (!prio) { |
| return true; |
| } |
| bool ok = t->SetPriority(static_cast<talk_base::ThreadPriority>(prio)); |
| if (!ok) { |
| std::cout << "Error setting thread priority." << std::endl; |
| } |
| return ok; |
| } |
| |
| int CpuLoad(double cpuload, double duration, int numthreads, |
| int priority, double interval, int affinity) { |
| int ret = 0; |
| std::vector<BusyThread*> threads; |
| for (int i = 0; i < numthreads; ++i) { |
| threads.push_back(new BusyThread(cpuload, duration, interval)); |
| // NOTE(fbarchard): Priority must be done before Start. |
| if (!SetThreadPriority(threads[i], priority) || |
| !threads[i]->Start() || |
| !SetThreadAffinity(threads[i], i, affinity)) { |
| ret = 1; |
| break; |
| } |
| } |
| // Wait on each thread |
| if (ret == 0) { |
| for (int i = 0; i < numthreads; ++i) { |
| threads[i]->Stop(); |
| } |
| } |
| |
| for (int i = 0; i < numthreads; ++i) { |
| delete threads[i]; |
| } |
| return ret; |
| } |
| |
| // Make 2 CPUs busy |
| static void CpuTwoBusyLoop(int busytime) { |
| CpuLoad(100.0, busytime / 1000.0, 2, 1, 0.050, -1); |
| } |
| |
| // Make 1 CPUs busy |
| static void CpuBusyLoop(int busytime) { |
| CpuLoad(100.0, busytime / 1000.0, 1, 1, 0.050, -1); |
| } |
| |
| // Make 1 use half CPU time. |
| static void CpuHalfBusyLoop(int busytime) { |
| CpuLoad(50.0, busytime / 1000.0, 1, 1, 0.050, -1); |
| } |
| |
| void TestCpuSampler(bool test_proc, bool test_sys, bool force_fallback) { |
| CpuSampler sampler; |
| sampler.set_force_fallback(force_fallback); |
| EXPECT_TRUE(sampler.Init()); |
| sampler.set_load_interval(100); |
| int cpus = sampler.GetMaxCpus(); |
| |
| // Test1: CpuSampler under idle situation. |
| Thread::SleepMs(kSettleTime); |
| sampler.GetProcessLoad(); |
| sampler.GetSystemLoad(); |
| |
| Thread::SleepMs(kIdleTime); |
| |
| float proc_idle = 0.f, sys_idle = 0.f; |
| if (test_proc) { |
| proc_idle = sampler.GetProcessLoad(); |
| } |
| if (test_sys) { |
| sys_idle = sampler.GetSystemLoad(); |
| } |
| if (test_proc) { |
| LOG(LS_INFO) << "ProcessLoad Idle: " |
| << setiosflags(std::ios_base::fixed) |
| << std::setprecision(2) << std::setw(6) << proc_idle; |
| EXPECT_GE(proc_idle, 0.f); |
| EXPECT_LE(proc_idle, static_cast<float>(cpus)); |
| } |
| if (test_sys) { |
| LOG(LS_INFO) << "SystemLoad Idle: " |
| << setiosflags(std::ios_base::fixed) |
| << std::setprecision(2) << std::setw(6) << sys_idle; |
| EXPECT_GE(sys_idle, 0.f); |
| EXPECT_LE(sys_idle, static_cast<float>(cpus)); |
| } |
| |
| // Test2: CpuSampler with main process at 50% busy. |
| Thread::SleepMs(kSettleTime); |
| sampler.GetProcessLoad(); |
| sampler.GetSystemLoad(); |
| |
| CpuHalfBusyLoop(kBusyTime); |
| |
| float proc_halfbusy = 0.f, sys_halfbusy = 0.f; |
| if (test_proc) { |
| proc_halfbusy = sampler.GetProcessLoad(); |
| } |
| if (test_sys) { |
| sys_halfbusy = sampler.GetSystemLoad(); |
| } |
| if (test_proc) { |
| LOG(LS_INFO) << "ProcessLoad Halfbusy: " |
| << setiosflags(std::ios_base::fixed) |
| << std::setprecision(2) << std::setw(6) << proc_halfbusy; |
| EXPECT_GE(proc_halfbusy, 0.f); |
| EXPECT_LE(proc_halfbusy, static_cast<float>(cpus)); |
| } |
| if (test_sys) { |
| LOG(LS_INFO) << "SystemLoad Halfbusy: " |
| << setiosflags(std::ios_base::fixed) |
| << std::setprecision(2) << std::setw(6) << sys_halfbusy; |
| EXPECT_GE(sys_halfbusy, 0.f); |
| EXPECT_LE(sys_halfbusy, static_cast<float>(cpus)); |
| } |
| |
| // Test3: CpuSampler with main process busy. |
| Thread::SleepMs(kSettleTime); |
| sampler.GetProcessLoad(); |
| sampler.GetSystemLoad(); |
| |
| CpuBusyLoop(kBusyTime); |
| |
| float proc_busy = 0.f, sys_busy = 0.f; |
| if (test_proc) { |
| proc_busy = sampler.GetProcessLoad(); |
| } |
| if (test_sys) { |
| sys_busy = sampler.GetSystemLoad(); |
| } |
| if (test_proc) { |
| LOG(LS_INFO) << "ProcessLoad Busy: " |
| << setiosflags(std::ios_base::fixed) |
| << std::setprecision(2) << std::setw(6) << proc_busy; |
| EXPECT_GE(proc_busy, 0.f); |
| EXPECT_LE(proc_busy, static_cast<float>(cpus)); |
| } |
| if (test_sys) { |
| LOG(LS_INFO) << "SystemLoad Busy: " |
| << setiosflags(std::ios_base::fixed) |
| << std::setprecision(2) << std::setw(6) << sys_busy; |
| EXPECT_GE(sys_busy, 0.f); |
| EXPECT_LE(sys_busy, static_cast<float>(cpus)); |
| } |
| |
| // Test4: CpuSampler with 2 cpus process busy. |
| if (cpus >= 2) { |
| Thread::SleepMs(kSettleTime); |
| sampler.GetProcessLoad(); |
| sampler.GetSystemLoad(); |
| |
| CpuTwoBusyLoop(kBusyTime); |
| |
| float proc_twobusy = 0.f, sys_twobusy = 0.f; |
| if (test_proc) { |
| proc_twobusy = sampler.GetProcessLoad(); |
| } |
| if (test_sys) { |
| sys_twobusy = sampler.GetSystemLoad(); |
| } |
| if (test_proc) { |
| LOG(LS_INFO) << "ProcessLoad 2 CPU Busy:" |
| << setiosflags(std::ios_base::fixed) |
| << std::setprecision(2) << std::setw(6) << proc_twobusy; |
| EXPECT_GE(proc_twobusy, 0.f); |
| EXPECT_LE(proc_twobusy, static_cast<float>(cpus)); |
| } |
| if (test_sys) { |
| LOG(LS_INFO) << "SystemLoad 2 CPU Busy: " |
| << setiosflags(std::ios_base::fixed) |
| << std::setprecision(2) << std::setw(6) << sys_twobusy; |
| EXPECT_GE(sys_twobusy, 0.f); |
| EXPECT_LE(sys_twobusy, static_cast<float>(cpus)); |
| } |
| } |
| |
| // Test5: CpuSampler with idle process after being busy. |
| Thread::SleepMs(kSettleTime); |
| sampler.GetProcessLoad(); |
| sampler.GetSystemLoad(); |
| |
| Thread::SleepMs(kIdleTime); |
| |
| if (test_proc) { |
| proc_idle = sampler.GetProcessLoad(); |
| } |
| if (test_sys) { |
| sys_idle = sampler.GetSystemLoad(); |
| } |
| if (test_proc) { |
| LOG(LS_INFO) << "ProcessLoad Idle: " |
| << setiosflags(std::ios_base::fixed) |
| << std::setprecision(2) << std::setw(6) << proc_idle; |
| EXPECT_GE(proc_idle, 0.f); |
| EXPECT_LE(proc_idle, proc_busy); |
| } |
| if (test_sys) { |
| LOG(LS_INFO) << "SystemLoad Idle: " |
| << setiosflags(std::ios_base::fixed) |
| << std::setprecision(2) << std::setw(6) << sys_idle; |
| EXPECT_GE(sys_idle, 0.f); |
| EXPECT_LE(sys_idle, static_cast<float>(cpus)); |
| } |
| } |
| |
| TEST(CpuMonitorTest, TestCpus) { |
| CpuSampler sampler; |
| EXPECT_TRUE(sampler.Init()); |
| int current_cpus = sampler.GetCurrentCpus(); |
| int cpus = sampler.GetMaxCpus(); |
| LOG(LS_INFO) << "Current Cpus: " << std::setw(9) << current_cpus; |
| LOG(LS_INFO) << "Maximum Cpus: " << std::setw(9) << cpus; |
| EXPECT_GT(cpus, 0); |
| EXPECT_LE(cpus, kMaxCpus); |
| EXPECT_GT(current_cpus, 0); |
| EXPECT_LE(current_cpus, cpus); |
| } |
| |
| #ifdef WIN32 |
| // Tests overall system CpuSampler using legacy OS fallback code if applicable. |
| TEST(CpuMonitorTest, TestGetSystemLoadForceFallback) { |
| TestCpuSampler(false, true, true); |
| } |
| #endif |
| |
| // Tests both process and system functions in use at same time. |
| TEST(CpuMonitorTest, TestGetBothLoad) { |
| TestCpuSampler(true, true, false); |
| } |
| |
| // Tests a query less than the interval produces the same value. |
| TEST(CpuMonitorTest, TestInterval) { |
| CpuSampler sampler; |
| EXPECT_TRUE(sampler.Init()); |
| |
| // Test1: Interval of 500 ms |
| sampler.set_load_interval(500); |
| |
| sampler.GetProcessLoad(); |
| sampler.GetSystemLoad(); |
| |
| float proc_orig = sampler.GetProcessLoad(); |
| float sys_orig = sampler.GetSystemLoad(); |
| |
| CpuBusyLoop(200); |
| |
| float proc_halftime = sampler.GetProcessLoad(); |
| float sys_halftime = sampler.GetSystemLoad(); |
| |
| EXPECT_EQ(proc_orig, proc_halftime); |
| EXPECT_EQ(sys_orig, sys_halftime); |
| } |
| |
| TEST(CpuMonitorTest, TestCpuMonitor) { |
| CpuMonitor monitor(Thread::Current()); |
| CpuLoadListener listener; |
| monitor.SignalUpdate.connect(&listener, &CpuLoadListener::OnCpuLoad); |
| EXPECT_TRUE(monitor.Start(10)); |
| Thread::Current()->ProcessMessages(50); |
| EXPECT_GT(listener.count(), 2); // We have checked cpu load more than twice. |
| EXPECT_GT(listener.current_cpus(), 0); |
| EXPECT_GT(listener.cpus(), 0); |
| EXPECT_GE(listener.process_load(), .0f); |
| EXPECT_GE(listener.system_load(), .0f); |
| |
| monitor.Stop(); |
| // Wait 20 ms to ake sure all signals are delivered. |
| Thread::Current()->ProcessMessages(20); |
| int old_count = listener.count(); |
| Thread::Current()->ProcessMessages(20); |
| // Verfy no more siganls. |
| EXPECT_EQ(old_count, listener.count()); |
| } |
| |
| } // namespace talk_base |