Index: tools/win/IdleWakeups/idle_wakeups.cpp |
diff --git a/tools/win/IdleWakeups/idle_wakeups.cpp b/tools/win/IdleWakeups/idle_wakeups.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..cb9dacc9d1396de0dfa6123e49110f2100dfa8c9 |
--- /dev/null |
+++ b/tools/win/IdleWakeups/idle_wakeups.cpp |
@@ -0,0 +1,348 @@ |
+// 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 <algorithm> |
+#include <map> |
+#include <vector> |
+ |
+#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<Result> 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 <typename T> |
+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<ProcessId, HANDLE> 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<ULONG>(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<ProcessDataSnapshot> 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<ProcessDataSnapshot> 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<ULONG>(&results, GetIdleWakeupsPerSec); |
+ median_result.cpu_usage = GetMedian<double>(&results, GetCpuUsage); |
+ median_result.working_set = GetMedian<ULONGLONG>(&results, GetWorkingSet); |
+ median_result.power = GetMedian<double>(&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; |
+} |