blob: cb9dacc9d1396de0dfa6123e49110f2100dfa8c9 [file] [log] [blame]
// 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;
}