Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(293)

Side by Side Diff: tools/win/IdleWakeups/idle_wakeups.cpp

Issue 2356753004: IdleWakeups tool (Closed)
Patch Set: Missing vcproj changes. Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "stdafx.h"
6
7 #include <map>
8 #include <vector>
9 #include <algorithm>
10
11 #include "power_sampler.h"
12
13 // From ntdef.h
14 typedef struct _UNICODE_STRING {
15 USHORT Length;
16 USHORT MaximumLength;
17 PWCH Buffer;
18 } UNICODE_STRING;
19
20 // From <wdm.h>
21 typedef LONG KPRIORITY;
22 typedef LONG KWAIT_REASON; // Full definition is in wdm.h
23
24 // From ntddk.h
25 typedef struct _VM_COUNTERS {
26 SIZE_T PeakVirtualSize;
27 SIZE_T VirtualSize;
28 ULONG PageFaultCount;
29 // Padding here in 64-bit
30 SIZE_T PeakWorkingSetSize;
31 SIZE_T WorkingSetSize;
32 SIZE_T QuotaPeakPagedPoolUsage;
33 SIZE_T QuotaPagedPoolUsage;
34 SIZE_T QuotaPeakNonPagedPoolUsage;
35 SIZE_T QuotaNonPagedPoolUsage;
36 SIZE_T PagefileUsage;
37 SIZE_T PeakPagefileUsage;
38 } VM_COUNTERS;
39
40 // Two possibilities available from here:
41 // http://stackoverflow.com/questions/28858849/where-is-system-information-class -defined
42
43 typedef enum _SYSTEM_INFORMATION_CLASS {
44 SystemBasicInformation = 0,
45 SystemPerformanceInformation = 2,
46 SystemTimeOfDayInformation = 3,
47 SystemProcessInformation = 5, // This is the number that we need
48 SystemProcessorPerformanceInformation = 8,
49 SystemInterruptInformation = 23,
50 SystemExceptionInformation = 33,
51 SystemRegistryQuotaInformation = 37,
52 SystemLookasideInformation = 45
53 } SYSTEM_INFORMATION_CLASS;
54
55 // https://msdn.microsoft.com/en-us/library/gg750647.aspx?f=255&MSPPError=-21472 17396
56 typedef struct {
57 HANDLE UniqueProcess; // Actually process ID
58 HANDLE UniqueThread; // Actually thread ID
59 } CLIENT_ID;
60
61 // From http://alax.info/blog/1182, with corrections and modifications
62 // Originally from
63 // http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%2 0Functions%2FSystem%20Information%2FStructures%2FSYSTEM_THREAD.html
64 struct SYSTEM_THREAD_INFORMATION {
65 ULONGLONG KernelTime;
66 ULONGLONG UserTime;
67 ULONGLONG CreateTime;
68 ULONG WaitTime;
69 // Padding here in 64-bit
70 PVOID StartAddress;
71 CLIENT_ID ClientId;
72 KPRIORITY Priority;
73 LONG BasePriority;
74 ULONG ContextSwitchCount;
75 ULONG State;
76 KWAIT_REASON WaitReason;
77 };
78 #if _M_X64
79 static_assert(sizeof(SYSTEM_THREAD_INFORMATION) == 80,
80 "Structure size mismatch");
81 #else
82 static_assert(sizeof(SYSTEM_THREAD_INFORMATION) == 64,
83 "Structure size mismatch");
84 #endif
85
86 // From http://alax.info/blog/1182, with corrections and modifications
87 // Originally from
88 // http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%2 0Functions%2FSystem%20Information%2FStructures%2FSYSTEM_THREAD.html
89 struct SYSTEM_PROCESS_INFORMATION {
90 ULONG NextEntryOffset;
91 ULONG NumberOfThreads;
92 // 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
93 ULONGLONG WorkingSetPrivateSize;
94 ULONG HardFaultCount;
95 ULONG Reserved1;
96 ULONGLONG CycleTime;
97 ULONGLONG CreateTime;
98 ULONGLONG UserTime;
99 ULONGLONG KernelTime;
100 UNICODE_STRING ImageName;
101 KPRIORITY BasePriority;
102 HANDLE ProcessId;
103 HANDLE ParentProcessId;
104 ULONG HandleCount;
105 ULONG Reserved2[2];
106 // Padding here in 64-bit
107 VM_COUNTERS VirtualMemoryCounters;
108 size_t Reserved3;
109 IO_COUNTERS IoCounters;
110 SYSTEM_THREAD_INFORMATION Threads[1];
111 };
112 #if _M_X64
113 static_assert(sizeof(SYSTEM_PROCESS_INFORMATION) == 336,
114 "Structure size mismatch");
115 #else
116 static_assert(sizeof(SYSTEM_PROCESS_INFORMATION) == 248,
117 "Structure size mismatch");
118 #endif
119
120 // ntstatus.h conflicts with windows.h so define this locally.
121 #define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
122 #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
123 #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
124
125 typedef NTSTATUS(WINAPI* NTQUERYSYSTEMINFORMATION)(
126 SYSTEM_INFORMATION_CLASS SystemInformationClass,
127 PVOID SystemInformation,
128 ULONG SystemInformationLength,
129 PULONG ReturnLength);
130
131 __declspec(noreturn) void oops(const char* pMessage) {
132 printf("%s\n", pMessage);
133 exit(0);
134 }
135
136 // Contains per thread data stored in each data snapshot.
137 struct ThreadData {
138 HANDLE ThreadId;
139 ULONG ContextSwitches;
140 };
141
142 typedef std::vector<ThreadData> ThreadsVector;
143
144 // Contains per process data stored in each data snapshot.
145 struct ProcessData {
146 HANDLE ProcessId;
147 ULONGLONG CpuTime;
148 ULONGLONG WorkingSetPrivateSize;
149 ThreadsVector Threads;
150 };
151
152 // A vector of ProcessData represents one snapshot of perf data
153 // collected by the tool each collection interval.
154 typedef std::vector<ProcessData> ProcessesVector;
155
156 // Result data structure contains a final set of values calculated based on
157 // comparison of two snapshots. These are the values that the tool prints
158 // in the output.
159 struct Result {
160 ULONG IdleWakeupsPerSec;
161 double CpuUsage;
162 ULONGLONG WorkingSet;
163 double Power;
164 };
165
166 typedef std::vector<Result> ResultVector;
167
168 // The following 4 functions are used for sorting of ResultVector.
169 ULONG GetIdleWakeupsPerSec(const Result& r) {
170 return r.IdleWakeupsPerSec;
171 }
172 double GetCpuUsage(const Result& r) {
173 return r.CpuUsage;
174 }
175 ULONGLONG GetWorkingSet(const Result& r) {
176 return r.WorkingSet;
177 }
178 double GetPower(const Result& r) {
179 return r.Power;
180 }
181
182 template <typename T>
183 T GetMedian(ResultVector* results, T (*getter)(const Result&)) {
184 std::sort(results->begin(), results->end(),
185 [&](const Result& lhs, const Result& rhs) {
186 return getter(lhs) < getter(rhs);
187 });
188
189 size_t median_index = results->size() / 2;
190 if (results->size() % 2 != 0) {
191 return getter((*results)[median_index]);
192 } else {
193 return (getter((*results)[median_index - 1]) +
194 getter((*results)[median_index])) /
195 2;
196 }
197 }
198
199 // This class holds the app state and constains a number of utilities for
200 // collecting and diffing snapshots of data, handling processes, etc.
201 class IdleWakeups {
202 public:
203 IdleWakeups(const wchar_t* process_name);
204 ~IdleWakeups();
205
206 double TakeSnapshot(ProcessesVector* processes);
207 Result DiffSnapshots(double time_delta,
208 const ProcessesVector& prev_processes,
209 const ProcessesVector& processes);
210
211 void OpenProcesses(const ProcessesVector& processes);
212 void CloseProcesses();
213
214 const wchar_t* target_process_name_filter() const {
215 return target_process_name;
216 }
217
218 private:
219 HANDLE GetProcessHandle(const ProcessData& process);
220 void OpenProcess(const ProcessData& process);
221 void CloseProcess(const ProcessData& process);
222 bool GetFinishedProcessCpuTime(const ProcessData& process,
223 ULONGLONG* cpu_usage);
224
225 static void SortThreads(ThreadsVector* processes);
226 static void SortProcesses(ProcessesVector* processes);
227
228 static ULONG CountContextSwitches(const ProcessData& process_data);
229 static ULONG DiffContextSwitches(const ProcessData& prev_process_data,
230 const ProcessData& process_data);
231
232 DWORD NumberOfprocessors() const { return number_of_processors; }
233
234 NTQUERYSYSTEMINFORMATION nt_query_system_information_ptr;
235 DWORD number_of_processors;
236 wchar_t target_process_name[256];
237 LARGE_INTEGER perf_frequency;
238 LARGE_INTEGER previous_perf_counter_value;
239 ULONG previous_buffer_size = 0;
240
241 // The first argument of HANDLE type is actually used for process IDs. This is
242 // consistent with data structures above.
243 std::map<HANDLE, HANDLE> process_id_to_hanle_map;
244 };
245
246 IdleWakeups::IdleWakeups(const wchar_t* process_name) {
247 lstrcpyn(target_process_name, process_name,
248 sizeof(target_process_name) / sizeof(wchar_t));
249
250 HMODULE ntdll = GetModuleHandle(_T("ntdll.dll"));
251 if (!ntdll)
252 oops("Couldn't load ntdll.dll");
253 nt_query_system_information_ptr = (NTQUERYSYSTEMINFORMATION)GetProcAddress(
254 ntdll, "NtQuerySystemInformation");
255 if (!nt_query_system_information_ptr)
256 oops("Couldn't find NtQuerySystemInformation");
257
258 SYSTEM_INFO system_info;
259 GetNativeSystemInfo(&system_info);
260 number_of_processors = system_info.dwNumberOfProcessors;
261 printf("Number of processors: %d\n", number_of_processors);
262
263 QueryPerformanceFrequency(&perf_frequency);
264 }
265
266 IdleWakeups::~IdleWakeups() {
267 CloseProcesses();
268 }
269
270 void IdleWakeups::OpenProcesses(const ProcessesVector& processes) {
271 for (auto& process : processes) {
272 OpenProcess(process);
273 }
274 }
275
276 void IdleWakeups::CloseProcesses() {
277 for (auto& pair : process_id_to_hanle_map) {
278 CloseHandle(pair.second);
279 }
280 process_id_to_hanle_map.clear();
281 }
282
283 HANDLE IdleWakeups::GetProcessHandle(const ProcessData& process_data) {
284 return process_id_to_hanle_map[process_data.ProcessId];
285 }
286
287 void IdleWakeups::OpenProcess(const ProcessData& process_data) {
288 process_id_to_hanle_map[process_data.ProcessId] =
289 ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
290 (DWORD)(ULONGLONG)process_data.ProcessId);
291 }
292
293 void IdleWakeups::CloseProcess(const ProcessData& process) {
294 HANDLE handle = GetProcessHandle(process);
295 CloseHandle(handle);
296 process_id_to_hanle_map.erase(process.ProcessId);
297 }
298
299 double IdleWakeups::TakeSnapshot(ProcessesVector* processes) {
300 ULONG data_size;
301
302 LARGE_INTEGER perf_counter_value;
303 std::vector<BYTE> process_data(previous_buffer_size);
304
305 for (;;) {
306 data_size = 0;
307 NTSTATUS result = nt_query_system_information_ptr(
308 SystemProcessInformation,
309 previous_buffer_size > 0 ? &process_data[0] : NULL,
310 previous_buffer_size, &data_size);
311 if (result == STATUS_INFO_LENGTH_MISMATCH ||
312 result == STATUS_BUFFER_TOO_SMALL) {
313 // Reallocate the buffer.
314 previous_buffer_size = data_size;
315 process_data.resize(data_size);
316 continue;
317 }
318
319 if (result != STATUS_SUCCESS)
320 oops("NtQuerySystemInformation failed");
321
322 QueryPerformanceCounter(&perf_counter_value);
323 break;
324 }
325
326 for (size_t offset = 0; offset < data_size;) {
327 auto pi = reinterpret_cast<const SYSTEM_PROCESS_INFORMATION*>(
328 process_data.data() + offset);
329
330 // Validate that the offset is valid and all needed data is within
331 // the buffer boundary.
332 if (offset + sizeof(SYSTEM_PROCESS_INFORMATION) > data_size)
333 break;
334 if (offset + sizeof(SYSTEM_PROCESS_INFORMATION) +
335 (pi->NumberOfThreads - 1) * sizeof(SYSTEM_THREAD_INFORMATION) >
336 data_size)
337 break;
338
339 if (pi->ImageName.Buffer &&
340 wcsncmp(target_process_name_filter(), pi->ImageName.Buffer,
341 lstrlen(target_process_name_filter())) == 0) {
342 // There is no point in recording per-process idle wakeups with any more
343 // precision than they are recorded per-thread because the per-thread
344 // numbers may have wrapped. Therefore idleWakeups can only really be used
345 // to calculate diffs, or for processes that have not run long enough to
346 // accumulate four billion context switches.
347 ProcessData process_data;
348 process_data.ProcessId = pi->ProcessId;
349 process_data.CpuTime = pi->KernelTime + pi->UserTime;
350 process_data.WorkingSetPrivateSize = pi->WorkingSetPrivateSize;
351 for (ULONG thread_index = 0; thread_index < pi->NumberOfThreads;
352 ++thread_index) {
353 const SYSTEM_THREAD_INFORMATION* ti = &pi->Threads[thread_index];
354 if (ti->ClientId.UniqueProcess != pi->ProcessId)
355 continue;
356
357 ThreadData thread_data;
358 thread_data.ThreadId = ti->ClientId.UniqueThread;
359 thread_data.ContextSwitches = ti->ContextSwitchCount;
360 process_data.Threads.push_back(thread_data);
361 }
362
363 SortThreads(&process_data.Threads);
364
365 processes->push_back(process_data);
366 }
367
368 // Check for end of the list.
369 if (!pi->NextEntryOffset)
370 break;
371
372 // Jump to the next entry.
373 offset += pi->NextEntryOffset;
374 }
375
376 SortProcesses(processes);
377
378 double time_delta = 0.0;
379
380 if (previous_perf_counter_value.QuadPart != 0) {
381 time_delta = static_cast<double>(perf_counter_value.QuadPart -
382 previous_perf_counter_value.QuadPart) /
383 perf_frequency.QuadPart;
384 }
385 previous_perf_counter_value = perf_counter_value;
386
387 return time_delta;
388 }
389
390 void IdleWakeups::SortThreads(ThreadsVector* threads) {
391 std::sort(threads->begin(), threads->end(),
392 [](const ThreadData& lhs, const ThreadData& rhs) {
393 return lhs.ThreadId < rhs.ThreadId;
394 });
395 }
396
397 void IdleWakeups::SortProcesses(ProcessesVector* processes) {
398 std::sort(processes->begin(), processes->end(),
399 [](const ProcessData& lhs, const ProcessData& rhs) {
400 return lhs.ProcessId < rhs.ProcessId;
401 });
402 }
403
404 ULONG IdleWakeups::CountContextSwitches(const ProcessData& process_data) {
405 ULONG context_switches = 0;
406
407 for (const auto& thread_data : process_data.Threads) {
408 context_switches += thread_data.ContextSwitches;
409 }
410
411 return context_switches;
412 }
413
414 ULONG IdleWakeups::DiffContextSwitches(const ProcessData& prev_process_data,
415 const ProcessData& process_data) {
416 ULONG context_switches = 0;
417 size_t prev_index = 0;
418
419 for (const auto& thread_data : process_data.Threads) {
420 ULONG prev_context_switches = 0;
421
422 for (; prev_index < prev_process_data.Threads.size(); ++prev_index) {
423 const auto& prev_thread_data = prev_process_data.Threads[prev_index];
424 if (prev_thread_data.ThreadId == thread_data.ThreadId) {
425 prev_context_switches = prev_thread_data.ContextSwitches;
426 ++prev_index;
427 break;
428 }
429
430 if (prev_thread_data.ThreadId > thread_data.ThreadId)
431 break;
432 }
433
434 context_switches += thread_data.ContextSwitches - prev_context_switches;
435 }
436
437 return context_switches;
438 }
439
440 bool IdleWakeups::GetFinishedProcessCpuTime(const ProcessData& process,
441 ULONGLONG* cpu_time) {
442 HANDLE process_handle = GetProcessHandle(process);
443
444 FILETIME creation_time, exit_time, kernel_time, user_time;
445 if (GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time,
446 &user_time)) {
447 ULARGE_INTEGER ul_kernel_time, ul_user_time;
448 ul_kernel_time.LowPart = kernel_time.dwLowDateTime;
449 ul_kernel_time.HighPart = kernel_time.dwHighDateTime;
450 ul_user_time.LowPart = user_time.dwLowDateTime;
451 ul_user_time.HighPart = user_time.dwHighDateTime;
452 *cpu_time = ul_kernel_time.QuadPart + ul_user_time.QuadPart;
453 return true;
454 }
455
456 *cpu_time = 0;
457 return false;
458 }
459
460 Result IdleWakeups::DiffSnapshots(double time_delta,
461 const ProcessesVector& prev_processes,
462 const ProcessesVector& processes) {
463 ULONG idle_wakeups_delta = 0;
464 ULONGLONG cpu_usage_delta = 0;
465 ULONGLONG total_working_set = 0;
466
467 size_t prev_index = 0;
468
469 for (const auto& process_data : processes) {
470 const ProcessData* prev_process_data_to_diff = nullptr;
471 ULONGLONG prev_process_cpu_time = 0;
472
473 for (; prev_index < prev_processes.size(); ++prev_index) {
474 const auto& prev_process_data = prev_processes[prev_index];
475
476 if (prev_process_data.ProcessId == process_data.ProcessId) {
477 prev_process_data_to_diff = &prev_process_data;
478 prev_process_cpu_time = prev_process_data.CpuTime;
479 ++prev_index;
480 break;
481 }
482
483 if (prev_process_data.ProcessId > process_data.ProcessId)
484 break;
485
486 // Prev process disappeared.
487 ULONGLONG last_known_cpu_time;
488 if (GetFinishedProcessCpuTime(prev_process_data, &last_known_cpu_time)) {
489 cpu_usage_delta += last_known_cpu_time - prev_process_data.CpuTime;
490 }
491 CloseProcess(prev_process_data);
492 }
493
494 if (prev_process_data_to_diff) {
495 idle_wakeups_delta +=
496 DiffContextSwitches(*prev_process_data_to_diff, process_data);
497 } else {
498 // New process that we haven't seen before.
499 OpenProcess(process_data);
500 idle_wakeups_delta += CountContextSwitches(process_data);
501 }
502
503 cpu_usage_delta += process_data.CpuTime - prev_process_cpu_time;
504 total_working_set += process_data.WorkingSetPrivateSize / 1024;
505 }
506
507 Result result;
508 result.IdleWakeupsPerSec =
509 static_cast<ULONG>(idle_wakeups_delta / time_delta);
510 // brucedawson: Don't divide by number of processors so that all numbers are
511 // percentage of a core
512 // result.CpuUsage = (double)cpu_usage_delta * 100 / (time_delta * 10000000 *
513 // NumberOfprocessors());
514 result.CpuUsage = (double)cpu_usage_delta * 100 / (time_delta * 10000000);
515 result.WorkingSet = total_working_set;
516
517 return result;
518 }
519
520 HANDLE ctrl_c_pressed = NULL;
521
522 BOOL WINAPI HandlerFunction(DWORD ctrl_type) {
523 if (ctrl_type == CTRL_C_EVENT) {
524 printf("Ctrl+C pressed...\n");
525 SetEvent(ctrl_c_pressed);
526 return TRUE;
527 }
528
529 return FALSE;
530 }
531
532 const DWORD sleep_time_sec = 2;
533
534 void PrintHeader() {
535 printf(
536 "------------------------------------------------------------------------"
537 "----------\n");
538 printf(
539 " Context switches/sec CPU usage Working set "
540 " Power\n");
541 printf(
542 "------------------------------------------------------------------------"
543 "----------\n");
544 }
545
546 #define RESULT_FORMAT_STRING " %20lu %8.2f%c %6.2f MiB %4.2f W\n"
547
548 int wmain(int argc, wchar_t* argv[]) {
549 ctrl_c_pressed = CreateEvent(NULL, FALSE, FALSE, NULL);
550 SetConsoleCtrlHandler(HandlerFunction, TRUE);
551
552 PowerSampler power_sampler;
553
554 IdleWakeups the_app(argc > 1 ? argv[1] : L"chrome.exe");
555
556 ProcessesVector prev_chrome_processes;
557 ProcessesVector chrome_processes;
558
559 // Take the initial snapshot.
560 the_app.TakeSnapshot(&chrome_processes);
561 the_app.OpenProcesses(chrome_processes);
562
563 ULONG cumulative_idle_wakeups_per_sec = 0;
564 double cumulative_cpu_usage = 0.0;
565 ULONGLONG cumulative_working_set = 0;
566 double cumulative_energy = 0.0;
567
568 ResultVector results;
569
570 printf("Capturing perf data for all processes matching %ls\n",
571 the_app.target_process_name_filter());
572
573 PrintHeader();
574
575 for (;;) {
576 prev_chrome_processes.swap(chrome_processes);
577 chrome_processes.clear();
578
579 if (WaitForSingleObject(ctrl_c_pressed, sleep_time_sec * 1000) ==
580 WAIT_OBJECT_0)
581 break;
582
583 double time_delta = the_app.TakeSnapshot(&chrome_processes);
584
585 Result result = the_app.DiffSnapshots(time_delta, prev_chrome_processes,
586 chrome_processes);
587 power_sampler.SampleCPUPowerState();
588 result.Power = power_sampler.get_power(L"Processor");
589
590 printf("%9u processes" RESULT_FORMAT_STRING, (DWORD)chrome_processes.size(),
591 result.IdleWakeupsPerSec, result.CpuUsage, '%',
592 result.WorkingSet / 1024.0, result.Power);
593
594 cumulative_idle_wakeups_per_sec += result.IdleWakeupsPerSec;
595 cumulative_cpu_usage += result.CpuUsage;
596 cumulative_working_set += result.WorkingSet;
597 cumulative_energy += result.Power;
598
599 results.push_back(result);
600 }
601
602 CloseHandle(ctrl_c_pressed);
603
604 ULONG sample_count = (ULONG)results.size();
605 if (sample_count == 0)
606 return 0;
607
608 PrintHeader();
609
610 printf(" Average" RESULT_FORMAT_STRING,
611 cumulative_idle_wakeups_per_sec / sample_count,
612 cumulative_cpu_usage / sample_count, '%',
613 (cumulative_working_set / 1024.0) / sample_count,
614 cumulative_energy / sample_count);
615
616 Result median_result;
617
618 median_result.IdleWakeupsPerSec =
619 GetMedian<ULONG>(&results, GetIdleWakeupsPerSec);
620 median_result.CpuUsage = GetMedian<double>(&results, GetCpuUsage);
621 median_result.WorkingSet = GetMedian<ULONGLONG>(&results, GetWorkingSet);
622 median_result.Power = GetMedian<double>(&results, GetPower);
623
624 printf(" Median" RESULT_FORMAT_STRING,
625 median_result.IdleWakeupsPerSec, median_result.CpuUsage, '%',
626 median_result.WorkingSet / 1024.0, median_result.Power);
627
628 return 0;
629 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698