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..0da1b96bc20312d4ca48e46521f8bf68e72c094a |
--- /dev/null |
+++ b/tools/win/IdleWakeups/idle_wakeups.cpp |
@@ -0,0 +1,629 @@ |
+// 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 <map> |
+#include <vector> |
+#include <algorithm> |
+ |
+#include "power_sampler.h" |
+ |
+// From ntdef.h |
+typedef struct _UNICODE_STRING { |
+ USHORT Length; |
+ USHORT MaximumLength; |
+ PWCH Buffer; |
+} UNICODE_STRING; |
+ |
+// From <wdm.h> |
+typedef LONG KPRIORITY; |
+typedef LONG KWAIT_REASON; // Full definition is in wdm.h |
+ |
+// From ntddk.h |
+typedef struct _VM_COUNTERS { |
+ SIZE_T PeakVirtualSize; |
+ SIZE_T VirtualSize; |
+ ULONG PageFaultCount; |
+ // Padding here in 64-bit |
+ SIZE_T PeakWorkingSetSize; |
+ SIZE_T WorkingSetSize; |
+ SIZE_T QuotaPeakPagedPoolUsage; |
+ SIZE_T QuotaPagedPoolUsage; |
+ SIZE_T QuotaPeakNonPagedPoolUsage; |
+ SIZE_T QuotaNonPagedPoolUsage; |
+ SIZE_T PagefileUsage; |
+ SIZE_T PeakPagefileUsage; |
+} VM_COUNTERS; |
+ |
+// Two possibilities available from here: |
+// http://stackoverflow.com/questions/28858849/where-is-system-information-class-defined |
+ |
+typedef enum _SYSTEM_INFORMATION_CLASS { |
+ SystemBasicInformation = 0, |
+ SystemPerformanceInformation = 2, |
+ SystemTimeOfDayInformation = 3, |
+ SystemProcessInformation = 5, // This is the number that we need |
+ SystemProcessorPerformanceInformation = 8, |
+ SystemInterruptInformation = 23, |
+ SystemExceptionInformation = 33, |
+ SystemRegistryQuotaInformation = 37, |
+ SystemLookasideInformation = 45 |
+} SYSTEM_INFORMATION_CLASS; |
+ |
+// https://msdn.microsoft.com/en-us/library/gg750647.aspx?f=255&MSPPError=-2147217396 |
+typedef struct { |
+ HANDLE UniqueProcess; // Actually process ID |
+ HANDLE UniqueThread; // Actually thread ID |
+} CLIENT_ID; |
+ |
+// From http://alax.info/blog/1182, with corrections and modifications |
+// Originally from |
+// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FSystem%20Information%2FStructures%2FSYSTEM_THREAD.html |
+struct SYSTEM_THREAD_INFORMATION { |
+ ULONGLONG KernelTime; |
+ ULONGLONG UserTime; |
+ ULONGLONG CreateTime; |
+ ULONG WaitTime; |
+ // Padding here in 64-bit |
+ PVOID StartAddress; |
+ CLIENT_ID ClientId; |
+ KPRIORITY Priority; |
+ LONG BasePriority; |
+ ULONG ContextSwitchCount; |
+ ULONG State; |
+ KWAIT_REASON WaitReason; |
+}; |
+#if _M_X64 |
+static_assert(sizeof(SYSTEM_THREAD_INFORMATION) == 80, |
+ "Structure size mismatch"); |
+#else |
+static_assert(sizeof(SYSTEM_THREAD_INFORMATION) == 64, |
+ "Structure size mismatch"); |
+#endif |
+ |
+// From http://alax.info/blog/1182, with corrections and modifications |
+// Originally from |
+// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FSystem%20Information%2FStructures%2FSYSTEM_THREAD.html |
+struct SYSTEM_PROCESS_INFORMATION { |
+ ULONG NextEntryOffset; |
+ ULONG NumberOfThreads; |
+ // http://processhacker.sourceforge.net/doc/struct___s_y_s_t_e_m___p_r_o_c_e_s_s___i_n_f_o_r_m_a_t_i_o_n.html |
+ ULONGLONG WorkingSetPrivateSize; |
+ ULONG HardFaultCount; |
+ ULONG Reserved1; |
+ ULONGLONG CycleTime; |
+ ULONGLONG CreateTime; |
+ ULONGLONG UserTime; |
+ ULONGLONG KernelTime; |
+ UNICODE_STRING ImageName; |
+ KPRIORITY BasePriority; |
+ HANDLE ProcessId; |
+ HANDLE ParentProcessId; |
+ ULONG HandleCount; |
+ ULONG Reserved2[2]; |
+ // Padding here in 64-bit |
+ VM_COUNTERS VirtualMemoryCounters; |
+ size_t Reserved3; |
+ IO_COUNTERS IoCounters; |
+ SYSTEM_THREAD_INFORMATION Threads[1]; |
+}; |
+#if _M_X64 |
+static_assert(sizeof(SYSTEM_PROCESS_INFORMATION) == 336, |
+ "Structure size mismatch"); |
+#else |
+static_assert(sizeof(SYSTEM_PROCESS_INFORMATION) == 248, |
+ "Structure size mismatch"); |
+#endif |
+ |
+// ntstatus.h conflicts with windows.h so define this locally. |
+#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) |
+#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) |
+#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) |
+ |
+typedef NTSTATUS(WINAPI* NTQUERYSYSTEMINFORMATION)( |
+ SYSTEM_INFORMATION_CLASS SystemInformationClass, |
+ PVOID SystemInformation, |
+ ULONG SystemInformationLength, |
+ PULONG ReturnLength); |
+ |
+__declspec(noreturn) void oops(const char* pMessage) { |
+ printf("%s\n", pMessage); |
+ exit(0); |
+} |
+ |
+// Contains per thread data stored in each data snapshot. |
+struct ThreadData { |
+ HANDLE ThreadId; |
+ ULONG ContextSwitches; |
+}; |
+ |
+typedef std::vector<ThreadData> ThreadsVector; |
+ |
+// Contains per process data stored in each data snapshot. |
+struct ProcessData { |
+ HANDLE ProcessId; |
+ ULONGLONG CpuTime; |
+ ULONGLONG WorkingSetPrivateSize; |
+ ThreadsVector Threads; |
+}; |
+ |
+// A vector of ProcessData represents one snapshot of perf data |
+// collected by the tool each collection interval. |
+typedef std::vector<ProcessData> ProcessesVector; |
+ |
+// 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 IdleWakeupsPerSec; |
+ double CpuUsage; |
+ ULONGLONG WorkingSet; |
+ double Power; |
+}; |
+ |
+typedef std::vector<Result> ResultVector; |
+ |
+// The following 4 functions are used for sorting of ResultVector. |
+ULONG GetIdleWakeupsPerSec(const Result& r) { |
+ return r.IdleWakeupsPerSec; |
+} |
+double GetCpuUsage(const Result& r) { |
+ return r.CpuUsage; |
+} |
+ULONGLONG GetWorkingSet(const Result& r) { |
+ return r.WorkingSet; |
+} |
+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(const wchar_t* process_name); |
+ ~IdleWakeups(); |
+ |
+ double TakeSnapshot(ProcessesVector* processes); |
+ Result DiffSnapshots(double time_delta, |
+ const ProcessesVector& prev_processes, |
+ const ProcessesVector& processes); |
+ |
+ void OpenProcesses(const ProcessesVector& processes); |
+ void CloseProcesses(); |
+ |
+ const wchar_t* target_process_name_filter() const { |
+ return target_process_name; |
+ } |
+ |
+ private: |
+ HANDLE GetProcessHandle(const ProcessData& process); |
+ void OpenProcess(const ProcessData& process); |
+ void CloseProcess(const ProcessData& process); |
+ bool GetFinishedProcessCpuTime(const ProcessData& process, |
+ ULONGLONG* cpu_usage); |
+ |
+ static void SortThreads(ThreadsVector* processes); |
+ static void SortProcesses(ProcessesVector* processes); |
+ |
+ static ULONG CountContextSwitches(const ProcessData& process_data); |
+ static ULONG DiffContextSwitches(const ProcessData& prev_process_data, |
+ const ProcessData& process_data); |
+ |
+ DWORD NumberOfprocessors() const { return number_of_processors; } |
+ |
+ NTQUERYSYSTEMINFORMATION nt_query_system_information_ptr; |
+ DWORD number_of_processors; |
+ wchar_t target_process_name[256]; |
+ LARGE_INTEGER perf_frequency; |
+ LARGE_INTEGER previous_perf_counter_value; |
+ ULONG previous_buffer_size = 0; |
+ |
+ // The first argument of HANDLE type is actually used for process IDs. This is |
+ // consistent with data structures above. |
+ std::map<HANDLE, HANDLE> process_id_to_hanle_map; |
+}; |
+ |
+IdleWakeups::IdleWakeups(const wchar_t* process_name) { |
+ lstrcpyn(target_process_name, process_name, |
+ sizeof(target_process_name) / sizeof(wchar_t)); |
+ |
+ HMODULE ntdll = GetModuleHandle(_T("ntdll.dll")); |
+ if (!ntdll) |
+ oops("Couldn't load ntdll.dll"); |
+ nt_query_system_information_ptr = (NTQUERYSYSTEMINFORMATION)GetProcAddress( |
+ ntdll, "NtQuerySystemInformation"); |
+ if (!nt_query_system_information_ptr) |
+ oops("Couldn't find NtQuerySystemInformation"); |
+ |
+ SYSTEM_INFO system_info; |
+ GetNativeSystemInfo(&system_info); |
+ number_of_processors = system_info.dwNumberOfProcessors; |
+ printf("Number of processors: %d\n", number_of_processors); |
+ |
+ QueryPerformanceFrequency(&perf_frequency); |
+} |
+ |
+IdleWakeups::~IdleWakeups() { |
+ CloseProcesses(); |
+} |
+ |
+void IdleWakeups::OpenProcesses(const ProcessesVector& processes) { |
+ for (auto& process : processes) { |
+ OpenProcess(process); |
+ } |
+} |
+ |
+void IdleWakeups::CloseProcesses() { |
+ for (auto& pair : process_id_to_hanle_map) { |
+ CloseHandle(pair.second); |
+ } |
+ process_id_to_hanle_map.clear(); |
+} |
+ |
+HANDLE IdleWakeups::GetProcessHandle(const ProcessData& process_data) { |
+ return process_id_to_hanle_map[process_data.ProcessId]; |
+} |
+ |
+void IdleWakeups::OpenProcess(const ProcessData& process_data) { |
+ process_id_to_hanle_map[process_data.ProcessId] = |
+ ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, |
+ (DWORD)(ULONGLONG)process_data.ProcessId); |
+} |
+ |
+void IdleWakeups::CloseProcess(const ProcessData& process) { |
+ HANDLE handle = GetProcessHandle(process); |
+ CloseHandle(handle); |
+ process_id_to_hanle_map.erase(process.ProcessId); |
+} |
+ |
+double IdleWakeups::TakeSnapshot(ProcessesVector* processes) { |
+ ULONG data_size; |
+ |
+ LARGE_INTEGER perf_counter_value; |
+ std::vector<BYTE> process_data(previous_buffer_size); |
+ |
+ for (;;) { |
+ data_size = 0; |
+ NTSTATUS result = nt_query_system_information_ptr( |
+ SystemProcessInformation, |
+ previous_buffer_size > 0 ? &process_data[0] : NULL, |
+ previous_buffer_size, &data_size); |
+ if (result == STATUS_INFO_LENGTH_MISMATCH || |
+ result == STATUS_BUFFER_TOO_SMALL) { |
+ // Reallocate the buffer. |
+ previous_buffer_size = data_size; |
+ process_data.resize(data_size); |
+ continue; |
+ } |
+ |
+ if (result != STATUS_SUCCESS) |
+ oops("NtQuerySystemInformation failed"); |
+ |
+ QueryPerformanceCounter(&perf_counter_value); |
+ break; |
+ } |
+ |
+ for (size_t offset = 0; offset < data_size;) { |
+ auto pi = reinterpret_cast<const SYSTEM_PROCESS_INFORMATION*>( |
+ process_data.data() + offset); |
+ |
+ // Validate that the offset is valid and all needed data is within |
+ // the buffer boundary. |
+ if (offset + sizeof(SYSTEM_PROCESS_INFORMATION) > data_size) |
+ break; |
+ if (offset + sizeof(SYSTEM_PROCESS_INFORMATION) + |
+ (pi->NumberOfThreads - 1) * sizeof(SYSTEM_THREAD_INFORMATION) > |
+ data_size) |
+ break; |
+ |
+ if (pi->ImageName.Buffer && |
+ wcsncmp(target_process_name_filter(), pi->ImageName.Buffer, |
+ lstrlen(target_process_name_filter())) == 0) { |
+ // There is no point in recording per-process idle wakeups with any more |
+ // precision than they are recorded per-thread because the per-thread |
+ // numbers may have wrapped. Therefore idleWakeups can only really be used |
+ // to calculate diffs, or for processes that have not run long enough to |
+ // accumulate four billion context switches. |
+ ProcessData process_data; |
+ process_data.ProcessId = pi->ProcessId; |
+ process_data.CpuTime = pi->KernelTime + pi->UserTime; |
+ process_data.WorkingSetPrivateSize = pi->WorkingSetPrivateSize; |
+ for (ULONG thread_index = 0; thread_index < pi->NumberOfThreads; |
+ ++thread_index) { |
+ const SYSTEM_THREAD_INFORMATION* ti = &pi->Threads[thread_index]; |
+ if (ti->ClientId.UniqueProcess != pi->ProcessId) |
+ continue; |
+ |
+ ThreadData thread_data; |
+ thread_data.ThreadId = ti->ClientId.UniqueThread; |
+ thread_data.ContextSwitches = ti->ContextSwitchCount; |
+ process_data.Threads.push_back(thread_data); |
+ } |
+ |
+ SortThreads(&process_data.Threads); |
+ |
+ processes->push_back(process_data); |
+ } |
+ |
+ // Check for end of the list. |
+ if (!pi->NextEntryOffset) |
+ break; |
+ |
+ // Jump to the next entry. |
+ offset += pi->NextEntryOffset; |
+ } |
+ |
+ SortProcesses(processes); |
+ |
+ double time_delta = 0.0; |
+ |
+ if (previous_perf_counter_value.QuadPart != 0) { |
+ time_delta = static_cast<double>(perf_counter_value.QuadPart - |
+ previous_perf_counter_value.QuadPart) / |
+ perf_frequency.QuadPart; |
+ } |
+ previous_perf_counter_value = perf_counter_value; |
+ |
+ return time_delta; |
+} |
+ |
+void IdleWakeups::SortThreads(ThreadsVector* threads) { |
+ std::sort(threads->begin(), threads->end(), |
+ [](const ThreadData& lhs, const ThreadData& rhs) { |
+ return lhs.ThreadId < rhs.ThreadId; |
+ }); |
+} |
+ |
+void IdleWakeups::SortProcesses(ProcessesVector* processes) { |
+ std::sort(processes->begin(), processes->end(), |
+ [](const ProcessData& lhs, const ProcessData& rhs) { |
+ return lhs.ProcessId < rhs.ProcessId; |
+ }); |
+} |
+ |
+ULONG IdleWakeups::CountContextSwitches(const ProcessData& process_data) { |
+ ULONG context_switches = 0; |
+ |
+ for (const auto& thread_data : process_data.Threads) { |
+ context_switches += thread_data.ContextSwitches; |
+ } |
+ |
+ 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.ThreadId == thread_data.ThreadId) { |
+ prev_context_switches = prev_thread_data.ContextSwitches; |
+ ++prev_index; |
+ break; |
+ } |
+ |
+ if (prev_thread_data.ThreadId > thread_data.ThreadId) |
+ break; |
+ } |
+ |
+ context_switches += thread_data.ContextSwitches - prev_context_switches; |
+ } |
+ |
+ return context_switches; |
+} |
+ |
+bool IdleWakeups::GetFinishedProcessCpuTime(const ProcessData& process, |
+ ULONGLONG* cpu_time) { |
+ HANDLE process_handle = GetProcessHandle(process); |
+ |
+ 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(double time_delta, |
+ const ProcessesVector& prev_processes, |
+ const ProcessesVector& processes) { |
+ ULONG idle_wakeups_delta = 0; |
+ ULONGLONG cpu_usage_delta = 0; |
+ ULONGLONG total_working_set = 0; |
+ |
+ size_t prev_index = 0; |
+ |
+ for (const auto& process_data : processes) { |
+ const ProcessData* prev_process_data_to_diff = nullptr; |
+ ULONGLONG prev_process_cpu_time = 0; |
+ |
+ for (; prev_index < prev_processes.size(); ++prev_index) { |
+ const auto& prev_process_data = prev_processes[prev_index]; |
+ |
+ if (prev_process_data.ProcessId == process_data.ProcessId) { |
+ prev_process_data_to_diff = &prev_process_data; |
+ prev_process_cpu_time = prev_process_data.CpuTime; |
+ ++prev_index; |
+ break; |
+ } |
+ |
+ if (prev_process_data.ProcessId > process_data.ProcessId) |
+ break; |
+ |
+ // Prev process disappeared. |
+ ULONGLONG last_known_cpu_time; |
+ if (GetFinishedProcessCpuTime(prev_process_data, &last_known_cpu_time)) { |
+ cpu_usage_delta += last_known_cpu_time - prev_process_data.CpuTime; |
+ } |
+ CloseProcess(prev_process_data); |
+ } |
+ |
+ 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_data); |
+ idle_wakeups_delta += CountContextSwitches(process_data); |
+ } |
+ |
+ cpu_usage_delta += process_data.CpuTime - prev_process_cpu_time; |
+ total_working_set += process_data.WorkingSetPrivateSize / 1024; |
+ } |
+ |
+ Result result; |
+ result.IdleWakeupsPerSec = |
+ 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.CpuUsage = (double)cpu_usage_delta * 100 / (time_delta * 10000000 * |
+ // NumberOfprocessors()); |
+ result.CpuUsage = (double)cpu_usage_delta * 100 / (time_delta * 10000000); |
+ result.WorkingSet = 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; |
+ |
+ IdleWakeups the_app(argc > 1 ? argv[1] : L"chrome.exe"); |
+ |
+ ProcessesVector prev_chrome_processes; |
+ ProcessesVector chrome_processes; |
+ |
+ // Take the initial snapshot. |
+ the_app.TakeSnapshot(&chrome_processes); |
+ the_app.OpenProcesses(chrome_processes); |
+ |
+ 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", |
+ the_app.target_process_name_filter()); |
+ |
+ PrintHeader(); |
+ |
+ for (;;) { |
+ prev_chrome_processes.swap(chrome_processes); |
+ chrome_processes.clear(); |
+ |
+ if (WaitForSingleObject(ctrl_c_pressed, sleep_time_sec * 1000) == |
+ WAIT_OBJECT_0) |
+ break; |
+ |
+ double time_delta = the_app.TakeSnapshot(&chrome_processes); |
+ |
+ Result result = the_app.DiffSnapshots(time_delta, prev_chrome_processes, |
+ chrome_processes); |
+ power_sampler.SampleCPUPowerState(); |
+ result.Power = power_sampler.get_power(L"Processor"); |
+ |
+ printf("%9u processes" RESULT_FORMAT_STRING, (DWORD)chrome_processes.size(), |
+ result.IdleWakeupsPerSec, result.CpuUsage, '%', |
+ result.WorkingSet / 1024.0, result.Power); |
+ |
+ cumulative_idle_wakeups_per_sec += result.IdleWakeupsPerSec; |
+ cumulative_cpu_usage += result.CpuUsage; |
+ cumulative_working_set += result.WorkingSet; |
+ 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.IdleWakeupsPerSec = |
+ GetMedian<ULONG>(&results, GetIdleWakeupsPerSec); |
+ median_result.CpuUsage = GetMedian<double>(&results, GetCpuUsage); |
+ median_result.WorkingSet = GetMedian<ULONGLONG>(&results, GetWorkingSet); |
+ median_result.Power = GetMedian<double>(&results, GetPower); |
+ |
+ printf(" Median" RESULT_FORMAT_STRING, |
+ median_result.IdleWakeupsPerSec, median_result.CpuUsage, '%', |
+ median_result.WorkingSet / 1024.0, median_result.Power); |
+ |
+ return 0; |
+} |