Chromium Code Reviews| Index: tools/win/IdleWakeups/system_information_sampler.cpp |
| diff --git a/tools/win/IdleWakeups/system_information_sampler.cpp b/tools/win/IdleWakeups/system_information_sampler.cpp |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..0f4c6fc8f4429ba7243d627e70a105fe2ff5dde5 |
| --- /dev/null |
| +++ b/tools/win/IdleWakeups/system_information_sampler.cpp |
| @@ -0,0 +1,314 @@ |
| +// 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 "system_information_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); |
| +} |
| + |
| +// Simple memory buffer wrapper for passing the data out of |
| +// QuerySystemProcessInformation. |
| +class ByteBuffer { |
| + public: |
| + explicit ByteBuffer(size_t capacity) : size_(0), capacity_(0) { |
| + if (capacity > 0) |
| + grow(capacity); |
| + } |
| + |
| + ~ByteBuffer() {} |
| + |
| + BYTE* data() { return data_.get(); } |
| + |
| + size_t size() { return size_; } |
| + |
| + void set_size(size_t new_size) { size_ = new_size; } |
| + |
| + size_t capacity() { return capacity_; } |
| + |
| + void grow(size_t new_capacity) { |
| + capacity_ = new_capacity; |
| + data_.reset(new BYTE[new_capacity]); |
| + } |
| + |
| + private: |
| + std::unique_ptr<BYTE[]> data_; |
| + size_t size_; |
| + size_t capacity_; |
| + |
| + ByteBuffer& operator=(const ByteBuffer&) = delete; |
| + ByteBuffer(const ByteBuffer&) = delete; |
| +}; |
| + |
| +// Wrapper for NtQuerySystemProcessInformation with buffer reallocation logic. |
| +bool QuerySystemProcessInformation(ByteBuffer* buffer) { |
| + typedef NTSTATUS(WINAPI * NTQUERYSYSTEMINFORMATION)( |
| + SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, |
| + ULONG SystemInformationLength, PULONG ReturnLength); |
| + |
| + HMODULE ntdll = GetModuleHandle(L"ntdll.dll"); |
| + if (!ntdll) { |
| + oops("Couldn't load ntdll.dll"); |
| + } |
| + |
| + NTQUERYSYSTEMINFORMATION nt_query_system_information_ptr = |
| + reinterpret_cast<NTQUERYSYSTEMINFORMATION>( |
| + GetProcAddress(ntdll, "NtQuerySystemInformation")); |
| + if (!nt_query_system_information_ptr) |
| + oops("Couldn't find NtQuerySystemInformation"); |
| + |
| + NTSTATUS result; |
| + |
| + // There is a potential race condition between growing the buffer and new |
| + // processes being created. Try a few times before giving up. |
| + for (int i = 0; i < 10; i++) { |
| + ULONG data_size = 0; |
| + ULONG buffer_size = static_cast<ULONG>(buffer->capacity()); |
| + result = nt_query_system_information_ptr( |
| + SystemProcessInformation, buffer->data(), buffer_size, &data_size); |
| + |
| + if (result == STATUS_SUCCESS) { |
| + buffer->set_size(data_size); |
| + break; |
| + } |
| + |
| + if (result == STATUS_INFO_LENGTH_MISMATCH || |
| + result == STATUS_BUFFER_TOO_SMALL) { |
| + // Insufficient buffer. Grow to the returned |data_size| plus 10% extra |
| + // to avoid frequent reallocations and try again. |
| + buffer->grow(static_cast<ULONG>(data_size * 1.1)); |
| + } else { |
| + // An error other than the two above. |
| + break; |
| + } |
| + } |
| + |
| + return result == STATUS_SUCCESS; |
| +} |
| + |
| +SystemInformationSampler::SystemInformationSampler( |
| + const wchar_t* process_name) { |
| + lstrcpyn(target_process_name_, process_name, |
| + sizeof(target_process_name_) / sizeof(wchar_t)); |
| + |
| + QueryPerformanceFrequency(&perf_frequency_); |
| + QueryPerformanceCounter(&initial_counter_); |
| +} |
| + |
| +SystemInformationSampler::~SystemInformationSampler() {} |
| + |
| +std::unique_ptr<ProcessDataSnapshot> SystemInformationSampler::TakeSnapshot() { |
| + // Preallocate the buffer with the size determined on the previous call to |
| + // QuerySystemProcessInformation. This should be sufficient most of the time. |
| + // QuerySystemProcessInformation will grow the buffer if necessary. |
| + ByteBuffer data_buffer(previous_buffer_size_); |
| + |
| + if (!QuerySystemProcessInformation(&data_buffer)) |
| + return std::unique_ptr<ProcessDataSnapshot>(); |
|
stanisc
2016/09/23 01:57:27
Should fail here rather than return an empty snaps
|
| + |
| + previous_buffer_size_ = data_buffer.capacity(); |
| + |
| + std::unique_ptr<ProcessDataSnapshot> snapshot(new ProcessDataSnapshot); |
| + |
| + LARGE_INTEGER perf_counter_value; |
| + QueryPerformanceCounter(&perf_counter_value); |
| + snapshot->timestamp = static_cast<double>( |
| + (perf_counter_value.QuadPart - initial_counter_.QuadPart) / |
| + perf_frequency_.QuadPart); |
| + |
| + for (size_t offset = 0; offset < data_buffer.size();) { |
| + auto pi = reinterpret_cast<const SYSTEM_PROCESS_INFORMATION*>( |
| + data_buffer.data() + offset); |
| + |
| + // Validate that the offset is valid and all needed data is within |
| + // the buffer boundary. |
| + if (offset + sizeof(SYSTEM_PROCESS_INFORMATION) > data_buffer.size()) |
| + break; |
| + if (offset + sizeof(SYSTEM_PROCESS_INFORMATION) + |
| + (pi->NumberOfThreads - 1) * sizeof(SYSTEM_THREAD_INFORMATION) > |
| + data_buffer.size()) |
| + break; |
| + |
| + if (pi->ImageName.Buffer) { |
| + // Validate that the image name is within the buffer boundary. |
| + // ImageName.Length seems to be in bytes rather than characters. |
| + size_t image_name_offset = |
| + reinterpret_cast<BYTE*>(pi->ImageName.Buffer) - data_buffer.data(); |
| + if (image_name_offset + pi->ImageName.Length > data_buffer.size()) |
| + break; |
| + |
| + // Check if this is a chrome process. Ignore all other processes. |
| + if (wcsncmp(target_process_name_filter(), pi->ImageName.Buffer, |
| + lstrlen(target_process_name_filter())) == 0) { |
| + // Collect enough data to be able to do a diff between two snapshots. |
| + // Some threads might stop or new threads might be created between two |
| + // snapshots. If a thread with a large number of context switches gets |
| + // terminated the total number of context switches for the process might |
| + // go down and the delta would be negative. |
| + // To avoid that we need to compare thread IDs between two snapshots and |
| + // not count context switches for threads that are missing in the most |
| + // recent snapshot. |
| + ProcessData process_data; |
| + |
| + process_data.cpu_time = pi->KernelTime + pi->UserTime; |
| + process_data.working_set = pi->WorkingSetPrivateSize; |
| + |
| + // Iterate over threads and store each thread's ID and number of context |
| + // switches. |
| + 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.thread_id = ti->ClientId.UniqueThread; |
| + thread_data.context_switches = ti->ContextSwitchCount; |
| + process_data.threads.push_back(thread_data); |
| + } |
| + |
| + // Order thread data by thread ID to help diff two snapshots. |
| + std::sort(process_data.threads.begin(), process_data.threads.end(), |
| + [](const ThreadData& l, const ThreadData r) { |
| + return l.thread_id < r.thread_id; |
| + }); |
| + |
| + snapshot->processes.insert( |
| + std::make_pair(pi->ProcessId, std::move(process_data))); |
| + } |
| + } |
| + |
| + // Check for end of the list. |
| + if (!pi->NextEntryOffset) |
| + break; |
| + |
| + // Jump to the next entry. |
| + offset += pi->NextEntryOffset; |
| + } |
| + |
| + return snapshot; |
| +} |