| 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;
|
| +}
|
|
|