1 /* 2 * libjingle 3 * Copyright 2010 Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include "talk/base/cpumonitor.h" 29 30 #include <string> 31 32 #include "talk/base/common.h" 33 #include "talk/base/logging.h" 34 #include "talk/base/scoped_ptr.h" 35 #include "talk/base/systeminfo.h" 36 #include "talk/base/thread.h" 37 #include "talk/base/timeutils.h" 38 39 #ifdef WIN32 40 #include "talk/base/win32.h" 41 #include <winternl.h> 42 #endif 43 44 #ifdef POSIX 45 #include <sys/time.h> 46 #endif 47 48 #if defined(IOS) || defined(OSX) 49 #include <mach/mach_host.h> 50 #include <mach/mach_init.h> 51 #include <mach/mach_port.h> 52 #include <mach/host_info.h> 53 #include <mach/task.h> 54 #endif // defined(IOS) || defined(OSX) 55 56 #if defined(LINUX) || defined(ANDROID) 57 #include <sys/resource.h> 58 #include <errno.h> 59 #include <stdio.h> 60 #include "talk/base/fileutils.h" 61 #include "talk/base/pathutils.h" 62 #endif // defined(LINUX) || defined(ANDROID) 63 64 #if defined(IOS) || defined(OSX) 65 static uint64 TimeValueTToInt64(const time_value_t &time_value) { 66 return talk_base::kNumMicrosecsPerSec * time_value.seconds + 67 time_value.microseconds; 68 } 69 #endif // defined(IOS) || defined(OSX) 70 71 // How CpuSampler works 72 // When threads switch, the time they spent is accumulated to system counters. 73 // The time can be treated as user, kernel or idle. 74 // user time is applications. 75 // kernel time is the OS, including the thread switching code itself. 76 // typically kernel time indicates IO. 77 // idle time is a process that wastes time when nothing is ready to run. 78 // 79 // User time is broken down by process (application). One of the applications 80 // is the current process. When you add up all application times, this is 81 // system time. If only your application is running, system time should be the 82 // same as process time. 83 // 84 // All cores contribute to these accumulators. A dual core process is able to 85 // process twice as many cycles as a single core. The actual code efficiency 86 // may be worse, due to contention, but the available cycles is exactly twice 87 // as many, and the cpu load will reflect the efficiency. Hyperthreads behave 88 // the same way. The load will reflect 200%, but the actual amount of work 89 // completed will be much less than a true dual core. 90 // 91 // Total available performance is the sum of all accumulators. 92 // If you tracked this for 1 second, it would essentially give you the clock 93 // rate - number of cycles per second. 94 // Speed step / Turbo Boost is not considered, so infact more processing time 95 // may be available. 96 97 namespace talk_base { 98 99 // Note Tests on Windows show 600 ms is minimum stable interval for Windows 7. 100 static const int32 kDefaultInterval = 950; // Slightly under 1 second. 101 102 CpuSampler::CpuSampler() 103 : min_load_interval_(kDefaultInterval) 104 #ifdef WIN32 105 , get_system_times_(NULL), 106 nt_query_system_information_(NULL), 107 force_fallback_(false) 108 #endif 109 { 110 } 111 112 CpuSampler::~CpuSampler() { 113 } 114 115 // Set minimum interval in ms between computing new load values. Default 950. 116 void CpuSampler::set_load_interval(int min_load_interval) { 117 min_load_interval_ = min_load_interval; 118 } 119 120 bool CpuSampler::Init() { 121 sysinfo_.reset(new SystemInfo); 122 cpus_ = sysinfo_->GetMaxCpus(); 123 if (cpus_ == 0) { 124 return false; 125 } 126 #ifdef WIN32 127 // Note that GetSystemTimes is available in Windows XP SP1 or later. 128 // http://msdn.microsoft.com/en-us/library/ms724400.aspx 129 // NtQuerySystemInformation is used as a fallback. 130 if (!force_fallback_) { 131 get_system_times_ = GetProcAddress(GetModuleHandle(L"kernel32.dll"), 132 "GetSystemTimes"); 133 } 134 nt_query_system_information_ = GetProcAddress(GetModuleHandle(L"ntdll.dll"), 135 "NtQuerySystemInformation"); 136 if ((get_system_times_ == NULL) && (nt_query_system_information_ == NULL)) { 137 return false; 138 } 139 #endif 140 #if defined(LINUX) || defined(ANDROID) 141 Pathname sname("/proc/stat"); 142 sfile_.reset(Filesystem::OpenFile(sname, "rb")); 143 if (!sfile_) { 144 LOG_ERR(LS_ERROR) << "open proc/stat failed:"; 145 return false; 146 } 147 if (!sfile_->DisableBuffering()) { 148 LOG_ERR(LS_ERROR) << "could not disable buffering for proc/stat"; 149 return false; 150 } 151 #endif // defined(LINUX) || defined(ANDROID) 152 GetProcessLoad(); // Initialize values. 153 GetSystemLoad(); 154 // Help next user call return valid data by recomputing load. 155 process_.prev_load_time_ = 0u; 156 system_.prev_load_time_ = 0u; 157 return true; 158 } 159 160 float CpuSampler::UpdateCpuLoad(uint64 current_total_times, 161 uint64 current_cpu_times, 162 uint64 *prev_total_times, 163 uint64 *prev_cpu_times) { 164 float result = 0.f; 165 if (current_total_times < *prev_total_times || 166 current_cpu_times < *prev_cpu_times) { 167 LOG(LS_ERROR) << "Inconsistent time values are passed. ignored"; 168 } else { 169 const uint64 cpu_diff = current_cpu_times - *prev_cpu_times; 170 const uint64 total_diff = current_total_times - *prev_total_times; 171 result = (total_diff == 0ULL ? 0.f : 172 static_cast<float>(1.0f * cpu_diff / total_diff)); 173 if (result > static_cast<float>(cpus_)) { 174 result = static_cast<float>(cpus_); 175 } 176 *prev_total_times = current_total_times; 177 *prev_cpu_times = current_cpu_times; 178 } 179 return result; 180 } 181 182 float CpuSampler::GetSystemLoad() { 183 uint32 timenow = Time(); 184 int elapsed = static_cast<int>(TimeDiff(timenow, system_.prev_load_time_)); 185 if (min_load_interval_ != 0 && system_.prev_load_time_ != 0u && 186 elapsed < min_load_interval_) { 187 return system_.prev_load_; 188 } 189 #ifdef WIN32 190 uint64 total_times, cpu_times; 191 192 typedef BOOL (_stdcall *GST_PROC)(LPFILETIME, LPFILETIME, LPFILETIME); 193 typedef NTSTATUS (WINAPI *QSI_PROC)(SYSTEM_INFORMATION_CLASS, 194 PVOID, ULONG, PULONG); 195 196 GST_PROC get_system_times = reinterpret_cast<GST_PROC>(get_system_times_); 197 QSI_PROC nt_query_system_information = reinterpret_cast<QSI_PROC>( 198 nt_query_system_information_); 199 200 if (get_system_times) { 201 FILETIME idle_time, kernel_time, user_time; 202 if (!get_system_times(&idle_time, &kernel_time, &user_time)) { 203 LOG(LS_ERROR) << "::GetSystemTimes() failed: " << ::GetLastError(); 204 return 0.f; 205 } 206 // kernel_time includes Kernel idle time, so no need to 207 // include cpu_time as total_times 208 total_times = ToUInt64(kernel_time) + ToUInt64(user_time); 209 cpu_times = total_times - ToUInt64(idle_time); 210 211 } else { 212 if (nt_query_system_information) { 213 ULONG returned_length = 0; 214 scoped_ptr<SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[]> processor_info( 215 new SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[cpus_]); 216 nt_query_system_information( 217 ::SystemProcessorPerformanceInformation, 218 reinterpret_cast<void*>(processor_info.get()), 219 cpus_ * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION), 220 &returned_length); 221 222 if (returned_length != 223 (cpus_ * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION))) { 224 LOG(LS_ERROR) << "NtQuerySystemInformation has unexpected size"; 225 return 0.f; 226 } 227 228 uint64 current_idle = 0; 229 uint64 current_kernel = 0; 230 uint64 current_user = 0; 231 for (int ix = 0; ix < cpus_; ++ix) { 232 current_idle += processor_info[ix].IdleTime.QuadPart; 233 current_kernel += processor_info[ix].UserTime.QuadPart; 234 current_user += processor_info[ix].KernelTime.QuadPart; 235 } 236 total_times = current_kernel + current_user; 237 cpu_times = total_times - current_idle; 238 } else { 239 return 0.f; 240 } 241 } 242 #endif // WIN32 243 244 #if defined(IOS) || defined(OSX) 245 mach_port_t mach_host = mach_host_self(); 246 host_cpu_load_info_data_t cpu_info; 247 mach_msg_type_number_t info_count = HOST_CPU_LOAD_INFO_COUNT; 248 kern_return_t kr = host_statistics(mach_host, HOST_CPU_LOAD_INFO, 249 reinterpret_cast<host_info_t>(&cpu_info), 250 &info_count); 251 mach_port_deallocate(mach_task_self(), mach_host); 252 if (KERN_SUCCESS != kr) { 253 LOG(LS_ERROR) << "::host_statistics() failed"; 254 return 0.f; 255 } 256 257 const uint64 cpu_times = cpu_info.cpu_ticks[CPU_STATE_NICE] + 258 cpu_info.cpu_ticks[CPU_STATE_SYSTEM] + 259 cpu_info.cpu_ticks[CPU_STATE_USER]; 260 const uint64 total_times = cpu_times + cpu_info.cpu_ticks[CPU_STATE_IDLE]; 261 #endif // defined(IOS) || defined(OSX) 262 263 #if defined(LINUX) || defined(ANDROID) 264 if (!sfile_) { 265 LOG(LS_ERROR) << "Invalid handle for proc/stat"; 266 return 0.f; 267 } 268 std::string statbuf; 269 sfile_->SetPosition(0); 270 if (!sfile_->ReadLine(&statbuf)) { 271 LOG_ERR(LS_ERROR) << "Could not read proc/stat file"; 272 return 0.f; 273 } 274 275 unsigned long long user; 276 unsigned long long nice; 277 unsigned long long system; 278 unsigned long long idle; 279 if (sscanf(statbuf.c_str(), "cpu %Lu %Lu %Lu %Lu", 280 &user, &nice, 281 &system, &idle) != 4) { 282 LOG_ERR(LS_ERROR) << "Could not parse cpu info"; 283 return 0.f; 284 } 285 const uint64 cpu_times = nice + system + user; 286 const uint64 total_times = cpu_times + idle; 287 #endif // defined(LINUX) || defined(ANDROID) 288 289 #if defined(__native_client__) 290 // TODO(ryanpetrie): Implement this via PPAPI when it's available. 291 const uint64 cpu_times = 0; 292 const uint64 total_times = 0; 293 #endif // defined(__native_client__) 294 295 system_.prev_load_time_ = timenow; 296 system_.prev_load_ = UpdateCpuLoad(total_times, 297 cpu_times * cpus_, 298 &system_.prev_total_times_, 299 &system_.prev_cpu_times_); 300 return system_.prev_load_; 301 } 302 303 float CpuSampler::GetProcessLoad() { 304 uint32 timenow = Time(); 305 int elapsed = static_cast<int>(TimeDiff(timenow, process_.prev_load_time_)); 306 if (min_load_interval_ != 0 && process_.prev_load_time_ != 0u && 307 elapsed < min_load_interval_) { 308 return process_.prev_load_; 309 } 310 #ifdef WIN32 311 FILETIME current_file_time; 312 ::GetSystemTimeAsFileTime(¤t_file_time); 313 314 FILETIME create_time, exit_time, kernel_time, user_time; 315 if (!::GetProcessTimes(::GetCurrentProcess(), 316 &create_time, &exit_time, &kernel_time, &user_time)) { 317 LOG(LS_ERROR) << "::GetProcessTimes() failed: " << ::GetLastError(); 318 return 0.f; 319 } 320 321 const uint64 total_times = 322 ToUInt64(current_file_time) - ToUInt64(create_time); 323 const uint64 cpu_times = 324 (ToUInt64(kernel_time) + ToUInt64(user_time)); 325 #endif // WIN32 326 327 #ifdef POSIX 328 // Common to both OSX and Linux. 329 struct timeval tv; 330 gettimeofday(&tv, NULL); 331 const uint64 total_times = tv.tv_sec * kNumMicrosecsPerSec + tv.tv_usec; 332 #endif 333 334 #if defined(IOS) || defined(OSX) 335 // Get live thread usage. 336 task_thread_times_info task_times_info; 337 mach_msg_type_number_t info_count = TASK_THREAD_TIMES_INFO_COUNT; 338 339 if (KERN_SUCCESS != task_info(mach_task_self(), TASK_THREAD_TIMES_INFO, 340 reinterpret_cast<task_info_t>(&task_times_info), 341 &info_count)) { 342 LOG(LS_ERROR) << "::task_info(TASK_THREAD_TIMES_INFO) failed"; 343 return 0.f; 344 } 345 346 // Get terminated thread usage. 347 task_basic_info task_term_info; 348 info_count = TASK_BASIC_INFO_COUNT; 349 if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO, 350 reinterpret_cast<task_info_t>(&task_term_info), 351 &info_count)) { 352 LOG(LS_ERROR) << "::task_info(TASK_BASIC_INFO) failed"; 353 return 0.f; 354 } 355 356 const uint64 cpu_times = (TimeValueTToInt64(task_times_info.user_time) + 357 TimeValueTToInt64(task_times_info.system_time) + 358 TimeValueTToInt64(task_term_info.user_time) + 359 TimeValueTToInt64(task_term_info.system_time)); 360 #endif // defined(IOS) || defined(OSX) 361 362 #if defined(LINUX) || defined(ANDROID) 363 rusage usage; 364 if (getrusage(RUSAGE_SELF, &usage) < 0) { 365 LOG_ERR(LS_ERROR) << "getrusage failed"; 366 return 0.f; 367 } 368 369 const uint64 cpu_times = 370 (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) * kNumMicrosecsPerSec + 371 usage.ru_utime.tv_usec + usage.ru_stime.tv_usec; 372 #endif // defined(LINUX) || defined(ANDROID) 373 374 #if defined(__native_client__) 375 // TODO(ryanpetrie): Implement this via PPAPI when it's available. 376 const uint64 cpu_times = 0; 377 #endif // defined(__native_client__) 378 379 process_.prev_load_time_ = timenow; 380 process_.prev_load_ = UpdateCpuLoad(total_times, 381 cpu_times, 382 &process_.prev_total_times_, 383 &process_.prev_cpu_times_); 384 return process_.prev_load_; 385 } 386 387 int CpuSampler::GetMaxCpus() const { 388 return cpus_; 389 } 390 391 int CpuSampler::GetCurrentCpus() { 392 return sysinfo_->GetCurCpus(); 393 } 394 395 /////////////////////////////////////////////////////////////////// 396 // Implementation of class CpuMonitor. 397 CpuMonitor::CpuMonitor(Thread* thread) 398 : monitor_thread_(thread) { 399 } 400 401 CpuMonitor::~CpuMonitor() { 402 Stop(); 403 } 404 405 void CpuMonitor::set_thread(Thread* thread) { 406 ASSERT(monitor_thread_ == NULL || monitor_thread_ == thread); 407 monitor_thread_ = thread; 408 } 409 410 bool CpuMonitor::Start(int period_ms) { 411 if (!monitor_thread_ || !sampler_.Init()) return false; 412 413 monitor_thread_->SignalQueueDestroyed.connect( 414 this, &CpuMonitor::OnMessageQueueDestroyed); 415 416 period_ms_ = period_ms; 417 monitor_thread_->PostDelayed(period_ms_, this); 418 419 return true; 420 } 421 422 void CpuMonitor::Stop() { 423 if (monitor_thread_) { 424 monitor_thread_->Clear(this); 425 } 426 } 427 428 void CpuMonitor::OnMessage(Message* msg) { 429 int max_cpus = sampler_.GetMaxCpus(); 430 int current_cpus = sampler_.GetCurrentCpus(); 431 float process_load = sampler_.GetProcessLoad(); 432 float system_load = sampler_.GetSystemLoad(); 433 SignalUpdate(current_cpus, max_cpus, process_load, system_load); 434 435 if (monitor_thread_) { 436 monitor_thread_->PostDelayed(period_ms_, this); 437 } 438 } 439 440 } // namespace talk_base 441