// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "stdafx.h" #include #include #include #include "power_sampler.h" #include "system_information_sampler.h" // Result data structure contains a final set of values calculated based on // comparison of two snapshots. These are the values that the tool prints // in the output. struct Result { ULONG idle_wakeups_per_sec; double cpu_usage; ULONGLONG working_set; double power; }; typedef std::vector ResultVector; // The following 4 functions are used for sorting of ResultVector. ULONG GetIdleWakeupsPerSec(const Result& r) { return r.idle_wakeups_per_sec; } double GetCpuUsage(const Result& r) { return r.cpu_usage; } ULONGLONG GetWorkingSet(const Result& r) { return r.working_set; } double GetPower(const Result& r) { return r.power; } template T GetMedian(ResultVector* results, T (*getter)(const Result&)) { std::sort(results->begin(), results->end(), [&](const Result& lhs, const Result& rhs) { return getter(lhs) < getter(rhs); }); size_t median_index = results->size() / 2; if (results->size() % 2 != 0) { return getter((*results)[median_index]); } else { return (getter((*results)[median_index - 1]) + getter((*results)[median_index])) / 2; } } // This class holds the app state and constains a number of utilities for // collecting and diffing snapshots of data, handling processes, etc. class IdleWakeups { public: IdleWakeups(); ~IdleWakeups(); Result DiffSnapshots(const ProcessDataSnapshot& prev_snapshot, const ProcessDataSnapshot& snapshot); void OpenProcesses(const ProcessDataSnapshot& snapshot); void CloseProcesses(); private: HANDLE GetProcessHandle(ProcessId process_id); void OpenProcess(ProcessId process_id); void CloseProcess(ProcessId process_id); bool GetFinishedProcessCpuTime(ProcessId process_id, ULONGLONG* cpu_usage); static ULONG CountContextSwitches(const ProcessData& process_data); static ULONG DiffContextSwitches(const ProcessData& prev_process_data, const ProcessData& process_data); std::map process_id_to_hanle_map; IdleWakeups& operator=(const IdleWakeups&) = delete; IdleWakeups(const IdleWakeups&) = delete; }; IdleWakeups::IdleWakeups() {} IdleWakeups::~IdleWakeups() { CloseProcesses(); } void IdleWakeups::OpenProcesses(const ProcessDataSnapshot& snapshot) { for (auto& pair : snapshot.processes) { OpenProcess(pair.first); } } void IdleWakeups::CloseProcesses() { for (auto& pair : process_id_to_hanle_map) { CloseHandle(pair.second); } process_id_to_hanle_map.clear(); } HANDLE IdleWakeups::GetProcessHandle(ProcessId process_id) { return process_id_to_hanle_map[process_id]; } void IdleWakeups::OpenProcess(ProcessId process_id) { process_id_to_hanle_map[process_id] = ::OpenProcess( PROCESS_QUERY_LIMITED_INFORMATION, FALSE, (DWORD)(ULONGLONG)process_id); } void IdleWakeups::CloseProcess(ProcessId process_id) { HANDLE handle = GetProcessHandle(process_id); CloseHandle(handle); process_id_to_hanle_map.erase(process_id); } ULONG IdleWakeups::CountContextSwitches(const ProcessData& process_data) { ULONG context_switches = 0; for (const auto& thread_data : process_data.threads) { context_switches += thread_data.context_switches; } return context_switches; } ULONG IdleWakeups::DiffContextSwitches(const ProcessData& prev_process_data, const ProcessData& process_data) { ULONG context_switches = 0; size_t prev_index = 0; for (const auto& thread_data : process_data.threads) { ULONG prev_context_switches = 0; for (; prev_index < prev_process_data.threads.size(); ++prev_index) { const auto& prev_thread_data = prev_process_data.threads[prev_index]; if (prev_thread_data.thread_id == thread_data.thread_id) { prev_context_switches = prev_thread_data.context_switches; ++prev_index; break; } if (prev_thread_data.thread_id > thread_data.thread_id) break; } context_switches += thread_data.context_switches - prev_context_switches; } return context_switches; } bool IdleWakeups::GetFinishedProcessCpuTime(ProcessId process_id, ULONGLONG* cpu_time) { HANDLE process_handle = GetProcessHandle(process_id); FILETIME creation_time, exit_time, kernel_time, user_time; if (GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) { ULARGE_INTEGER ul_kernel_time, ul_user_time; ul_kernel_time.LowPart = kernel_time.dwLowDateTime; ul_kernel_time.HighPart = kernel_time.dwHighDateTime; ul_user_time.LowPart = user_time.dwLowDateTime; ul_user_time.HighPart = user_time.dwHighDateTime; *cpu_time = ul_kernel_time.QuadPart + ul_user_time.QuadPart; return true; } *cpu_time = 0; return false; } Result IdleWakeups::DiffSnapshots(const ProcessDataSnapshot& prev_snapshot, const ProcessDataSnapshot& snapshot) { ULONG idle_wakeups_delta = 0; ULONGLONG cpu_usage_delta = 0; ULONGLONG total_working_set = 0; ProcessDataMap::const_iterator prev_it = prev_snapshot.processes.begin(); for (const auto& it : snapshot.processes) { ProcessId process_id = it.first; const ProcessData& process_data = it.second; const ProcessData* prev_process_data_to_diff = nullptr; ULONGLONG prev_process_cpu_time = 0; for (; prev_it != prev_snapshot.processes.end(); ++prev_it) { ProcessId prev_process_id = prev_it->first; const ProcessData& prev_process_data = prev_it->second; if (prev_process_id == process_id) { prev_process_data_to_diff = &prev_process_data; prev_process_cpu_time = prev_process_data.cpu_time; ++prev_it; break; } if (prev_process_id > process_id) break; // Prev process disappeared. ULONGLONG last_known_cpu_time; if (GetFinishedProcessCpuTime(prev_process_id, &last_known_cpu_time)) { cpu_usage_delta += last_known_cpu_time - prev_process_data.cpu_time; } CloseProcess(prev_process_id); } if (prev_process_data_to_diff) { idle_wakeups_delta += DiffContextSwitches(*prev_process_data_to_diff, process_data); } else { // New process that we haven't seen before. OpenProcess(process_id); idle_wakeups_delta += CountContextSwitches(process_data); } cpu_usage_delta += process_data.cpu_time - prev_process_cpu_time; total_working_set += process_data.working_set / 1024; } double time_delta = snapshot.timestamp - prev_snapshot.timestamp; Result result; result.idle_wakeups_per_sec = static_cast(idle_wakeups_delta / time_delta); // brucedawson: Don't divide by number of processors so that all numbers are // percentage of a core // result.cpu_usage = (double)cpu_usage_delta * 100 / (time_delta * 10000000 * // NumberOfprocessors()); result.cpu_usage = (double)cpu_usage_delta * 100 / (time_delta * 10000000); result.working_set = total_working_set; return result; } HANDLE ctrl_c_pressed = NULL; BOOL WINAPI HandlerFunction(DWORD ctrl_type) { if (ctrl_type == CTRL_C_EVENT) { printf("Ctrl+C pressed...\n"); SetEvent(ctrl_c_pressed); return TRUE; } return FALSE; } const DWORD sleep_time_sec = 2; void PrintHeader() { printf( "------------------------------------------------------------------------" "----------\n"); printf( " Context switches/sec CPU usage Working set " " Power\n"); printf( "------------------------------------------------------------------------" "----------\n"); } #define RESULT_FORMAT_STRING " %20lu %8.2f%c %6.2f MiB %4.2f W\n" int wmain(int argc, wchar_t* argv[]) { ctrl_c_pressed = CreateEvent(NULL, FALSE, FALSE, NULL); SetConsoleCtrlHandler(HandlerFunction, TRUE); PowerSampler power_sampler; SystemInformationSampler system_information_sampler(argc > 1 ? argv[1] : L"chrome.exe"); IdleWakeups the_app; // Take the initial snapshot. std::unique_ptr previous_snapshot = system_information_sampler.TakeSnapshot(); the_app.OpenProcesses(*previous_snapshot); ULONG cumulative_idle_wakeups_per_sec = 0; double cumulative_cpu_usage = 0.0; ULONGLONG cumulative_working_set = 0; double cumulative_energy = 0.0; ResultVector results; printf("Capturing perf data for all processes matching %ls\n", system_information_sampler.target_process_name_filter()); PrintHeader(); for (;;) { if (WaitForSingleObject(ctrl_c_pressed, sleep_time_sec * 1000) == WAIT_OBJECT_0) break; std::unique_ptr snapshot = system_information_sampler.TakeSnapshot(); size_t number_of_processes = snapshot->processes.size(); Result result = the_app.DiffSnapshots(*previous_snapshot, *snapshot); previous_snapshot = std::move(snapshot); power_sampler.SampleCPUPowerState(); result.power = power_sampler.get_power(L"Processor"); printf("%9u processes" RESULT_FORMAT_STRING, (DWORD)number_of_processes, result.idle_wakeups_per_sec, result.cpu_usage, '%', result.working_set / 1024.0, result.power); cumulative_idle_wakeups_per_sec += result.idle_wakeups_per_sec; cumulative_cpu_usage += result.cpu_usage; cumulative_working_set += result.working_set; cumulative_energy += result.power; results.push_back(result); } CloseHandle(ctrl_c_pressed); ULONG sample_count = (ULONG)results.size(); if (sample_count == 0) return 0; PrintHeader(); printf(" Average" RESULT_FORMAT_STRING, cumulative_idle_wakeups_per_sec / sample_count, cumulative_cpu_usage / sample_count, '%', (cumulative_working_set / 1024.0) / sample_count, cumulative_energy / sample_count); Result median_result; median_result.idle_wakeups_per_sec = GetMedian(&results, GetIdleWakeupsPerSec); median_result.cpu_usage = GetMedian(&results, GetCpuUsage); median_result.working_set = GetMedian(&results, GetWorkingSet); median_result.power = GetMedian(&results, GetPower); printf(" Median" RESULT_FORMAT_STRING, median_result.idle_wakeups_per_sec, median_result.cpu_usage, '%', median_result.working_set / 1024.0, median_result.power); return 0; }