blob: 7e049c9f62fd6eb93a5cd494f88f98d306996782 [file] [log] [blame]
Ahmad Sharifae1714d2013-01-17 11:29:37 -08001// Copyright (c) 2013 The Chromium OS 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
Alex Vakulenko262be3f2014-07-30 15:25:50 -07005#include "debugd/src/perf_tool.h"
Ahmad Sharifae1714d2013-01-17 11:29:37 -08006
David Sharpe01f7252015-06-30 16:24:05 -07007#include <base/strings/string_number_conversions.h>
Ben Chan9953a592014-02-05 23:32:00 -08008#include <base/strings/string_split.h>
Simon Que21bb7902014-07-28 16:17:20 -07009#include <base/strings/string_util.h>
Simon Que795327b2015-03-16 17:42:35 -070010#include <sys/utsname.h>
Simon Que21bb7902014-07-28 16:17:20 -070011
12#include <algorithm>
David Sharpe01f7252015-06-30 16:24:05 -070013#include <map>
Ahmad Sharif3abea3c2013-05-13 20:43:04 -070014
Alex Vakulenko262be3f2014-07-30 15:25:50 -070015#include "debugd/src/cpu_info_parser.h"
16#include "debugd/src/process_with_output.h"
Simon Que795327b2015-03-16 17:42:35 -070017#include "debugd/src/random_selector.h"
Ahmad Sharifae1714d2013-01-17 11:29:37 -080018
Ben Chan55903dd2014-04-24 00:29:04 -070019using base::StringPrintf;
20
David Sharpe01f7252015-06-30 16:24:05 -070021namespace debugd {
22
Ahmad Shariff5597f62013-04-25 12:25:41 -070023namespace {
24
David Sharp61901312015-08-05 13:32:07 -070025const char kUnsupportedPerfToolErrorName[] =
26 "org.chromium.debugd.error.UnsupportedPerfTool";
27
Ahmad Shariff5597f62013-04-25 12:25:41 -070028// Location of quipper on ChromeOS.
29const char kQuipperLocation[] = "/usr/bin/quipper";
30
Simon Que21bb7902014-07-28 16:17:20 -070031// This is registered trademark symbol that appears in model name strings.
32const char kRegisteredTrademarkSymbol[] = "(R)";
33
Ahmad Sharif3abea3c2013-05-13 20:43:04 -070034// Processor model name substrings for which we have perf commands.
Simon Que795327b2015-03-16 17:42:35 -070035
36// For 64-bit x86 processors.
37const char* kx86_64CPUOddsFiles[] = {
Simon Que795327b2015-03-16 17:42:35 -070038 NULL,
Ahmad Sharif3abea3c2013-05-13 20:43:04 -070039};
Ahmad Shariff5597f62013-04-25 12:25:41 -070040
Simon Que795327b2015-03-16 17:42:35 -070041// For 32-bit x86 processors.
42const char* kx86_32CPUOddsFiles[] = {
43 // 32-bit x86 doesn't have any special cases, so all processors use the
44 // default commands. Add future special cases here.
45 NULL,
46};
47
48// For ARMv7 processors.
49const char* kARMv7CPUOddsFiles[] = {
50 // ARMv7 doesn't have any special cases, so all processors use the default
51 // commands. Add future special cases here.
52 NULL,
53};
54
55// For miscellaneous processors models of a known architecture.
56const char kMiscCPUModelOddsFile[] = "default";
57
58// Odds file name for miscellaneous processor architectures.
59const char kMiscCPUArchOddsFile[] = "unknown";
60
Ahmad Sharif3abea3c2013-05-13 20:43:04 -070061// Prefix path to attach to the CPU odds file.
Ahmad Sharifbdb33912013-07-16 11:44:49 -040062const char kCPUOddsFilePrefix[] = "/etc/perf_commands/";
Ahmad Shariff5597f62013-04-25 12:25:41 -070063
Ahmad Sharif3abea3c2013-05-13 20:43:04 -070064// Suffix to attach to the CPU odds file.
65const char kCPUOddsFileSuffix[] = ".txt";
Ahmad Shariff5597f62013-04-25 12:25:41 -070066
David Sharpe01f7252015-06-30 16:24:05 -070067const std::map<std::string, std::string> kIntelUarchFileTable {
68 // These were found on various sources on the Internet. Main ones are:
69 // http://instlatx64.atw.hu/ for CPUID to model name and
70 // http://www.cpu-world.com for model name to microarchitecture
71 // {"06_1C", "Bonnell"}, // Atom
72 // {"06_26", "Bonnell"}, // Atom
73 // {"06_36", "Saltwell"}, // Atom
74 // {"06_4C", "Airmont"}, // Braswell
75 // {"06_4E", "Skylake"},
David Sharp75bfc0e2015-07-09 14:50:10 -070076 // {"06_37", "Silvermont"},
77 {"06_56", "Broadwell"}, // Broadwell-DE
78 {"06_47", "Broadwell"}, // Broadwell-H
79 {"06_3D", "Broadwell"},
David Sharpe01f7252015-06-30 16:24:05 -070080 {"06_3C", "Haswell"},
81 {"06_3F", "Haswell"},
82 {"06_45", "Haswell"},
83 {"06_46", "Haswell"},
84 {"06_3A", "IvyBridge"},
85 {"06_3E", "IvyBridge"},
86 {"06_2A", "SandyBridge"},
87 {"06_2D", "SandyBridge"},
88 // {"06_0F", "Merom"},
89 // {"06_16", "Merom"},
90 // {"06_17", "Nehalem"},
91 // {"06_1A", "Nehalem"},
92 // {"06_1D", "Nehalem"},
93 // {"06_1E", "Nehalem"},
94 // {"06_1F", "Nehalem"},
95 // {"06_2E", "Nehalem"},
96 // {"06_0D", "Dothan"},
97 // {"06_09", "Banias"},
98 // {"0F_03", "Prescott"},
99 // {"0F_04", "Prescott"},
100 // {"0F_06", "Presler"},
101 // {"06_25", "Westmere"},
102 // {"06_2C", "Westmere"},
103 // {"06_2F", "Westmere"},
104};
105
David Sharp0001e252015-08-27 15:59:31 -0700106// Struct containing the parsed CPU identity
107struct CPUIdentity {
108 // The system architecture from uname().
109 // (Technically, not a property of the CPU.)
110 std::string arch;
111 // CPU model name. e.g. "Intel(R) Celeron(R) 2955U @ 1.40GHz"
112 std::string model_name;
113 // For Intel CPUs, the family_model numeric identifiers from CPUID, in
114 // underscore-separated uppercase hex. e.g. "06_2A"
115 std::string intel_family_model;
116};
117
Simon Queecb08352015-10-02 17:38:12 -0700118enum PerfSubcommand {
119 PERF_COMMAND_RECORD,
120 PERF_COMMAND_STAT,
121 PERF_COMMAND_MEM,
122 PERF_COMMAND_UNSUPPORTED,
123};
124
125// Returns one of the above enums given an vector of perf arguments, starting
126// with "perf" itself in |args[0]|.
127PerfSubcommand GetPerfSubcommandType(const std::vector<std::string>& args) {
128 if (args[0] == "perf" && args.size() > 1) {
129 if (args[1] == "record")
130 return PERF_COMMAND_RECORD;
131 if (args[1] == "stat")
132 return PERF_COMMAND_STAT;
133 if (args[1] == "mem")
134 return PERF_COMMAND_MEM;
135 }
136
137 return PERF_COMMAND_UNSUPPORTED;
138}
139
David Sharp0001e252015-08-27 15:59:31 -0700140// Fills in |model_name| and maybe |intel_family_model| fields of |cpuid|.
141void ParseCPUModel(const CPUInfoParser& cpu_info_parser, CPUIdentity* cpuid) {
142 // Get CPU model name, e.g. "Intel(R) Celeron(R) 2955U @ 1.40GHz".
143 cpu_info_parser.GetKey("model name", &cpuid->model_name);
144 std::string vendor;
145 cpu_info_parser.GetKey("vendor_id", &vendor);
146 if (vendor == "GenuineIntel") {
147 std::string cpu_family;
148 cpu_info_parser.GetKey("cpu family", &cpu_family);
149 unsigned int cpu_family_int;
150 base::StringToUint(cpu_family, &cpu_family_int);
151
152 std::string model;
153 cpu_info_parser.GetKey("model", &model);
154 unsigned int model_int;
155 base::StringToUint(model, &model_int);
156
157 cpuid->intel_family_model =
158 StringPrintf("%02X_%02X", cpu_family_int, model_int);
159 }
160}
161
Simon Que21bb7902014-07-28 16:17:20 -0700162// Converts an CPU model name string into a format that can be used as a file
163// name. The rules are:
164// - Replace spaces with hyphens.
165// - Strip all "(R)" symbols.
166// - Convert to lower case.
167std::string ModelNameToFileName(const std::string& model_name) {
168 std::string result = model_name;
169 std::replace(result.begin(), result.end(), ' ', '-');
170 ReplaceSubstringsAfterOffset(&result, 0, kRegisteredTrademarkSymbol, "");
Ben Chane1ca84e2014-09-05 08:20:59 -0700171 return base::StringToLowerASCII(result);
Simon Que21bb7902014-07-28 16:17:20 -0700172}
173
David Sharp0001e252015-08-27 15:59:31 -0700174// For the given |cpuid|, look for the CPU odds file that corresponds to this
175// CPU. If no matches are found for |cpuid.arch|, return the odds file for
176// unknown CPU types. If the arch is valid, but no matches are found for
177// |cpuid.model_name|, look for an odds file for the microarchitecture (only
178// Intel uarchs currently supported). Otherwise, return the odds file for
179// unknown models of the CPU architecture.
180std::string GetOddsFilenameForCPU(const CPUIdentity& cpuid) {
181 const std::string& arch = cpuid.arch;
182 const std::string& model_name = cpuid.model_name;
Simon Que795327b2015-03-16 17:42:35 -0700183 const char** cpu_odds_file_list = NULL;
David Sharpe01f7252015-06-30 16:24:05 -0700184 if (arch == "i386" || arch == "i486" || arch == "i586" || arch == "i686") {
Simon Que795327b2015-03-16 17:42:35 -0700185 cpu_odds_file_list = kx86_32CPUOddsFiles;
David Sharpe01f7252015-06-30 16:24:05 -0700186 } else if (arch == "amd64" || arch == "x86_64") {
Simon Que795327b2015-03-16 17:42:35 -0700187 cpu_odds_file_list = kx86_64CPUOddsFiles;
David Sharpe01f7252015-06-30 16:24:05 -0700188 } else if (arch == "armv7l") {
Simon Que795327b2015-03-16 17:42:35 -0700189 cpu_odds_file_list = kARMv7CPUOddsFiles;
190 } else {
191 // If the CPU arch doesn't match any of the recognized arch families, just
192 // use the CPU odds file for unknown CPU types.
193 return kMiscCPUArchOddsFile;
194 }
195
David Sharpe01f7252015-06-30 16:24:05 -0700196 std::string adjusted_model_name = ModelNameToFileName(model_name);
Simon Que795327b2015-03-16 17:42:35 -0700197 for (size_t i = 0; cpu_odds_file_list[i]; ++i) {
198 if (adjusted_model_name.find(cpu_odds_file_list[i]) != std::string::npos) {
David Sharpe01f7252015-06-30 16:24:05 -0700199 return arch + "/" + cpu_odds_file_list[i];
Ahmad Sharif3abea3c2013-05-13 20:43:04 -0700200 }
201 }
David Sharpe01f7252015-06-30 16:24:05 -0700202
David Sharp0001e252015-08-27 15:59:31 -0700203 if (!cpuid.intel_family_model.empty()) {
David Sharpe01f7252015-06-30 16:24:05 -0700204 // See if we have a microarchitecture-specific file.
David Sharp0001e252015-08-27 15:59:31 -0700205 const auto& it = kIntelUarchFileTable.find(cpuid.intel_family_model);
David Sharpe01f7252015-06-30 16:24:05 -0700206 if (it != kIntelUarchFileTable.end()) {
207 const std::string& uarch = it->second;
208 return arch + "/" + uarch;
209 }
210 }
211
Simon Que795327b2015-03-16 17:42:35 -0700212 // If there isn't an odds file for the particular model, use the generic odds
213 // for the CPU arch.
David Sharpe01f7252015-06-30 16:24:05 -0700214 return arch + "/" + kMiscCPUModelOddsFile;
Ahmad Shariff5597f62013-04-25 12:25:41 -0700215}
216
217} // namespace
218
David Sharp0001e252015-08-27 15:59:31 -0700219PerfTool::PerfTool() : PerfTool(CPUInfoParser(), new RandomSelector, uname) {}
Simon Que795327b2015-03-16 17:42:35 -0700220
David Sharp0001e252015-08-27 15:59:31 -0700221PerfTool::PerfTool(const CPUInfoParser& cpuinfo,
222 RandomSelector* random_selector,
223 UnameFunc uname_func)
Simon Que795327b2015-03-16 17:42:35 -0700224 : random_selector_(random_selector) {
David Sharp0001e252015-08-27 15:59:31 -0700225 struct CPUIdentity cpuid = {};
226 ParseCPUModel(cpuinfo, &cpuid);
227
228 // Get CPU machine hardware class, e.g. "i686", "x86_64", "armv7l".
229 struct utsname uname_info;
230 if (!uname_func(&uname_info))
231 cpuid.arch = uname_info.machine;
232
233 std::string odds_filename = GetOddsFilenameForCPU(cpuid);
Simon Que795327b2015-03-16 17:42:35 -0700234 random_selector_->SetOddsFromFile(
235 kCPUOddsFilePrefix + odds_filename + kCPUOddsFileSuffix);
Ahmad Shariff5597f62013-04-25 12:25:41 -0700236}
Ahmad Sharifae1714d2013-01-17 11:29:37 -0800237
David Sharp61901312015-08-05 13:32:07 -0700238int PerfTool::GetPerfOutput(const uint32_t& duration_secs,
239 const std::vector<std::string>& perf_args,
240 std::vector<uint8_t>* perf_data,
241 std::vector<uint8_t>* perf_stat,
242 DBus::Error* error) {
Simon Queecb08352015-10-02 17:38:12 -0700243 PerfSubcommand subcommand = GetPerfSubcommandType(perf_args);
244 if (subcommand == PERF_COMMAND_UNSUPPORTED) {
David Sharp61901312015-08-05 13:32:07 -0700245 error->set(kUnsupportedPerfToolErrorName,
Simon Queecb08352015-10-02 17:38:12 -0700246 "perf_args must begin with {\"perf\", \"record\"}, "
247 " {\"perf\", \"stat\"}, or {\"perf\", \"mem\"}");
David Sharp61901312015-08-05 13:32:07 -0700248 return -1;
249 }
250
Simon Queac5f9cf2015-06-20 13:50:22 -0700251 std::string output_string;
252 int result =
253 GetPerfOutputHelper(duration_secs, perf_args, error, &output_string);
254
Simon Queecb08352015-10-02 17:38:12 -0700255 switch (subcommand) {
256 case PERF_COMMAND_RECORD:
257 case PERF_COMMAND_MEM:
Simon Queac5f9cf2015-06-20 13:50:22 -0700258 perf_data->assign(output_string.begin(), output_string.end());
Simon Queecb08352015-10-02 17:38:12 -0700259 break;
260 case PERF_COMMAND_STAT:
Simon Queac5f9cf2015-06-20 13:50:22 -0700261 perf_stat->assign(output_string.begin(), output_string.end());
Simon Queecb08352015-10-02 17:38:12 -0700262 break;
263 default:
264 // Discard the output.
265 break;
266 }
Simon Queac5f9cf2015-06-20 13:50:22 -0700267
268 return result;
269}
270
David Sharp4324e9a2015-08-05 14:23:41 -0700271int PerfTool::GetRandomPerfOutput(const uint32_t& duration_secs,
272 std::vector<uint8_t>* perf_data,
273 std::vector<uint8_t>* perf_stat,
274 DBus::Error* error) {
275 const std::vector<std::string>& perf_args = random_selector_->GetNext();
276 return GetPerfOutput(
277 duration_secs, perf_args, perf_data, perf_stat, error);
278}
279
280std::vector<uint8_t> PerfTool::GetRichPerfData(const uint32_t& duration_secs,
281 DBus::Error* error) {
282 const std::vector<std::string>& perf_args = random_selector_->GetNext();
283 if (perf_args[1] != "record")
284 return std::vector<uint8_t>();
285
286 std::string output_string;
287 int result =
288 GetPerfOutputHelper(duration_secs, perf_args, error, &output_string);
289
290 if (result > 0)
291 return std::vector<uint8_t>();
292
293 return std::vector<uint8_t>(output_string.begin(), output_string.end());
294}
295
Simon Queac5f9cf2015-06-20 13:50:22 -0700296int PerfTool::GetPerfOutputHelper(const uint32_t& duration_secs,
297 const std::vector<std::string>& perf_args,
298 DBus::Error* error,
299 std::string* data_string) {
Ahmad Sharifae1714d2013-01-17 11:29:37 -0800300 // This whole method is synchronous, so we create a subprocess, let it run to
301 // completion, then gather up its output to return it.
302 ProcessWithOutput process;
303 process.SandboxAs("root", "root");
304 if (!process.Init())
305 *data_string = "<process init failed>";
306 // If you're going to add switches to a command, have a look at the Process
307 // interface; there's support for adding options specifically.
Ahmad Shariff5597f62013-04-25 12:25:41 -0700308 process.AddArg(kQuipperLocation);
309 process.AddArg(StringPrintf("%u", duration_secs));
David Sharpdada3d02015-02-09 18:24:48 -0800310 for (const auto& arg : perf_args) {
311 process.AddArg(arg);
312 }
Ahmad Sharifae1714d2013-01-17 11:29:37 -0800313 // Run the process to completion. If the process might take a while, you may
314 // have to make this asynchronous using .Start().
315 int status = process.Run();
316 if (status != 0)
317 *data_string = StringPrintf("<process exited with status: %d", status);
318 process.GetOutput(data_string);
Simon Queac5f9cf2015-06-20 13:50:22 -0700319
320 return status;
Ahmad Sharifae1714d2013-01-17 11:29:37 -0800321}
322
Ben Chana0011d82014-05-13 00:19:29 -0700323} // namespace debugd