OLD | NEW |
---|---|
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/startup_metric_utils/startup_metric_utils.h" | 5 #include "components/startup_metric_utils/startup_metric_utils.h" |
6 | 6 |
7 #include "base/containers/hash_tables.h" | 7 #include "base/containers/hash_tables.h" |
8 #include "base/environment.h" | 8 #include "base/environment.h" |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
10 #include "base/metrics/histogram.h" | 10 #include "base/metrics/histogram.h" |
11 #include "base/metrics/histogram_base.h" | 11 #include "base/metrics/histogram_base.h" |
12 #include "base/metrics/statistics_recorder.h" | 12 #include "base/metrics/statistics_recorder.h" |
13 #include "base/process/process_info.h" | 13 #include "base/process/process_info.h" |
14 #include "base/strings/string_number_conversions.h" | 14 #include "base/strings/string_number_conversions.h" |
15 #include "base/synchronization/lock.h" | 15 #include "base/synchronization/lock.h" |
16 #include "base/sys_info.h" | 16 #include "base/sys_info.h" |
17 #include "base/time/time.h" | 17 #include "base/time/time.h" |
18 | 18 |
19 #if defined(OS_WIN) | |
20 #include <winternl.h> | |
21 #endif | |
22 | |
19 namespace { | 23 namespace { |
20 | 24 |
21 // Mark as volatile to defensively make sure usage is thread-safe. | 25 // Mark as volatile to defensively make sure usage is thread-safe. |
22 // Note that at the time of this writing, access is only on the UI thread. | 26 // Note that at the time of this writing, access is only on the UI thread. |
23 volatile bool g_non_browser_ui_displayed = false; | 27 volatile bool g_non_browser_ui_displayed = false; |
24 | 28 |
25 base::Time* MainEntryPointTimeInternal() { | 29 base::Time* MainEntryPointTimeInternal() { |
26 static base::Time main_start_time = base::Time::Now(); | 30 static base::Time main_start_time = base::Time::Now(); |
27 return &main_start_time; | 31 return &main_start_time; |
28 } | 32 } |
29 | 33 |
30 typedef base::hash_map<std::string,base::TimeDelta> SubsystemStartupTimeHash; | 34 typedef base::hash_map<std::string,base::TimeDelta> SubsystemStartupTimeHash; |
31 | 35 |
32 SubsystemStartupTimeHash* GetSubsystemStartupTimeHash() { | 36 SubsystemStartupTimeHash* GetSubsystemStartupTimeHash() { |
33 static SubsystemStartupTimeHash* slow_startup_time_hash = | 37 static SubsystemStartupTimeHash* slow_startup_time_hash = |
34 new SubsystemStartupTimeHash; | 38 new SubsystemStartupTimeHash; |
35 return slow_startup_time_hash; | 39 return slow_startup_time_hash; |
36 } | 40 } |
37 | 41 |
38 base::Lock* GetSubsystemStartupTimeHashLock() { | 42 base::Lock* GetSubsystemStartupTimeHashLock() { |
39 static base::Lock* slow_startup_time_hash_lock = new base::Lock; | 43 static base::Lock* slow_startup_time_hash_lock = new base::Lock; |
40 return slow_startup_time_hash_lock; | 44 return slow_startup_time_hash_lock; |
41 } | 45 } |
42 | 46 |
47 #if defined(OS_WIN) | |
48 | |
49 // The struct used to return system process information via the NT internal | |
50 // QuerySystemInformation call. This is partially documented at | |
51 // http://goo.gl/Ja9MrH and fully documented at http://goo.gl/QJ70rn | |
52 // This structure is laid out in the same format on both 32-bit and 64-bit | |
53 // systems, but has a different size due to the various pointer-sized fields. | |
54 struct SYSTEM_PROCESS_INFORMATION_EX { | |
55 ULONG NextEntryOffset; | |
56 ULONG NumberOfThreads; | |
57 LARGE_INTEGER WorkingSetPrivateSize; | |
58 ULONG HardFaultCount; | |
59 BYTE Reserved1[36]; | |
60 PVOID Reserved2[3]; | |
61 // This is labeled a handle so that it expands to the correct size for 32-bit | |
62 // and 64-bit operating systems. However, under the hood it's a 32-bit DWORD | |
63 // containing the process ID. | |
64 HANDLE UniqueProcessId; | |
65 PVOID Reserved3; | |
66 ULONG HandleCount; | |
67 BYTE Reserved4[4]; | |
68 PVOID Reserved5[11]; | |
69 SIZE_T PeakPagefileUsage; | |
70 SIZE_T PrivatePageCount; | |
71 LARGE_INTEGER Reserved6[6]; | |
72 // Array of SYSTEM_THREAD_INFORMATION structs follows. | |
73 }; | |
74 | |
75 // The signature of the NtQuerySystemInformation function. | |
76 typedef NTSTATUS (WINAPI *NtQuerySystemInformationPtr)( | |
77 SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG); | |
78 | |
79 // Gets the hard fault count of the current process, returning it via | |
80 // |hard_fault_count|. Returns true on success, false otherwise. Also returns | |
81 // whether or not the system call was even possible for the current OS version | |
82 // via |has_os_support|. | |
83 bool GetHardFaultCountForCurrentProcess(uint32* hard_fault_count, | |
Alexei Svitkine (slow)
2015/04/15 17:36:19
Nit: uint32_t is best practice for new code.
chrisha
2015/04/15 17:56:01
Done.
| |
84 bool* has_os_support) { | |
85 DCHECK(hard_fault_count); | |
86 DCHECK(has_os_support); | |
87 | |
88 *has_os_support = false; | |
89 OSVERSIONINFOEX version_info = {}; | |
90 version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); | |
91 if (!::GetVersionEx(reinterpret_cast<LPOSVERSIONINFOW>(&version_info))) | |
Alexei Svitkine (slow)
2015/04/15 17:36:19
Can you use base/win/windows_version.h instead of
chrisha
2015/04/15 17:56:01
I certainly can, and it's far simpler that way. Th
| |
92 return false; | |
93 | |
94 // The hard fault counter is only available as of Windows 7 (version 6.1). | |
95 if (version_info.dwMajorVersion < 6) | |
96 return false; | |
97 if (version_info.dwMajorVersion == 6 && version_info.dwMinorVersion < 1) | |
98 return false; | |
99 | |
100 // At this point the OS supports the required system call. | |
101 *has_os_support = true; | |
102 | |
103 // Get the function pointer. | |
104 NtQuerySystemInformationPtr query_sys_info = | |
105 reinterpret_cast<NtQuerySystemInformationPtr>( | |
106 ::GetProcAddress(GetModuleHandle(L"ntdll.dll"), | |
107 "NtQuerySystemInformation")); | |
108 if (query_sys_info == nullptr) | |
109 return false; | |
110 | |
111 // The output of this system call depends on the number of threads and | |
112 // processes on the entire system, and this can change between calls. Retry | |
113 // a small handful of times growing the buffer along the way. | |
114 // NOTE: The actual required size depends entirely on the number of processes | |
115 // and threads running on the system. The initial guess suffices for | |
116 // ~100s of processes and ~1000s of threads. | |
117 std::vector<uint8> buffer(32 * 1024); | |
118 for (size_t tries = 0; tries < 3; ++tries) { | |
119 ULONG return_length = 0; | |
120 NTSTATUS status = query_sys_info( | |
121 SystemProcessInformation, | |
122 buffer.data(), | |
123 static_cast<ULONG>(buffer.size()), | |
124 &return_length); | |
125 // Insufficient space in the buffer. | |
126 if (return_length > buffer.size()) { | |
127 buffer.resize(return_length); | |
128 continue; | |
129 } | |
130 if (status == 0 && return_length <= buffer.size()) | |
Alexei Svitkine (slow)
2015/04/15 17:36:19
Should use NT_SUCCESS() instead of status == 0 I t
chrisha
2015/04/15 17:56:01
Done.
| |
131 break; | |
132 return false; | |
133 } | |
134 | |
135 // Look for the struct housing information for the current process. | |
136 DWORD proc_id = ::GetCurrentProcessId(); | |
137 size_t index = 0; | |
138 while (index < buffer.size()) { | |
139 SYSTEM_PROCESS_INFORMATION_EX* proc_info = | |
140 reinterpret_cast<SYSTEM_PROCESS_INFORMATION_EX*>(buffer.data() + index); | |
141 if (reinterpret_cast<DWORD>(proc_info->UniqueProcessId) == proc_id) { | |
142 *hard_fault_count = proc_info->HardFaultCount; | |
143 return true; | |
144 } | |
145 // The list ends with NextEntryOffset is zero. This also prevents busy | |
146 // looping if the data is in fact invalid. | |
147 if (proc_info->NextEntryOffset == 0) | |
148 return false; | |
149 index += proc_info->NextEntryOffset; | |
150 } | |
151 | |
152 return false; | |
153 } | |
154 | |
155 #endif // defined(OS_WIN) | |
156 | |
157 // On Windows, records the number of hard-faults that have occurred in the | |
158 // current chrome.exe process since it was started. This is a nop on other | |
159 // platforms. | |
160 // crbug.com/476923 | |
161 // TODO(chrisha): If this proves useful, use it to split startup stats in two. | |
162 void RecordHardFaultHistogram(bool is_first_run) { | |
163 #if defined(OS_WIN) | |
164 uint32 hard_fault_count = 0; | |
165 bool has_os_support = false; | |
166 bool success = GetHardFaultCountForCurrentProcess( | |
167 &hard_fault_count, &has_os_support); | |
168 | |
169 // Log whether or not the system call was successful, assuming the OS was | |
170 // detected to support it. | |
171 if (has_os_support) { | |
172 UMA_HISTOGRAM_BOOLEAN( | |
173 "Startup.BrowserMessageLoopStartHardFaultCount.Success", | |
174 success); | |
Alexei Svitkine (slow)
2015/04/15 17:36:19
Since there are different cases available, I sugge
chrisha
2015/04/15 17:56:01
The "no OS support" case is completely determinist
Alexei Svitkine (slow)
2015/04/15 18:08:10
I was thinking it would be simpler to reason about
| |
175 } | |
176 | |
177 // Don't log a histogram value if unable to get the hard fault count. | |
178 if (!success) | |
179 return; | |
180 | |
181 // Hard fault counts are expected to be in the thousands range, | |
182 // corresponding to faulting in ~10s of MBs of code ~10s of KBs at a time. | |
183 // (Observed to vary from 1000 to 10000 on various test machines and | |
184 // platforms.) | |
185 if (is_first_run) { | |
186 UMA_HISTOGRAM_CUSTOM_COUNTS( | |
187 "Startup.BrowserMessageLoopStartHardFaultCount.FirstRun", | |
188 hard_fault_count, | |
189 0, 40000, 50); | |
190 } else { | |
191 UMA_HISTOGRAM_CUSTOM_COUNTS( | |
192 "Startup.BrowserMessageLoopStartHardFaultCount", | |
193 hard_fault_count, | |
194 0, 40000, 50); | |
195 } | |
196 #endif // defined(OS_WIN) | |
197 } | |
198 | |
43 // Record time of main entry so it can be read from Telemetry performance | 199 // Record time of main entry so it can be read from Telemetry performance |
44 // tests. | 200 // tests. |
45 // TODO(jeremy): Remove once crbug.com/317481 is fixed. | 201 // TODO(jeremy): Remove once crbug.com/317481 is fixed. |
46 void RecordMainEntryTimeHistogram() { | 202 void RecordMainEntryTimeHistogram() { |
47 const int kLowWordMask = 0xFFFFFFFF; | 203 const int kLowWordMask = 0xFFFFFFFF; |
48 const int kLower31BitsMask = 0x7FFFFFFF; | 204 const int kLower31BitsMask = 0x7FFFFFFF; |
49 base::TimeDelta browser_main_entry_time_absolute = | 205 base::TimeDelta browser_main_entry_time_absolute = |
50 *MainEntryPointTimeInternal() - base::Time::UnixEpoch(); | 206 *MainEntryPointTimeInternal() - base::Time::UnixEpoch(); |
51 | 207 |
52 uint64 browser_main_entry_time_raw_ms = | 208 uint64 browser_main_entry_time_raw_ms = |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
108 } | 264 } |
109 #endif // OS_ANDROID | 265 #endif // OS_ANDROID |
110 | 266 |
111 // Return the time recorded by RecordMainEntryPointTime(). | 267 // Return the time recorded by RecordMainEntryPointTime(). |
112 const base::Time MainEntryStartTime() { | 268 const base::Time MainEntryStartTime() { |
113 DCHECK(g_main_entry_time_was_recorded); | 269 DCHECK(g_main_entry_time_was_recorded); |
114 return *MainEntryPointTimeInternal(); | 270 return *MainEntryPointTimeInternal(); |
115 } | 271 } |
116 | 272 |
117 void OnBrowserStartupComplete(bool is_first_run) { | 273 void OnBrowserStartupComplete(bool is_first_run) { |
274 RecordHardFaultHistogram(is_first_run); | |
118 RecordMainEntryTimeHistogram(); | 275 RecordMainEntryTimeHistogram(); |
119 | 276 |
120 // Bail if uptime < 7 minutes, to filter out cases where Chrome may have been | 277 // Bail if uptime < 7 minutes, to filter out cases where Chrome may have been |
121 // autostarted and the machine is under io pressure. | 278 // autostarted and the machine is under io pressure. |
122 const int64 kSevenMinutesInMilliseconds = | 279 const int64 kSevenMinutesInMilliseconds = |
123 base::TimeDelta::FromMinutes(7).InMilliseconds(); | 280 base::TimeDelta::FromMinutes(7).InMilliseconds(); |
124 if (base::SysInfo::Uptime() < kSevenMinutesInMilliseconds) { | 281 if (base::SysInfo::Uptime() < kSevenMinutesInMilliseconds) { |
125 g_startup_stats_collection_finished = true; | 282 g_startup_stats_collection_finished = true; |
126 return; | 283 return; |
127 } | 284 } |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
242 SubsystemStartupTimeHash* hash = GetSubsystemStartupTimeHash(); | 399 SubsystemStartupTimeHash* hash = GetSubsystemStartupTimeHash(); |
243 // Only record the initial sample for a given histogram. | 400 // Only record the initial sample for a given histogram. |
244 if (hash->find(histogram_name_) != hash->end()) | 401 if (hash->find(histogram_name_) != hash->end()) |
245 return; | 402 return; |
246 | 403 |
247 (*hash)[histogram_name_] = | 404 (*hash)[histogram_name_] = |
248 base::TimeTicks::Now() - start_time_; | 405 base::TimeTicks::Now() - start_time_; |
249 } | 406 } |
250 | 407 |
251 } // namespace startup_metric_utils | 408 } // namespace startup_metric_utils |
OLD | NEW |