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