1 // Copyright 2018 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 "chrome/browser/metrics/perf/perf_events_collector.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/files/file_util.h"
12 #include "base/metrics/histogram_functions.h"
13 #include "base/rand_util.h"
14 #include "base/sequenced_task_runner.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/system/sys_info.h"
19 #include "base/task/post_task.h"
20 #include "base/task/thread_pool.h"
21 #include "chrome/browser/metrics/perf/cpu_identity.h"
22 #include "chrome/browser/metrics/perf/process_type_collector.h"
23 #include "chrome/browser/metrics/perf/windowed_incognito_observer.h"
24 #include "chrome/browser/ui/browser_list.h"
25 #include "chromeos/dbus/debug_daemon/debug_daemon_client_provider.h"
26 #include "components/variations/variations_associated_data.h"
27 #include "third_party/metrics_proto/sampled_profile.pb.h"
28 
29 namespace metrics {
30 
31 namespace {
32 
33 const char kCWPFieldTrialName[] = "ChromeOSWideProfilingCollection";
34 
35 // Name the histogram that represents the success and various failure modes for
36 // parsing CPU frequencies.
37 const char kParseFrequenciesHistogramName[] =
38     "ChromeOS.CWP.ParseCPUFrequencies";
39 
40 // Limit the total size of protobufs that can be cached, so they don't take up
41 // too much memory. If the size of cached protobufs exceeds this value, stop
42 // collecting further perf data. The current value is 4 MB.
43 const size_t kCachedPerfDataProtobufSizeThreshold = 4 * 1024 * 1024;
44 
45 // Name of the perf events collector. It is appended to the UMA metric names
46 // for reporting collection and upload status.
47 const char kPerfCollectorName[] = "Perf";
48 
49 // Gets parameter named by |key| from the map. If it is present and is an
50 // integer, stores the result in |out| and return true. Otherwise return false.
GetInt64Param(const std::map<std::string,std::string> & params,const std::string & key,int64_t * out)51 bool GetInt64Param(const std::map<std::string, std::string>& params,
52                    const std::string& key,
53                    int64_t* out) {
54   auto it = params.find(key);
55   if (it == params.end())
56     return false;
57   int64_t value;
58   // NB: StringToInt64 will set value even if the conversion fails.
59   if (!base::StringToInt64(it->second, &value))
60     return false;
61   *out = value;
62   return true;
63 }
64 
65 // Parses the key. e.g.: "PerfCommand::arm::0" returns "arm"
ExtractPerfCommandCpuSpecifier(const std::string & key,std::string * cpu_specifier)66 bool ExtractPerfCommandCpuSpecifier(const std::string& key,
67                                     std::string* cpu_specifier) {
68   std::vector<std::string> tokens = base::SplitStringUsingSubstr(
69       key, "::", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
70   if (tokens.size() != 3)
71     return false;
72   if (tokens[0] != "PerfCommand")
73     return false;
74   *cpu_specifier = tokens[1];
75   // tokens[2] is just a unique string (usually an index).
76   return true;
77 }
78 
79 // Parses the components of a version string, e.g. major.minor.bugfix
ExtractVersionNumbers(const std::string & version,int32_t * major_version,int32_t * minor_version,int32_t * bugfix_version)80 void ExtractVersionNumbers(const std::string& version,
81                            int32_t* major_version,
82                            int32_t* minor_version,
83                            int32_t* bugfix_version) {
84   *major_version = *minor_version = *bugfix_version = 0;
85   // Parse out the version numbers from the string.
86   sscanf(version.c_str(), "%d.%d.%d", major_version, minor_version,
87          bugfix_version);
88 }
89 
90 // Returns if a micro-architecture supports the cycles:ppp event.
MicroarchitectureHasCyclesPPPEvent(const std::string & uarch)91 bool MicroarchitectureHasCyclesPPPEvent(const std::string& uarch) {
92   return uarch == "Goldmont" || uarch == "GoldmontPlus" ||
93          uarch == "Broadwell" || uarch == "Kabylake" || uarch == "Tigerlake";
94 }
95 
96 // Returns if a micro-architecture supports LBR callgraph profiling.
MicroarchitectureHasLBRCallgraph(const std::string & uarch)97 bool MicroarchitectureHasLBRCallgraph(const std::string& uarch) {
98   return uarch == "Haswell" || uarch == "Broadwell" || uarch == "Skylake" ||
99          uarch == "Kabylake" || uarch == "Tigerlake";
100 }
101 
102 // Returns if a kernel release supports LBR callgraph profiling.
KernelReleaseHasLBRCallgraph(const std::string & release)103 bool KernelReleaseHasLBRCallgraph(const std::string& release) {
104   int32_t major, minor, bugfix;
105   ExtractVersionNumbers(release, &major, &minor, &bugfix);
106   return major > 4 || (major == 4 && minor >= 4) || (major == 3 && minor == 18);
107 }
108 
109 // Hopefully we never need a space in a command argument.
110 const char kPerfCommandDelimiter[] = " ";
111 
112 // Collect precise=3 (:ppp) cycle events on microarchitectures that support it.
113 const char kPerfFPCallgraphPPPCmd[] =
114     "perf record -a -e cycles:ppp -g -c 4000037";
115 
116 // Collect default (imprecise) cycle events everywhere else.
117 const char kPerfCyclesCmd[] = "perf record -a -e cycles -c 1000003";
118 
119 const char kPerfFPCallgraphCmd[] = "perf record -a -e cycles -g -c 4000037";
120 
121 const char kPerfLBRCallgraphCmd[] =
122     "perf record -a -e cycles -c 4000037 --call-graph lbr";
123 
124 const char kPerfLBRCmd[] = "perf record -a -e r20c4 -b -c 200011";
125 
126 // Silvermont, Airmont, Goldmont don't have a branches taken event. Therefore,
127 // we sample on the branches retired event.
128 const char kPerfLBRCmdAtom[] = "perf record -a -e rc4 -b -c 300001";
129 
130 // The following events count misses in the last level caches and level 2 TLBs.
131 
132 // TLB miss cycles for IvyBridge, Haswell, Broadwell and SandyBridge.
133 const char kPerfITLBMissCyclesCmdIvyBridge[] =
134     "perf record -a -e itlb_misses.walk_duration -c 30001";
135 
136 const char kPerfDTLBMissCyclesCmdIvyBridge[] =
137     "perf record -a -e dtlb_load_misses.walk_duration -g -c 160001";
138 
139 // TLB miss cycles for Skylake, Kabylake, Tigerlake.
140 const char kPerfITLBMissCyclesCmdSkylake[] =
141     "perf record -a -e itlb_misses.walk_pending -c 30001";
142 
143 const char kPerfDTLBMissCyclesCmdSkylake[] =
144     "perf record -a -e dtlb_load_misses.walk_pending -g -c 160001";
145 
146 // TLB miss cycles for Atom, including Silvermont, Airmont and Goldmont.
147 const char kPerfITLBMissCyclesCmdAtom[] =
148     "perf record -a -e page_walks.i_side_cycles -c 30001";
149 
150 const char kPerfDTLBMissCyclesCmdAtom[] =
151     "perf record -a -e page_walks.d_side_cycles -g -c 160001";
152 
153 const char kPerfLLCMissesCmd[] = "perf record -a -e r412e -g -c 30007";
154 // Precise events (request zero skid) for last level cache misses.
155 const char kPerfLLCMissesPreciseCmd[] =
156     "perf record -a -e r412e:pp -g -c 30007";
157 
GetDefaultCommands_x86_64(const CPUIdentity & cpuid)158 const std::vector<RandomSelector::WeightAndValue> GetDefaultCommands_x86_64(
159     const CPUIdentity& cpuid) {
160   using WeightAndValue = RandomSelector::WeightAndValue;
161   std::vector<WeightAndValue> cmds;
162   DCHECK_EQ(cpuid.arch, "x86_64");
163   const std::string cpu_uarch = GetCpuUarch(cpuid);
164 
165   // We use different perf events for iTLB, dTLB and LBR profiling on different
166   // microarchitectures. Customize each command based on the microarchitecture.
167   const char* itlb_miss_cycles_cmd = kPerfITLBMissCyclesCmdIvyBridge;
168   const char* dtlb_miss_cycles_cmd = kPerfDTLBMissCyclesCmdIvyBridge;
169   const char* lbr_cmd = kPerfLBRCmd;
170   const char* cycles_cmd = kPerfCyclesCmd;
171   const char* fp_callgraph_cmd = kPerfFPCallgraphCmd;
172   const char* lbr_callgraph_cmd = kPerfLBRCallgraphCmd;
173 
174   if (cpu_uarch == "Skylake" || cpu_uarch == "Kabylake" ||
175       cpu_uarch == "Tigerlake" || cpu_uarch == "GoldmontPlus") {
176     itlb_miss_cycles_cmd = kPerfITLBMissCyclesCmdSkylake;
177     dtlb_miss_cycles_cmd = kPerfDTLBMissCyclesCmdSkylake;
178   }
179   if (cpu_uarch == "Silvermont" || cpu_uarch == "Airmont" ||
180       cpu_uarch == "Goldmont") {
181     itlb_miss_cycles_cmd = kPerfITLBMissCyclesCmdAtom;
182     dtlb_miss_cycles_cmd = kPerfDTLBMissCyclesCmdAtom;
183   }
184   if (cpu_uarch == "Silvermont" || cpu_uarch == "Airmont" ||
185       cpu_uarch == "Goldmont" || cpu_uarch == "GoldmontPlus") {
186     lbr_cmd = kPerfLBRCmdAtom;
187   }
188   if (MicroarchitectureHasCyclesPPPEvent(cpu_uarch)) {
189     fp_callgraph_cmd = kPerfFPCallgraphPPPCmd;
190   }
191 
192   cmds.emplace_back(WeightAndValue(50.0, cycles_cmd));
193 
194   // Haswell and newer big Intel cores support LBR callstack profiling. This
195   // requires kernel support, which was added in kernel 4.4, and it was
196   // backported to kernel 3.18. Collect LBR callstack profiling where
197   // supported in addition to FP callchains. The former works with binaries
198   // compiled with frame pointers disabled, but it only captures callchains
199   // after profiling is enabled, so it's likely missing the lower frames of
200   // the callstack.
201   if (MicroarchitectureHasLBRCallgraph(cpu_uarch) &&
202       KernelReleaseHasLBRCallgraph(cpuid.release)) {
203     cmds.emplace_back(WeightAndValue(10.0, fp_callgraph_cmd));
204     cmds.emplace_back(WeightAndValue(10.0, lbr_callgraph_cmd));
205   } else {
206     cmds.emplace_back(WeightAndValue(20.0, fp_callgraph_cmd));
207   }
208 
209   if (cpu_uarch == "IvyBridge" || cpu_uarch == "Haswell" ||
210       cpu_uarch == "Broadwell" || cpu_uarch == "SandyBridge" ||
211       cpu_uarch == "Skylake" || cpu_uarch == "Kabylake" ||
212       cpu_uarch == "Tigerlake" || cpu_uarch == "Silvermont" ||
213       cpu_uarch == "Airmont" || cpu_uarch == "Goldmont" ||
214       cpu_uarch == "GoldmontPlus") {
215     cmds.emplace_back(WeightAndValue(15.0, lbr_cmd));
216     cmds.emplace_back(WeightAndValue(5.0, itlb_miss_cycles_cmd));
217     cmds.emplace_back(WeightAndValue(5.0, dtlb_miss_cycles_cmd));
218     // Only Goldmont and GoldmontPlus support precise events on last level cache
219     // misses.
220     if (cpu_uarch == "Goldmont" || cpu_uarch == "GoldmontPlus") {
221       cmds.emplace_back(WeightAndValue(5.0, kPerfLLCMissesPreciseCmd));
222     } else {
223       cmds.emplace_back(WeightAndValue(5.0, kPerfLLCMissesCmd));
224     }
225     return cmds;
226   }
227   // Other 64-bit x86. We collect LLC misses for other Intel CPUs, but not for
228   // non-Intel CPUs such as AMD, since the event code provided for LLC is
229   // Intel specific.
230   if (cpuid.vendor=="GenuineIntel"){
231     cmds.emplace_back(WeightAndValue(25.0, cycles_cmd));
232     cmds.emplace_back(WeightAndValue(5.0, kPerfLLCMissesCmd));
233   } else {
234     cmds.emplace_back(WeightAndValue(30.0, cycles_cmd));
235   }
236   return cmds;
237 }
238 
OnCollectProcessTypes(SampledProfile * sampled_profile)239 void OnCollectProcessTypes(SampledProfile* sampled_profile) {
240   std::map<uint32_t, Process> process_types =
241       ProcessTypeCollector::ChromeProcessTypes();
242   std::map<uint32_t, Thread> thread_types =
243       ProcessTypeCollector::ChromeThreadTypes();
244   if (!process_types.empty() && !thread_types.empty()) {
245     sampled_profile->mutable_process_types()->insert(process_types.begin(),
246                                                      process_types.end());
247     sampled_profile->mutable_thread_types()->insert(thread_types.begin(),
248                                                     thread_types.end());
249   }
250 }
251 
252 }  // namespace
253 
254 namespace internal {
255 
GetDefaultCommandsForCpu(const CPUIdentity & cpuid)256 std::vector<RandomSelector::WeightAndValue> GetDefaultCommandsForCpu(
257     const CPUIdentity& cpuid) {
258   using WeightAndValue = RandomSelector::WeightAndValue;
259 
260   if (cpuid.arch == "x86_64")  // 64-bit x86
261     return GetDefaultCommands_x86_64(cpuid);
262 
263   std::vector<WeightAndValue> cmds;
264   if (cpuid.arch == "x86" ||      // 32-bit x86, or...
265       cpuid.arch == "armv7l" ||   // ARM32
266       cpuid.arch == "aarch64") {  // ARM64
267     cmds.emplace_back(WeightAndValue(80.0, kPerfCyclesCmd));
268     cmds.emplace_back(WeightAndValue(20.0, kPerfFPCallgraphCmd));
269     return cmds;
270   }
271 
272   // Unknown CPUs
273   cmds.emplace_back(WeightAndValue(1.0, kPerfCyclesCmd));
274   return cmds;
275 }
276 
277 }  // namespace internal
278 
PerfCollector()279 PerfCollector::PerfCollector()
280     : internal::MetricCollector(kPerfCollectorName, CollectionParams()) {}
281 
282 PerfCollector::~PerfCollector() = default;
283 
SetUp()284 void PerfCollector::SetUp() {
285   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
286 
287   // Create DebugdClientProvider to bind its private DBus connection to the
288   // current sequence.
289   debugd_client_provider_ =
290       std::make_unique<chromeos::DebugDaemonClientProvider>();
291 
292   auto task_runner = base::SequencedTaskRunnerHandle::Get();
293   base::ThreadPool::PostTask(
294       FROM_HERE,
295       {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
296        base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
297       base::BindOnce(&PerfCollector::ParseCPUFrequencies, task_runner,
298                      weak_factory_.GetWeakPtr()));
299 
300   CHECK(command_selector_.SetOdds(
301       internal::GetDefaultCommandsForCpu(GetCPUIdentity())));
302   std::map<std::string, std::string> params;
303   if (variations::GetVariationParams(kCWPFieldTrialName, &params)) {
304     SetCollectionParamsFromVariationParams(params);
305   }
306 }
307 
ToolName() const308 const char* PerfCollector::ToolName() const {
309   return kPerfCollectorName;
310 }
311 
312 namespace internal {
313 
FindBestCpuSpecifierFromParams(const std::map<std::string,std::string> & params,const CPUIdentity & cpuid)314 std::string FindBestCpuSpecifierFromParams(
315     const std::map<std::string, std::string>& params,
316     const CPUIdentity& cpuid) {
317   std::string ret;
318   // The CPU specified in the variation params could be "default", a system
319   // architecture, a CPU microarchitecture, or a CPU model substring. We should
320   // prefer to match the most specific.
321   enum MatchSpecificity {
322     NO_MATCH,
323     DEFAULT,
324     SYSTEM_ARCH,
325     CPU_UARCH,
326     CPU_MODEL,
327   };
328   MatchSpecificity match_level = NO_MATCH;
329 
330   const std::string cpu_uarch = GetCpuUarch(cpuid);
331   const std::string simplified_cpu_model =
332       SimplifyCPUModelName(cpuid.model_name);
333 
334   for (const auto& key_val : params) {
335     const std::string& key = key_val.first;
336 
337     std::string cpu_specifier;
338     if (!ExtractPerfCommandCpuSpecifier(key, &cpu_specifier))
339       continue;
340 
341     if (match_level < DEFAULT && cpu_specifier == "default") {
342       match_level = DEFAULT;
343       ret = cpu_specifier;
344     }
345     if (match_level < SYSTEM_ARCH && cpu_specifier == cpuid.arch) {
346       match_level = SYSTEM_ARCH;
347       ret = cpu_specifier;
348     }
349     if (match_level < CPU_UARCH && !cpu_uarch.empty() &&
350         cpu_specifier == cpu_uarch) {
351       match_level = CPU_UARCH;
352       ret = cpu_specifier;
353     }
354     if (match_level < CPU_MODEL &&
355         simplified_cpu_model.find(cpu_specifier) != std::string::npos) {
356       match_level = CPU_MODEL;
357       ret = cpu_specifier;
358     }
359   }
360   return ret;
361 }
362 
363 }  // namespace internal
364 
SetCollectionParamsFromVariationParams(const std::map<std::string,std::string> & params)365 void PerfCollector::SetCollectionParamsFromVariationParams(
366     const std::map<std::string, std::string>& params) {
367   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
368   int64_t value;
369   CollectionParams& collector_params = collection_params();
370   if (GetInt64Param(params, "ProfileCollectionDurationSec", &value)) {
371     collector_params.collection_duration = base::TimeDelta::FromSeconds(value);
372   }
373   if (GetInt64Param(params, "PeriodicProfilingIntervalMs", &value)) {
374     collector_params.periodic_interval =
375         base::TimeDelta::FromMilliseconds(value);
376   }
377   if (GetInt64Param(params, "ResumeFromSuspend::SamplingFactor", &value)) {
378     collector_params.resume_from_suspend.sampling_factor = value;
379   }
380   if (GetInt64Param(params, "ResumeFromSuspend::MaxDelaySec", &value)) {
381     collector_params.resume_from_suspend.max_collection_delay =
382         base::TimeDelta::FromSeconds(value);
383   }
384   if (GetInt64Param(params, "RestoreSession::SamplingFactor", &value)) {
385     collector_params.restore_session.sampling_factor = value;
386   }
387   if (GetInt64Param(params, "RestoreSession::MaxDelaySec", &value)) {
388     collector_params.restore_session.max_collection_delay =
389         base::TimeDelta::FromSeconds(value);
390   }
391 
392   const std::string best_cpu_specifier =
393       internal::FindBestCpuSpecifierFromParams(params, GetCPUIdentity());
394 
395   if (best_cpu_specifier.empty())  // No matching cpu specifier. Keep defaults.
396     return;
397 
398   std::vector<RandomSelector::WeightAndValue> commands;
399   for (const auto& key_val : params) {
400     const std::string& key = key_val.first;
401     const std::string& val = key_val.second;
402 
403     std::string cpu_specifier;
404     if (!ExtractPerfCommandCpuSpecifier(key, &cpu_specifier))
405       continue;
406     if (cpu_specifier != best_cpu_specifier)
407       continue;
408 
409     auto split = val.find(" ");
410     if (split == std::string::npos)
411       continue;  // Just drop invalid commands.
412     std::string weight_str = std::string(val.begin(), val.begin() + split);
413 
414     double weight;
415     if (!(base::StringToDouble(weight_str, &weight) && weight > 0.0))
416       continue;  // Just drop invalid commands.
417     std::string command(val.begin() + split + 1, val.end());
418     commands.push_back(RandomSelector::WeightAndValue(weight, command));
419   }
420   command_selector_.SetOdds(commands);
421 }
422 
CreatePerfOutputCall(base::TimeDelta duration,const std::vector<std::string> & perf_args,PerfOutputCall::DoneCallback callback)423 std::unique_ptr<PerfOutputCall> PerfCollector::CreatePerfOutputCall(
424     base::TimeDelta duration,
425     const std::vector<std::string>& perf_args,
426     PerfOutputCall::DoneCallback callback) {
427   DCHECK(debugd_client_provider_.get());
428   return std::make_unique<PerfOutputCall>(
429       debugd_client_provider_->debug_daemon_client(), duration, perf_args,
430       std::move(callback));
431 }
432 
OnPerfOutputComplete(std::unique_ptr<WindowedIncognitoObserver> incognito_observer,std::unique_ptr<SampledProfile> sampled_profile,bool has_cycles,std::string perf_stdout)433 void PerfCollector::OnPerfOutputComplete(
434     std::unique_ptr<WindowedIncognitoObserver> incognito_observer,
435     std::unique_ptr<SampledProfile> sampled_profile,
436     bool has_cycles,
437     std::string perf_stdout) {
438   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
439 
440   current_trigger_ = SampledProfile::UNKNOWN_TRIGGER_EVENT;
441   // We are done using |perf_output_call| and may destroy it.
442   perf_output_call_ = nullptr;
443 
444   ParseOutputProtoIfValid(std::move(incognito_observer),
445                           std::move(sampled_profile), has_cycles,
446                           std::move(perf_stdout));
447 }
448 
ParseOutputProtoIfValid(std::unique_ptr<WindowedIncognitoObserver> incognito_observer,std::unique_ptr<SampledProfile> sampled_profile,bool has_cycles,std::string perf_stdout)449 void PerfCollector::ParseOutputProtoIfValid(
450     std::unique_ptr<WindowedIncognitoObserver> incognito_observer,
451     std::unique_ptr<SampledProfile> sampled_profile,
452     bool has_cycles,
453     std::string perf_stdout) {
454   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
455 
456   // Check whether an incognito window had been opened during profile
457   // collection. If there was an incognito window, discard the incoming data.
458   if (incognito_observer->IncognitoLaunched()) {
459     AddToUmaHistogram(CollectionAttemptStatus::INCOGNITO_LAUNCHED);
460     return;
461   }
462   if (has_cycles) {
463     // Store CPU max frequencies in the sampled profile.
464     std::copy(max_frequencies_mhz_.begin(), max_frequencies_mhz_.end(),
465               google::protobuf::RepeatedFieldBackInserter(
466                   sampled_profile->mutable_cpu_max_frequency_mhz()));
467   }
468 
469   bool posted = base::ThreadPool::PostTaskAndReply(
470       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
471       base::BindOnce(&OnCollectProcessTypes, sampled_profile.get()),
472       base::BindOnce(&PerfCollector::SaveSerializedPerfProto,
473                      weak_factory_.GetWeakPtr(), std::move(sampled_profile),
474                      std::move(perf_stdout)));
475   DCHECK(posted);
476 }
477 
GetWeakPtr()478 base::WeakPtr<internal::MetricCollector> PerfCollector::GetWeakPtr() {
479   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
480   return weak_factory_.GetWeakPtr();
481 }
482 
ShouldCollect() const483 bool PerfCollector::ShouldCollect() const {
484   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
485   // Only allow one active collection.
486   if (perf_output_call_) {
487     AddToUmaHistogram(CollectionAttemptStatus::ALREADY_COLLECTING);
488     return false;
489   }
490 
491   // Do not collect further data if we've already collected a substantial amount
492   // of data, as indicated by |kCachedPerfDataProtobufSizeThreshold|.
493   if (cached_data_size_ >= kCachedPerfDataProtobufSizeThreshold) {
494     AddToUmaHistogram(CollectionAttemptStatus::NOT_READY_TO_COLLECT);
495     return false;
496   }
497 
498   return true;
499 }
500 
501 namespace internal {
502 
CommandSamplesCPUCycles(const std::vector<std::string> & args)503 bool CommandSamplesCPUCycles(const std::vector<std::string>& args) {
504   // Command must start with "perf record".
505   if (args.size() < 4 || args[0] != "perf" || args[1] != "record")
506     return false;
507   // Cycles event can be either the raw 'cycles' event, or the event name can be
508   // annotated with some qualifier suffix. Check for all cases.
509   for (size_t i = 2; i + 1 < args.size(); ++i) {
510     if (args[i] == "-e" &&
511         (args[i + 1] == "cycles" || args[i + 1].rfind("cycles:", 0) == 0))
512       return true;
513   }
514   return false;
515 }
516 
517 }  // namespace internal
518 
CollectProfile(std::unique_ptr<SampledProfile> sampled_profile)519 void PerfCollector::CollectProfile(
520     std::unique_ptr<SampledProfile> sampled_profile) {
521   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
522 
523   auto incognito_observer = WindowedIncognitoMonitor::CreateObserver();
524   // For privacy reasons, Chrome should only collect perf data if there is no
525   // incognito session active (or gets spawned during the collection).
526   if (incognito_observer->IncognitoActive()) {
527     AddToUmaHistogram(CollectionAttemptStatus::INCOGNITO_ACTIVE);
528     return;
529   }
530 
531   std::vector<std::string> command =
532       base::SplitString(command_selector_.Select(), kPerfCommandDelimiter,
533                         base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
534   bool has_cycles = internal::CommandSamplesCPUCycles(command);
535 
536   DCHECK(sampled_profile->has_trigger_event());
537   current_trigger_ = sampled_profile->trigger_event();
538 
539   perf_output_call_ = CreatePerfOutputCall(
540       collection_params().collection_duration, command,
541       base::BindOnce(&PerfCollector::OnPerfOutputComplete,
542                      weak_factory_.GetWeakPtr(), std::move(incognito_observer),
543                      std::move(sampled_profile), has_cycles));
544 }
545 
546 // static
ParseCPUFrequencies(scoped_refptr<base::SequencedTaskRunner> task_runner,base::WeakPtr<PerfCollector> perf_collector)547 void PerfCollector::ParseCPUFrequencies(
548     scoped_refptr<base::SequencedTaskRunner> task_runner,
549     base::WeakPtr<PerfCollector> perf_collector) {
550   const char kCPUMaxFreqPath[] =
551       "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq";
552   int num_cpus = base::SysInfo::NumberOfProcessors();
553   int num_zeros = 0;
554   std::vector<uint32_t> frequencies_mhz;
555   for (int i = 0; i < num_cpus; ++i) {
556     std::string content;
557     unsigned int frequency_khz = 0;
558     auto path = base::StringPrintf(kCPUMaxFreqPath, i);
559     if (ReadFileToString(base::FilePath(path), &content)) {
560       DCHECK(!content.empty());
561       base::StringToUint(content, &frequency_khz);
562     }
563     if (frequency_khz == 0) {
564       num_zeros++;
565     }
566     // Convert kHz frequencies to MHz.
567     frequencies_mhz.push_back(static_cast<uint32_t>(frequency_khz / 1000));
568   }
569   if (num_cpus == 0) {
570     base::UmaHistogramEnumeration(kParseFrequenciesHistogramName,
571                                   ParseFrequencyStatus::kNumCPUsIsZero);
572   } else if (num_zeros == num_cpus) {
573     base::UmaHistogramEnumeration(kParseFrequenciesHistogramName,
574                                   ParseFrequencyStatus::kAllZeroCPUFrequencies);
575   } else if (num_zeros > 0) {
576     base::UmaHistogramEnumeration(
577         kParseFrequenciesHistogramName,
578         ParseFrequencyStatus::kSomeZeroCPUFrequencies);
579   } else {
580     base::UmaHistogramEnumeration(kParseFrequenciesHistogramName,
581                                   ParseFrequencyStatus::kSuccess);
582   }
583   task_runner->PostTask(FROM_HERE,
584                         base::BindOnce(&PerfCollector::SaveCPUFrequencies,
585                                        perf_collector, frequencies_mhz));
586 }
587 
SaveCPUFrequencies(const std::vector<uint32_t> & frequencies)588 void PerfCollector::SaveCPUFrequencies(
589     const std::vector<uint32_t>& frequencies) {
590   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
591   max_frequencies_mhz_ = frequencies;
592 }
593 
StopCollection()594 void PerfCollector::StopCollection() {
595   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
596   // StopCollection() can be called when a jank lasts for longer than the max
597   // collection duration, and a new collection is requested by another trigger.
598   // In this case, ignore the request to stop the collection.
599   if (current_trigger_ != SampledProfile::JANKY_TASK)
600     return;
601 
602   if (perf_output_call_)
603     perf_output_call_->Stop();
604 }
605 
606 }  // namespace metrics
607